FastLED 3.10.6
Loading...
Searching...
No Matches
wave_simulation_real.h
Go to the documentation of this file.
1/*
2Wave simulation classes for 1D and 2D simulations. This is called _Real because
3there is a one to one mapping between the simulation and the LED grid. For
4flexible super sampling see wave_simluation.h.
5
6Based on works and code by Shawn Silverman.
7*/
8
9#pragma once
10
11#include "fl/stl/stdint.h"
12
13#include "fl/stl/vector.h"
14#include "fl/stl/noexcept.h"
15
16namespace fl {
17
18namespace wave_detail {
19i16 float_to_fixed(float f);
20
21// Convert fixed Q15 to float.
22float fixed_to_float(i16 f);
23
24// Compute the Q15 damping decay factor for a power-of-two damping
25// exponent. Equivalent (modulo 1-LSB rounding) to the arithmetic-shift
26// form `f -= f >> damp` used by the kernel before #3099/§6.
28} // namespace wave_detail
29
31 public:
32 // Constructor:
33 // - length: inner simulation grid length (excluding the 2 boundary cells).
34 // - speed: simulation speed (Courant squared, C^2) stored in Q15.
35 // Clamped to [0.0, 1.0] (CFL stability bound for the 1D 5-point
36 // stencil). Values outside that range produce visual instability
37 // and are silently clamped.
38 // - dampening: exponent so that the effective damping factor is
39 // 2^(dampening).
40 WaveSimulation1D_Real(u32 length, float speed = 0.16f,
41 int dampening = 6) FL_NO_EXCEPT;
43
44 // Set simulation speed (Courant squared). Clamped to [0.0, 1.0] — see
45 // constructor for rationale.
46 void setSpeed(float something);
47
48 // Set the dampening exponent (effective damping factor is 2^(dampening)).
49 void setDampening(int damp) FL_NO_EXCEPT;
50
51 // Get the current dampening exponent.
52 int getDampenening() const;
53
54 // Get the simulation speed as a float.
55 float getSpeed() const;
56
57 void setHalfDuplex(bool on) { mHalfDuplex = on; }
58
59 bool getHalfDuplex() const { return mHalfDuplex; }
60
61 // Get the simulation value at the inner grid cell x (converted to float in
62 // the range [-1.0, 1.0]).
63 float getf(fl::size x) const;
64
65 i16 geti16(fl::size x) const;
66 i16 geti16Previous(fl::size x) const;
67
68 i8 geti8(fl::size x) const { return static_cast<i8>(geti16(x) >> 8); }
69
70 // If mHalfDuplex is set then the the values are adjusted so that negative
71 // values will instead be represented by zero.
72 u8 getu8(fl::size x) const {
73 i16 value = geti16(x);
74 // Rebase the range from [-32768, 32767] to [0, 65535] then extract the
75 // upper 8 bits.
76 // return static_cast<u8>(((static_cast<u16>(value) + 32768))
77 // >>
78 // 8);
79 if (mHalfDuplex) {
80 u16 v2 = static_cast<u16>(value);
81 v2 *= 2;
82 return static_cast<u8>(v2 >> 8);
83 } else {
84 return static_cast<u8>(
85 ((static_cast<u16>(value) + 32768)) >> 8);
86 }
87 }
88
89 // Returns whether x is within the inner grid bounds.
90 bool has(fl::size x) const;
91
92 // Set the value at grid cell x (expected range: [-1.0, 1.0]); the value is
93 // stored in Q15.
94 void set(fl::size x, float value);
95
96 // Advance the simulation one time step.
97 void update();
98
99 private:
100 u32 length; // Length of the inner simulation grid.
101 // Two grids stored in fixed Q15 format, each with length+2 entries
102 // (including boundary cells).
105 fl::size whichGrid; // Indicates the active grid (0 or 1).
106
107 i16 mCourantSq; // Simulation speed (courant squared) stored in Q15.
108 int mDampenening; // Dampening exponent (damping factor = 2^(mDampenening)).
109 // Precomputed Q15 decay multiplier (1 - 1/2^mDampenening). The inner
110 // loop uses `f = (i64(f) * mDampDecayQ15) >> 15` instead of the shift,
111 // which generalizes cleanly to non-power-of-two damping if the public
112 // API ever needs it.
115 true; // Flag to restrict values to positive range during update.
116
117};
118
119// Discrete Laplacian stencil for the 2D wave PDE.
120// FivePoint — standard 5-point: N+S+E+W - 4*C, O(h^2) accurate
121// but anisotropic (waves visibly prefer cardinal
122// directions, producing square-ish ripples at high
123// super-sample factors).
124// NinePointIsotropic — 1/6 * sum(diagonals) + 2/3 * sum(neighbors)
125// - 10/3 * C, also O(h^2) but with isotropic
126// leading error. ~2x reads + ALU per cell; gives
127// visibly rounder ripples at SUPER_SAMPLE_2X and
128// above. The wave_simulation.h wrapper auto-
129// selects this when SuperSample >= 2X.
134
136 public:
137 // Constructor: Initializes the simulation with inner grid size (W x H).
138 // The grid dimensions include a 1-cell border on each side.
139 // - speed: Courant squared (C^2) stored in Q15. Clamped to [0.0, 0.5]
140 // (CFL stability bound for the 2D 5-point stencil — stricter than 1D).
141 // Values outside that range produce visual instability and are
142 // silently clamped.
143 // - dampening: exponent so that the damping factor is 2^dampening.
144 WaveSimulation2D_Real(u32 W, u32 H, float speed = 0.16f,
145 float dampening = 6.0f) FL_NO_EXCEPT;
146
147 // Tag for explicit PSRAM-backed grid storage. Use the tagged
148 // constructor overload when you genuinely need a grid larger than
149 // SRAM can hold AND accept the per-cell perf cost (~5-10x slower
150 // on ESP32-S3 without L2 cache; ~no cost on ESP32-P4 with L2 +
151 // cached PSRAM). See #3114 / #3117 for the SRAM-default rationale.
152 //
153 // Example:
154 // WaveSimulation2D_Real sim{
155 // WaveSimulation2D_Real::PsramStorage{}, 256, 256};
156 struct PsramStorage {};
157
159 float speed = 0.16f,
160 float dampening = 6.0f) FL_NO_EXCEPT;
161
163
164 // Set the simulation speed (Courant squared). Clamped to [0.0, 0.5] — see
165 // constructor for rationale.
166 void setSpeed(float something);
167
168 // Set the dampening factor exponent.
169 // The dampening factor used is 2^(dampening).
170 void setDampening(int damp) FL_NO_EXCEPT;
171
172 // Get the current dampening exponent.
173 int getDampenening() const;
174
175 // Get the simulation speed as a float (converted from fixed Q15).
176 float getSpeed() const;
177
178 // Return the value at an inner grid cell (x,y), converted to float.
179 // The value returned is in the range [-1.0, 1.0].
180 float getf(fl::size x, fl::size y) const;
181
182 // Return the value at an inner grid cell (x,y) as a fixed Q15 integer
183 // in the range [-32768, 32767].
184 i16 geti16(fl::size x, fl::size y) const;
185 i16 geti16Previous(fl::size x, fl::size y) const;
186
187 i8 geti8(fl::size x, fl::size y) const {
188 return static_cast<i8>(geti16(x, y) >> 8);
189 }
190
191 u8 getu8(fl::size x, fl::size y) const {
192 i16 value = geti16(x, y);
193 // Rebase the range from [-32768, 32767] to [0, 65535] then extract the
194 // upper 8 bits.
195 // return static_cast<u8>(((static_cast<u16>(value) + 32768))
196 // >>
197 // 8);
198 if (mHalfDuplex) {
199 u16 v2 = static_cast<u16>(value);
200 v2 *= 2;
201 return static_cast<u8>(v2 >> 8);
202 } else {
203 return static_cast<u8>(
204 ((static_cast<u16>(value) + 32768)) >> 8);
205 }
206 }
207
208 // Select the discrete Laplacian stencil used by update(). Default is
209 // FivePoint (backward compatible). NinePointIsotropic costs ~2x reads
210 // + ALU per cell but produces visibly rounder ripples at high super-
211 // sample factors; the wrapper class auto-selects it when appropriate.
214
215 void setXCylindrical(bool on) { mXCylindrical = on; }
216
217 // Check if (x,y) is within the inner grid.
218 bool has(fl::size x, fl::size y) const;
219
220 // Set the value at an inner grid cell using a float;
221 // the value is stored in fixed Q15 format.
222 // value shoudl be between -1.0 and 1.0.
223 void setf(fl::size x, fl::size y, float value);
224
225 void seti16(fl::size x, fl::size y, i16 value);
226
227 void setHalfDuplex(bool on) { mHalfDuplex = on; }
228
229 bool getHalfDuplex() const { return mHalfDuplex; }
230
231 // Advance the simulation one time step using fixed-point arithmetic.
232 void update();
233
234 u32 getWidth() const { return width; }
235 u32 getHeight() const { return height; }
236
237 private:
238 u32 width; // Width of the inner grid.
239 u32 height; // Height of the inner grid.
240 u32 stride; // Row length (width + 2 for the borders).
241
242 // Two separate grids stored in fixed Q15 format.
243 //
244 // SRAM, not PSRAM. The previous `fl::vector_psram<i16>` default
245 // landed both grids in PSRAM on every ESP32 with PSRAM available —
246 // including ESP32-S3, which has no L2 cache and pays the full
247 // ~80 ns PSRAM latency per cell access. The 5-point stencil reads
248 // 5 cells per inner cell, so a 64×64 update was reading ~80 KB per
249 // step at ~50 MB/s practical PSRAM throughput → ~1.6 ms per step
250 // on memory alone, cratering frame rate at the grid sizes users
251 // actually want. SRAM is predictable everywhere: ESP32-S3 has
252 // 320 KB SRAM (64×64 i16 = 8 KB, 128×128 = 32 KB, both fit
253 // easily); ESP32-P4 ~750 KB SRAM (256×256 fits); Teensy 4 OCRAM
254 // is faster still.
255 //
256 // Users with grids genuinely too large for SRAM can construct a
257 // parallel large-grid variant that re-enables PSRAM as an explicit,
258 // documented opt-in (see #3114 follow-up).
261
262 fl::size whichGrid; // Indicates the active grid (0 or 1).
263
264 i16 mCourantSq; // Fixed speed parameter in Q15.
265 int mDampening; // Dampening exponent; used as 2^(dampening).
266 // Precomputed Q15 decay multiplier (1 - 1/2^mDampening). See the 1D
267 // class for the rationale.
270 true; // Flag to restrict values to positive range during update.
271 bool mXCylindrical = false; // Default to non-cylindrical mode
273 LaplacianStencil::FivePoint; // Backward-compatible default
274};
275
276} // namespace fl
uint16_t speed
Definition Noise.ino:72
static const int H
Definition PerfDisc.ino:21
static const int W
Definition PerfDisc.ino:20
void set(fl::size x, float value)
void setDampening(int damp) FL_NO_EXCEPT
WaveSimulation1D_Real(u32 length, float speed=0.16f, int dampening=6) FL_NO_EXCEPT
~WaveSimulation1D_Real() FL_NO_EXCEPT=default
void setf(fl::size x, fl::size y, float value)
i16 geti16Previous(fl::size x, fl::size y) const
u8 getu8(fl::size x, fl::size y) const
i8 geti8(fl::size x, fl::size y) const
LaplacianStencil getStencil() const FL_NO_EXCEPT
i16 geti16(fl::size x, fl::size y) const
WaveSimulation2D_Real(u32 W, u32 H, float speed=0.16f, float dampening=6.0f) FL_NO_EXCEPT
void setStencil(LaplacianStencil s) FL_NO_EXCEPT
void setDampening(int damp) FL_NO_EXCEPT
float getf(fl::size x, fl::size y) const
void seti16(fl::size x, fl::size y, i16 value)
bool has(fl::size x, fl::size y) const
fl::UISlider dampening("Dampening", 6.0f, 0.0f, 10.0f, 0.1f)
i16 compute_damp_decay_q15(int damp) FL_NO_EXCEPT
unsigned char u8
Definition stdint.h:131
constexpr int type_rank< T >::value
InputGamut g FL_NO_EXCEPT
Definition rgbw.h:121
signed char i8
Definition stdint.h:130
Base definition for an LED controller.
Definition crgb.hpp:179