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(new int16_t[length + 2]()), // Allocate and zero-initialize with
51 // length+2 elements.
52 grid2(new int16_t[length + 2]()), 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.get() : grid2.get();
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.get() : grid1.get();
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.get() : grid2.get();
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.get() : grid2.get();
105 curr[x + 1] = float_to_fixed(value);
106}
107
109 int16_t *curr = (whichGrid == 0) ? grid1.get() : grid2.get();
110 int16_t *next = (whichGrid == 0) ? grid2.get() : grid1.get();
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(new int16_t[(W + 2) * (H + 2)]()),
163 grid2(new int16_t[(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.get() : grid2.get());
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.get() : grid2.get());
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.get() : grid1.get());
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.get() : grid2.get());
223 curr[(y + 1) * stride + (x + 1)] = value;
224}
225
227 int16_t *curr = (whichGrid == 0 ? grid1.get() : grid2.get());
228 int16_t *next = (whichGrid == 0 ? grid2.get() : grid1.get());
229
230 // Update horizontal boundaries.
231 for (size_t j = 0; j < height + 2; ++j) {
232 curr[j * stride + 0] = curr[j * stride + 1];
233 curr[j * stride + (width + 1)] = curr[j * stride + width];
234 }
235
236 // Update vertical boundaries.
237 for (size_t i = 0; i < width + 2; ++i) {
238 curr[0 * stride + i] = curr[1 * stride + i];
239 curr[(height + 1) * stride + i] = curr[height * stride + i];
240 }
241
242 // Compute the dampening factor as an integer: 2^(dampening).
243 int32_t dampening_factor = 1 << mDampening; // e.g., 6 -> 64
244 int32_t mCourantSq32 = static_cast<int32_t>(mCourantSq);
245
246 // Update each inner cell.
247 for (size_t j = 1; j <= height; ++j) {
248 for (size_t i = 1; i <= width; ++i) {
249 int index = j * stride + i;
250 // Laplacian: sum of four neighbors minus 4 times the center.
251 int32_t laplacian = (int32_t)curr[index + 1] + curr[index - 1] +
252 curr[index + stride] + curr[index - stride] -
253 ((int32_t)curr[index] << 2);
254 // Compute the new value:
255 // f = - next[index] + 2 * curr[index] + mCourantSq * laplacian
256 // The multiplication is in Q15, so we shift right by 15.
257 int32_t term = (mCourantSq32 * laplacian) >> 15;
258 int32_t f =
259 -(int32_t)next[index] + ((int32_t)curr[index] << 1) + term;
260
261 // Apply damping:
262 f = f - (f / dampening_factor);
263
264 // Clamp f to the Q15 range.
265 if (f > 32767)
266 f = 32767;
267 else if (f < -32768)
268 f = -32768;
269
270 next[index] = (int16_t)f;
271 }
272 }
273
274 if (mHalfDuplex) {
275 // Set negative values to zero.
276 for (size_t j = 1; j <= height; ++j) {
277 for (size_t i = 1; i <= width; ++i) {
278 int index = j * stride + i;
279 if (next[index] < 0) {
280 next[index] = 0;
281 }
282 }
283 }
284 }
285
286 // Swap the roles of the grids.
287 whichGrid ^= 1;
288}
289
290} // namespace fl
int y
Definition Audio.ino:72
int x
Definition Audio.ino:71
UISlider speed("Speed", 1.0f, -20.0f, 20.0f, 0.01f)
UISlider dampening("Dampening", 6.0f, 0.0f, 10.0f, 0.1f)
void set(size_t x, float value)
fl::scoped_array< int16_t > grid2
int16_t geti16(size_t x) const
int16_t geti16Previous(size_t x) const
fl::scoped_array< int16_t > grid1
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::scoped_array< int16_t > grid1
void setf(size_t x, size_t y, float value)
int16_t geti16(size_t x, size_t y) const
fl::scoped_array< int16_t > grid2
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)
float getf(size_t x, size_t y) const
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