FastLED 3.9.15
Loading...
Searching...
No Matches
wave_simulation.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 {
10
11uint8_t half_duplex_blend_sqrt_q15(uint16_t x) {
12 x = MIN(x, 32767); // Q15
13 const int Q = 15;
14 uint32_t X = (uint32_t)x << Q; // promote to Q30
15 uint32_t y = (1u << Q); // start at “1.0” in Q15
16
17 // 3–4 iterations is plenty for 15‑bit precision:
18 for (int i = 0; i < 4; i++) {
19 y = (y + (X / y)) >> 1;
20 }
21 return static_cast<int16_t>(y) >> 8;
22}
23
24uint8_t half_duplex_blend_linear(uint16_t x) {
25 x = MIN(x, 32767); // Q15
26 x *= 2;
27 return x >> 8;
28}
29
30} // namespace
31
32namespace fl {
33
34void WaveSimulation2D::setSpeed(float speed) { mSim->setSpeed(speed); }
35
36WaveSimulation2D::WaveSimulation2D(uint32_t W, uint32_t H, SuperSample factor,
37 float speed, float dampening) {
38 init(W, H, factor, speed, dampening);
39}
40
41void WaveSimulation2D::init(uint32_t width, uint32_t height, SuperSample factor,
42 float speed, int dampening) {
43 mOuterWidth = width;
44 mOuterHeight = height;
45 mMultiplier = static_cast<uint32_t>(factor);
46 mSim.reset(); // clear out memory first.
47 uint32_t w = width * mMultiplier;
48 uint32_t h = height * mMultiplier;
49 mSim.reset(new WaveSimulation2D_Real(w, h, speed, dampening));
50 mChangeGrid.reset(w, h);
51 // Extra frames are needed because the simulation slows down in
52 // proportion to the supersampling factor.
53 mExtraFrames = uint8_t(factor) - 1;
54}
55
56void WaveSimulation2D::setDampening(int damp) { mSim->setDampening(damp); }
57
58int WaveSimulation2D::getDampenening() const { return mSim->getDampenening(); }
59
60float WaveSimulation2D::getSpeed() const { return mSim->getSpeed(); }
61
62float WaveSimulation2D::getf(size_t x, size_t y) const {
63 if (!has(x, y))
64 return 0.0f;
65 float sum = 0.0f;
66 for (uint32_t j = 0; j < mMultiplier; ++j) {
67 for (uint32_t i = 0; i < mMultiplier; ++i) {
68 sum += mSim->getf(x * mMultiplier + i, y * mMultiplier + j);
69 }
70 }
71 return sum / static_cast<float>(mMultiplier * mMultiplier);
72}
73
74int16_t WaveSimulation2D::geti16(size_t x, size_t y) const {
75 if (!has(x, y))
76 return 0;
77 int32_t sum = 0;
78 for (uint32_t j = 0; j < mMultiplier; ++j) {
79 for (uint32_t i = 0; i < mMultiplier; ++i) {
80 uint32_t xx = x * mMultiplier + i;
81 uint32_t yy = y * mMultiplier + j;
82 int32_t pt = mSim->geti16(xx, yy);
83 // int32_t ch_pt = mChangeGrid[(yy * mMultiplier) + xx];
84 int32_t ch_pt = mChangeGrid(xx, yy);
85 if (ch_pt != 0) { // we got a hit.
86 sum += ch_pt;
87 } else {
88 sum += pt;
89 }
90 }
91 }
92 int16_t out = static_cast<int16_t>(sum / (mMultiplier * mMultiplier));
93 return out;
94}
95
96int16_t WaveSimulation2D::geti16Previous(size_t x, size_t y) const {
97 if (!has(x, y))
98 return 0;
99 int32_t sum = 0;
100 for (uint32_t j = 0; j < mMultiplier; ++j) {
101 for (uint32_t i = 0; i < mMultiplier; ++i) {
102 sum +=
103 mSim->geti16Previous(x * mMultiplier + i, y * mMultiplier + j);
104 }
105 }
106 int16_t out = static_cast<int16_t>(sum / (mMultiplier * mMultiplier));
107 return out;
108}
109
110bool WaveSimulation2D::geti16All(size_t x, size_t y, int16_t *curr,
111 int16_t *prev, int16_t *diff) const {
112 if (!has(x, y))
113 return false;
114 *curr = geti16(x, y);
115 *prev = geti16Previous(x, y);
116 *diff = *curr - *prev;
117 return true;
118}
119
120int8_t WaveSimulation2D::geti8(size_t x, size_t y) const {
121 return static_cast<int8_t>(geti16(x, y) >> 8);
122}
123
124uint8_t WaveSimulation2D::getu8(size_t x, size_t y) const {
125 int16_t value = geti16(x, y);
126 if (mSim->getHalfDuplex()) {
127 uint16_t v2 = static_cast<uint16_t>(value);
128 switch (mU8Mode) {
130 return half_duplex_blend_linear(v2);
132 return half_duplex_blend_sqrt_q15(v2);
133 }
134 }
135 return static_cast<uint8_t>(((static_cast<uint16_t>(value) + 32768)) >> 8);
136}
137
138bool WaveSimulation2D::has(size_t x, size_t y) const {
139 return (x < mOuterWidth) && (y < mOuterHeight);
140}
141
142void WaveSimulation2D::seti16(size_t x, size_t y, int16_t v16) {
143 if (!has(x, y))
144 return;
145
146 // radius in pixels of your diamond
147 int rad = static_cast<int>(mMultiplier) / 2;
148
149 for (size_t j = 0; j < mMultiplier; ++j) {
150 for (size_t i = 0; i < mMultiplier; ++i) {
151 // compute offset from the center of this block
152 int dx = static_cast<int>(i) - rad;
153 int dy = static_cast<int>(j) - rad;
154 // keep only those points whose Manhattan distance ≤ rad
155 if (ABS(dx) + ABS(dy) > rad) {
156 continue;
157 }
158 size_t xx = x * mMultiplier + i;
159 size_t yy = y * mMultiplier + j;
160 if (mSim->has(xx, yy)) {
161 int16_t &pt = mChangeGrid.at(xx, yy);
162 if (pt == 0) {
163 // not set yet so set unconditionally.
164 pt = v16;
165 } else {
166 const bool sign_matches = (pt >= 0) == (v16 >= 0);
167 if (!sign_matches) {
168 // opposite signs, so overwrite
169 pt = v16;
170 } else {
171 // if the magnitude of the new pt is greater than what
172 // was already there, then overwrite.
173 uint16_t abs_pt = static_cast<uint16_t>(ABS(pt));
174 uint16_t abs_v16 = static_cast<uint16_t>(ABS(v16));
175 if (abs_v16 > abs_pt) {
176 pt = v16;
177 }
178 }
179 }
180 }
181 }
182 }
183}
184
185void WaveSimulation2D::setf(size_t x, size_t y, float value) {
186 if (!has(x, y))
187 return;
188
189 value = fl::clamp(value, 0.0f, 1.0f);
190 int16_t v16 = wave_detail::float_to_fixed(value);
191 seti16(x, y, v16);
192}
193
195 const vec2<int16_t> min_max = mChangeGrid.minMax();
196 const bool has_updates = min_max != vec2<int16_t>(0, 0);
197 for (uint8_t i = 0; i < mExtraFrames + 1; ++i) {
198 if (has_updates) {
199 // apply them
200 const uint32_t w = mChangeGrid.width();
201 const uint32_t h = mChangeGrid.height();
202 for (uint32_t x = 0; x < w; ++x) {
203 for (uint32_t y = 0; y < h; ++y) {
204 int16_t v16 = mChangeGrid(x, y);
205 if (v16 != 0) {
206 mSim->seti16(x, y, v16);
207 }
208 }
209 }
210 }
211 mSim->update();
212 }
213 // zero out mChangeGrid
214 mChangeGrid.clear();
215}
216
217uint32_t WaveSimulation2D::getWidth() const { return mOuterWidth; }
218uint32_t WaveSimulation2D::getHeight() const { return mOuterHeight; }
219
220void WaveSimulation2D::setExtraFrames(uint8_t extra) { mExtraFrames = extra; }
221
223 float speed, int dampening) {
224 init(length, factor, speed, dampening);
225}
226
227void WaveSimulation1D::init(uint32_t length, SuperSample factor, float speed,
228 int dampening) {
230 mMultiplier = static_cast<uint32_t>(factor);
231 mSim.reset(); // clear out memory first.
232 mSim.reset(
234 // Extra updates (frames) are applied because the simulation slows down in
235 // proportion to the supersampling factor.
236 mExtraFrames = static_cast<uint8_t>(factor) - 1;
237}
238
239void WaveSimulation1D::setSpeed(float speed) { mSim->setSpeed(speed); }
240
241void WaveSimulation1D::setDampening(int damp) { mSim->setDampening(damp); }
242
243int WaveSimulation1D::getDampenening() const { return mSim->getDampenening(); }
244
245void WaveSimulation1D::setExtraFrames(uint8_t extra) { mExtraFrames = extra; }
246
247float WaveSimulation1D::getSpeed() const { return mSim->getSpeed(); }
248
249float WaveSimulation1D::getf(size_t x) const {
250 if (!has(x))
251 return 0.0f;
252 float sum = 0.0f;
253 for (uint32_t i = 0; i < mMultiplier; ++i) {
254 sum += mSim->getf(x * mMultiplier + i);
255 }
256 return sum / static_cast<float>(mMultiplier);
257}
258
259int16_t WaveSimulation1D::geti16(size_t x) const {
260 if (!has(x))
261 return 0;
262 int32_t sum = 0;
263 for (uint32_t i = 0; i < mMultiplier; ++i) {
264 sum += mSim->geti16(x * mMultiplier + i);
265 }
266 return static_cast<int16_t>(sum / mMultiplier);
267}
268
269int16_t WaveSimulation1D::geti16Previous(size_t x) const {
270 if (!has(x))
271 return 0;
272 int32_t sum = 0;
273 for (uint32_t i = 0; i < mMultiplier; ++i) {
274 sum += mSim->geti16Previous(x * mMultiplier + i);
275 }
276 return static_cast<int16_t>(sum / mMultiplier);
277}
278
279bool WaveSimulation1D::geti16All(size_t x, int16_t *curr, int16_t *prev,
280 int16_t *diff) const {
281 if (!has(x))
282 return false;
283 *curr = geti16(x);
284 *prev = geti16Previous(x);
285 *diff = *curr - *prev;
286 return true;
287}
288
289int8_t WaveSimulation1D::geti8(size_t x) const {
290 return static_cast<int8_t>(geti16(x) >> 8);
291}
292
293// uint8_t WaveSimulation2D::getu8(size_t x, size_t y) const {
294// int16_t value = geti16(x, y);
295// if (mSim->getHalfDuplex()) {
296// uint16_t v2 = static_cast<uint16_t>(value);
297// switch (mU8Mode) {
298// case WAVE_U8_MODE_LINEAR:
299// return half_duplex_blend_linear(v2);
300// case WAVE_U8_MODE_SQRT:
301// return half_duplex_blend_sqrt_q15(v2);
302// }
303// }
304// return static_cast<uint8_t>(((static_cast<uint16_t>(value) + 32768)) >>
305// 8);
306// }
307
308uint8_t WaveSimulation1D::getu8(size_t x) const {
309 int16_t value = geti16(x);
310 if (mSim->getHalfDuplex()) {
311 uint16_t v2 = static_cast<uint16_t>(value);
312 switch (mU8Mode) {
314 return half_duplex_blend_linear(v2);
316 return half_duplex_blend_sqrt_q15(v2);
317 }
318 }
319 return static_cast<uint8_t>(((static_cast<uint16_t>(value) + 32768)) >> 8);
320}
321
322bool WaveSimulation1D::has(size_t x) const { return (x < mOuterLength); }
323
324void WaveSimulation1D::setf(size_t x, float value) {
325 if (!has(x))
326 return;
327 value = fl::clamp(value, -1.0f, 1.0f);
328 for (uint32_t i = 0; i < mMultiplier; ++i) {
329 mSim->set(x * mMultiplier + i, value);
330 }
331}
332
334 mSim->update();
335 for (uint8_t i = 0; i < mExtraFrames; ++i) {
336 mSim->update();
337 }
338}
339
340uint32_t WaveSimulation1D::getLength() const { return mOuterLength; }
341
342} // 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 setf(size_t x, float value)
fl::scoped_ptr< WaveSimulation1D_Real > mSim
void init(uint32_t length, SuperSample factor, float speed, int dampening)
WaveSimulation1D(uint32_t length, SuperSample factor=SuperSample::SUPER_SAMPLE_NONE, float speed=0.16f, int dampening=6)
int8_t geti8(size_t x) const
int16_t geti16(size_t x) const
bool has(size_t x) const
float getf(size_t x) const
void setSpeed(float speed)
U8EasingFunction mU8Mode
void setDampening(int damp)
void setExtraFrames(uint8_t extra)
uint8_t getu8(size_t x) const
int16_t geti16Previous(size_t x) const
bool geti16All(size_t x, int16_t *curr, int16_t *prev, int16_t *diff) const
uint32_t getLength() const
int16_t geti16(size_t x, size_t y) const
uint32_t getWidth() const
void init(uint32_t width, uint32_t height, SuperSample factor, float speed, int dampening)
void setExtraFrames(uint8_t extra)
uint32_t getHeight() const
int8_t geti8(size_t x, size_t y) const
fl::Grid< int16_t > mChangeGrid
fl::scoped_ptr< WaveSimulation2D_Real > mSim
void setSpeed(float speed)
bool geti16All(size_t x, size_t y, int16_t *curr, int16_t *prev, int16_t *diff) const
float getf(size_t x, size_t y) const
uint8_t getu8(size_t x, size_t y) const
void seti16(size_t x, size_t y, int16_t value)
void setf(size_t x, size_t y, float value)
U8EasingFunction mU8Mode
bool has(size_t x, size_t y) const
void setDampening(int damp)
WaveSimulation2D(uint32_t W, uint32_t H, SuperSample factor=SuperSample::SUPER_SAMPLE_NONE, float speed=0.16f, float dampening=6.0f)
int16_t geti16Previous(size_t x, size_t y) const
UISlider length("Length", 1.0f, 0.0f, 1.0f, 0.01f)
#define MIN(a, b)
Definition math_macros.h:15
#define ABS(x)
Definition math_macros.h:19
Implements the FastLED namespace macros.
int16_t float_to_fixed(float f)
@ WAVE_U8_MODE_LINEAR
@ WAVE_U8_MODE_SQRT
FASTLED_FORCE_INLINE T clamp(T value, T min, T max)
Definition clamp.h:10
SuperSample
Definition supersample.h:4
Implements a simple red square effect for 2D LED grids.
Definition crgb.h:16