FastLED 3.9.15
Loading...
Searching...
No Matches
wave_simulation_real.cpp
Go to the documentation of this file.
1// Based on works and code by Shawn Silverman.
2
3#include <stdint.h>
4
5#include "fl/clamp.h"
6#include "fl/namespace.h"
8
9namespace fl {
10
11// Define Q15 conversion constants.
12// #define FIXED_SCALE (1 << 15) // 32768: 1.0 in Q15
13#define INT16_POS (32767) // Maximum value for int16_t
14#define INT16_NEG (-32768) // Minimum value for int16_t
15
16namespace wave_detail { // Anonymous namespace for internal linkage
17// Convert float to fixed Q15.
18// int16_t float_to_fixed(float f) { return (int16_t)(f * FIXED_SCALE); }
19
20int16_t float_to_fixed(float f) {
21 f = fl::clamp(f, -1.0f, 1.0f);
22 if (f < 0.0f) {
23 return (int16_t)(f * INT16_NEG);
24 } else {
25 return (int16_t)(f * INT16_POS); // Round to nearest
26 }
27}
28
29// Convert fixed Q15 to float.
30float fixed_to_float(int16_t f) {
31 // return ((float)f) / FIXED_SCALE;
32 if (f < 0) {
33 return ((float)f) / INT16_NEG; // Negative values
34 } else {
35 return ((float)f) / INT16_POS; // Positive values
36 }
37}
38
39// // Multiply two Q15 fixed point numbers.
40// int16_t fixed_mul(int16_t a, int16_t b) {
41// return (int16_t)(((int32_t)a * b) >> 15);
42// }
43} // namespace wave_detail
44
45using namespace wave_detail;
46
48 int dampening)
49 : length(len),
50 grid1(length + 2), // Initialize vector with correct size
51 grid2(length + 2), // Initialize vector with correct size
52 whichGrid(0),
54 // Additional initialization can be added here if needed.
55}
56
57void WaveSimulation1D_Real::setSpeed(float something) {
58 mCourantSq = float_to_fixed(something);
59}
60
62
64
68
69int16_t WaveSimulation1D_Real::geti16(size_t x) const {
70 if (x >= length) {
71 FASTLED_WARN("Out of range.");
72 return 0;
73 }
74 const int16_t *curr = (whichGrid == 0) ? grid1.data() : grid2.data();
75 return curr[x + 1];
76}
77
79 if (x >= length) {
80 FASTLED_WARN("Out of range.");
81 return 0;
82 }
83 const int16_t *prev = (whichGrid == 0) ? grid2.data() : grid1.data();
84 return prev[x + 1];
85}
86
87float WaveSimulation1D_Real::getf(size_t x) const {
88 if (x >= length) {
89 FASTLED_WARN("Out of range.");
90 return 0.0f;
91 }
92 // Retrieve value from the active grid (offset by 1 for boundary).
93 const int16_t *curr = (whichGrid == 0) ? grid1.data() : grid2.data();
94 return fixed_to_float(curr[x + 1]);
95}
96
97bool WaveSimulation1D_Real::has(size_t x) const { return (x < length); }
98
99void WaveSimulation1D_Real::set(size_t x, float value) {
100 if (x >= length) {
101 FASTLED_WARN("warning X value too high");
102 return;
103 }
104 int16_t *curr = (whichGrid == 0) ? grid1.data() : grid2.data();
105 curr[x + 1] = float_to_fixed(value);
106}
107
109 int16_t *curr = (whichGrid == 0) ? grid1.data() : grid2.data();
110 int16_t *next = (whichGrid == 0) ? grid2.data() : grid1.data();
111
112 // Update boundaries with a Neumann (zero-gradient) condition:
113 curr[0] = curr[1];
114 curr[length + 1] = curr[length];
115
116 // Compute dampening factor as an integer value: 2^(mDampenening)
117 int32_t dampening_factor = 1 << mDampenening;
118
119 int32_t mCourantSq32 = static_cast<int32_t>(mCourantSq);
120 // Iterate over each inner cell.
121 for (size_t i = 1; i < length + 1; i++) {
122 // Compute the 1D Laplacian:
123 // lap = curr[i+1] - 2 * curr[i] + curr[i-1]
124 int32_t lap =
125 (int32_t)curr[i + 1] - ((int32_t)curr[i] << 1) + curr[i - 1];
126
127 // Multiply the Laplacian by the simulation speed using Q15 arithmetic:
128 int32_t term = (mCourantSq32 * lap) >> 15;
129
130 // Compute the new value:
131 // f = -next[i] + 2 * curr[i] + term
132 int32_t f = -(int32_t)next[i] + ((int32_t)curr[i] << 1) + term;
133
134 // Apply damping:
135 f = f - (f / dampening_factor);
136
137 // Clamp the result to the Q15 range [-32768, 32767].
138 if (f > 32767)
139 f = 32767;
140 else if (f < -32768)
141 f = -32768;
142
143 next[i] = (int16_t)f;
144 }
145
146 if (mHalfDuplex) {
147 // Set the negative values to zero.
148 for (size_t i = 1; i < length + 1; i++) {
149 if (next[i] < 0) {
150 next[i] = 0;
151 }
152 }
153 }
154
155 // Toggle the active grid.
156 whichGrid ^= 1;
157}
158
160 float speed, float dampening)
161 : width(W), height(H), stride(W + 2),
162 grid1((W + 2) * (H + 2)),
163 grid2((W + 2) * (H + 2)), whichGrid(0),
164 // Initialize speed 0.16 in fixed Q15
166 // Dampening exponent; e.g., 6 means a factor of 2^6 = 64.
168
169void WaveSimulation2D_Real::setSpeed(float something) {
170 mCourantSq = float_to_fixed(something);
171}
172
174
176
180
181float WaveSimulation2D_Real::getf(size_t x, size_t y) const {
182 if (x >= width || y >= height) {
183 FASTLED_WARN("Out of range: " << x << ", " << y);
184 return 0.0f;
185 }
186 const int16_t *curr = (whichGrid == 0 ? grid1.data() : grid2.data());
187 return fixed_to_float(curr[(y + 1) * stride + (x + 1)]);
188}
189
190int16_t WaveSimulation2D_Real::geti16(size_t x, size_t y) const {
191 if (x >= width || y >= height) {
192 FASTLED_WARN("Out of range: " << x << ", " << y);
193 return 0;
194 }
195 const int16_t *curr = (whichGrid == 0 ? grid1.data() : grid2.data());
196 return curr[(y + 1) * stride + (x + 1)];
197}
198
199int16_t WaveSimulation2D_Real::geti16Previous(size_t x, size_t y) const {
200 if (x >= width || y >= height) {
201 FASTLED_WARN("Out of range: " << x << ", " << y);
202 return 0;
203 }
204 const int16_t *prev = (whichGrid == 0 ? grid2.data() : grid1.data());
205 return prev[(y + 1) * stride + (x + 1)];
206}
207
208bool WaveSimulation2D_Real::has(size_t x, size_t y) const {
209 return (x < width && y < height);
210}
211
212void WaveSimulation2D_Real::setf(size_t x, size_t y, float value) {
213 int16_t v = float_to_fixed(value);
214 return seti16(x, y, v);
215}
216
217void WaveSimulation2D_Real::seti16(size_t x, size_t y, int16_t value) {
218 if (x >= width || y >= height) {
219 FASTLED_WARN("Out of range: " << x << ", " << y);
220 return;
221 }
222 int16_t *curr = (whichGrid == 0 ? grid1.data() : grid2.data());
223 curr[(y + 1) * stride + (x + 1)] = value;
224}
225
227 int16_t *curr = (whichGrid == 0 ? grid1.data() : grid2.data());
228 int16_t *next = (whichGrid == 0 ? grid2.data() : grid1.data());
229
230 // Update horizontal boundaries.
231 for (size_t j = 0; j < height + 2; ++j) {
232 if (mXCylindrical) {
233 curr[j * stride + 0] = curr[j * stride + width];
234 curr[j * stride + (width + 1)] = curr[j * stride + 1];
235 } else {
236 curr[j * stride + 0] = curr[j * stride + 1];
237 curr[j * stride + (width + 1)] = curr[j * stride + width];
238 }
239 }
240
241 // Update vertical boundaries.
242 for (size_t i = 0; i < width + 2; ++i) {
243 curr[0 * stride + i] = curr[1 * stride + i];
244 curr[(height + 1) * stride + i] = curr[height * stride + i];
245 }
246
247 // Compute the dampening factor as an integer: 2^(dampening).
248 int32_t dampening_factor = 1 << mDampening; // e.g., 6 -> 64
249 int32_t mCourantSq32 = static_cast<int32_t>(mCourantSq);
250
251 // Update each inner cell.
252 for (size_t j = 1; j <= height; ++j) {
253 for (size_t i = 1; i <= width; ++i) {
254 int index = j * stride + i;
255 // Laplacian: sum of four neighbors minus 4 times the center.
256 int32_t laplacian = (int32_t)curr[index + 1] + curr[index - 1] +
257 curr[index + stride] + curr[index - stride] -
258 ((int32_t)curr[index] << 2);
259 // Compute the new value:
260 // f = - next[index] + 2 * curr[index] + mCourantSq * laplacian
261 // The multiplication is in Q15, so we shift right by 15.
262 int32_t term = (mCourantSq32 * laplacian) >> 15;
263 int32_t f =
264 -(int32_t)next[index] + ((int32_t)curr[index] << 1) + term;
265
266 // Apply damping:
267 f = f - (f / dampening_factor);
268
269 // Clamp f to the Q15 range.
270 if (f > 32767)
271 f = 32767;
272 else if (f < -32768)
273 f = -32768;
274
275 next[index] = (int16_t)f;
276 }
277 }
278
279 if (mHalfDuplex) {
280 // Set negative values to zero.
281 for (size_t j = 1; j <= height; ++j) {
282 for (size_t i = 1; i <= width; ++i) {
283 int index = j * stride + i;
284 if (next[index] < 0) {
285 next[index] = 0;
286 }
287 }
288 }
289 }
290
291 // Swap the roles of the grids.
292 whichGrid ^= 1;
293}
294
295} // namespace fl
uint32_t x[NUM_LAYERS]
Definition Fire2023.ino:82
uint32_t y[NUM_LAYERS]
Definition Fire2023.ino:83
UISlider dampening("Dampening", 6.0f, 0.0f, 10.0f, 0.1f)
void set(size_t x, float value)
int16_t geti16(size_t x) const
int16_t geti16Previous(size_t x) const
WaveSimulation1D_Real(uint32_t length, float speed=0.16f, int dampening=6)
int16_t geti16Previous(size_t x, size_t y) const
bool has(size_t x, size_t y) const
fl::vector< int16_t, fl::allocator_psram< int16_t > > grid2
void setf(size_t x, size_t y, float value)
int16_t geti16(size_t x, size_t y) const
void seti16(size_t x, size_t y, int16_t value)
WaveSimulation2D_Real(uint32_t W, uint32_t H, float speed=0.16f, float dampening=6.0f)
fl::vector< int16_t, fl::allocator_psram< int16_t > > grid1
float getf(size_t x, size_t y) const
uint16_t speed
Definition funky.cpp:82
Implements the FastLED namespace macros.
float fixed_to_float(int16_t f)
int16_t float_to_fixed(float f)
FASTLED_FORCE_INLINE T clamp(T value, T min, T max)
Definition clamp.h:10
Implements a simple red square effect for 2D LED grids.
Definition crgb.h:16
#define FASTLED_WARN
Definition warn.h:7
#define INT16_NEG
#define INT16_POS