FastLED 3.9.15
Loading...
Searching...
No Matches
wavefx.cpp
Go to the documentation of this file.
1
2/*
3This demo is best viewed using the FastLED compiler.
4
5Windows/MacOS binaries: https://github.com/FastLED/FastLED/releases
6
7Python
8
9Install: pip install fastled
10Run: fastled <this sketch directory>
11This will compile and preview the sketch in the browser, and enable
12all the UI elements you see below.
13
14OVERVIEW:
15This sketch demonstrates a 2D wave simulation with multiple layers and blending effects.
16It creates ripple effects that propagate across the LED matrix, similar to water waves.
17The demo includes two wave layers (upper and lower) with different colors and properties,
18which are blended together to create complex visual effects.
19*/
20
21#include <Arduino.h> // Core Arduino functionality
22#include <FastLED.h> // Main FastLED library for controlling LEDs
23
24#if SKETCH_HAS_LOTS_OF_MEMORY
25
26#include "fl/math_macros.h" // Math helper functions and macros
27#include "fl/time_alpha.h" // Time-based alpha/transition effects
28#include "fl/ui.h" // UI components for the FastLED web compiler
29#include "fx/2d/blend.h" // 2D blending effects between layers
30#include "fx/2d/wave.h" // 2D wave simulation
31
32#include "wavefx.h" // Header file for this sketch
33
34using namespace fl; // Use the FastLED namespace for convenience
35
36
37
38// Array to hold all LED color values - one CRGB struct per LED
40
41// UI elements that appear in the FastLED web compiler interface:
42UITitle title("FxWave2D Demo");
43UIDescription description("Advanced layered and blended wave effects.");
44
45UICheckbox xCyclical("X Is Cyclical", false); // If true, waves wrap around the x-axis (like a loop)
46// Main control UI elements:
47UIButton button("Trigger"); // Button to trigger a single ripple
48UIButton buttonFancy("Trigger Fancy"); // Button to trigger a fancy cross-shaped effect
49UICheckbox autoTrigger("Auto Trigger", true); // Enable/disable automatic ripple triggering
50UISlider triggerSpeed("Trigger Speed", .5f, 0.0f, 1.0f, 0.01f); // Controls how frequently auto-triggers happen (lower = faster)
51UICheckbox easeModeSqrt("Ease Mode Sqrt", false); // Changes how wave heights are calculated (sqrt gives more natural waves)
52UICheckbox useChangeGrid("Use Change Grid", false); // Enable performance optimization (reduces visual oscillation)
53UISlider blurAmount("Global Blur Amount", 0, 0, 172, 1); // Controls overall blur amount for all layers
54UISlider blurPasses("Global Blur Passes", 1, 1, 10, 1); // Controls how many times blur is applied (more = smoother but slower)
55UISlider superSample("SuperSampleExponent", 1.f, 0.f, 3.f, 1.f); // Controls anti-aliasing quality (higher = better quality but more CPU)
56
57
58
59// Upper wave layer controls:
60UISlider speedUpper("Wave Upper: Speed", 0.12f, 0.0f, 1.0f); // How fast the upper wave propagates
61UISlider dampeningUpper("Wave Upper: Dampening", 8.9f, 0.0f, 20.0f, 0.1f); // How quickly the upper wave loses energy
62UICheckbox halfDuplexUpper("Wave Upper: Half Duplex", true); // If true, waves only go positive (not negative)
63UISlider blurAmountUpper("Wave Upper: Blur Amount", 95, 0, 172, 1); // Blur amount for upper wave layer
64UISlider blurPassesUpper("Wave Upper: Blur Passes", 1, 1, 10, 1); // Blur passes for upper wave layer
65
66// Lower wave layer controls:
67UISlider speedLower("Wave Lower: Speed", 0.26f, 0.0f, 1.0f); // How fast the lower wave propagates
68UISlider dampeningLower("Wave Lower: Dampening", 9.0f, 0.0f, 20.0f, 0.1f); // How quickly the lower wave loses energy
69UICheckbox halfDuplexLower("Wave Lower: Half Duplex", true); // If true, waves only go positive (not negative)
70UISlider blurAmountLower("Wave Lower: Blur Amount", 0, 0, 172, 1); // Blur amount for lower wave layer
71UISlider blurPassesLower("Wave Lower: Blur Passes", 1, 1, 10, 1); // Blur passes for lower wave layer
72
73// Fancy effect controls (for the cross-shaped effect):
74UISlider fancySpeed("Fancy Speed", 796, 0, 1000, 1); // Speed of the fancy effect animation
75UISlider fancyIntensity("Fancy Intensity", 32, 1, 255, 1); // Intensity/height of the fancy effect waves
76UISlider fancyParticleSpan("Fancy Particle Span", 0.06f, 0.01f, 0.2f, 0.01f); // Width of the fancy effect lines
77
78// Help text explaining the Use Change Grid feature
79UIHelp changeGridHelp("Use Change Grid preserves the set point over multiple iterations to ensure more stable results across simulation resolutions. However, turning it off may result in more dramatic effects and saves memory.");
80
81// Color palettes define the gradient of colors used for the wave effects
82// Each entry has the format: position (0-255), R, G, B
83
84DEFINE_GRADIENT_PALETTE(electricBlueFirePal){
85 0, 0, 0, 0, // Black (lowest wave height)
86 32, 0, 0, 70, // Dark blue (low wave height)
87 128, 20, 57, 255, // Electric blue (medium wave height)
88 255, 255, 255, 255 // White (maximum wave height)
89};
90
91DEFINE_GRADIENT_PALETTE(electricGreenFirePal){
92 0, 0, 0, 0, // Black (lowest wave height)
93 8, 128, 64, 64, // Green with red tint (very low wave height)
94 16, 255, 222, 222, // Pinkish red (low wave height)
95 64, 255, 255, 255, // White (medium wave height)
96 255, 255, 255, 255 // White (maximum wave height)
97};
98
99// Create mappings between 1D array positions and 2D x,y coordinates
100XYMap xyMap(WIDTH, HEIGHT, IS_SERPINTINE); // For the actual LED output (may be serpentine)
101XYMap xyRect(WIDTH, HEIGHT, false); // For the wave simulation (always rectangular grid)
102
103// Create default configuration for the lower wave layer
105 WaveFx::Args out;
106 out.factor = SuperSample::SUPER_SAMPLE_2X; // 2x supersampling for smoother waves
107 out.half_duplex = true; // Only positive waves (no negative values)
108 out.auto_updates = true; // Automatically update the simulation each frame
109 out.speed = 0.18f; // Wave propagation speed
110 out.dampening = 9.0f; // How quickly waves lose energy
111 out.crgbMap = fl::make_shared<WaveCrgbGradientMap>(electricBlueFirePal); // Color palette for this wave
112 return out;
113}
114
115// Create default configuration for the upper wave layer
117 WaveFx::Args out;
118 out.factor = SuperSample::SUPER_SAMPLE_2X; // 2x supersampling for smoother waves
119 out.half_duplex = true; // Only positive waves (no negative values)
120 out.auto_updates = true; // Automatically update the simulation each frame
121 out.speed = 0.25f; // Wave propagation speed (faster than lower)
122 out.dampening = 3.0f; // How quickly waves lose energy (less than lower)
123 out.crgbMap = fl::make_shared<WaveCrgbGradientMap>(electricGreenFirePal); // Color palette for this wave
124 return out;
125}
126
127// Create the two wave simulation layers with their default configurations
128WaveFx waveFxLower(xyRect, CreateArgsLower()); // Lower/background wave layer (blue)
129WaveFx waveFxUpper(xyRect, CreateArgsUpper()); // Upper/foreground wave layer (green/red)
130
131// Create a blender that will combine the two wave layers
132Blend2d fxBlend(xyMap);
133
134
135// Convert the UI slider value to the appropriate SuperSample enum value
136// SuperSample controls the quality of the wave simulation (higher = better quality but more CPU)
138 switch (int(superSample)) {
139 case 0:
140 return SuperSample::SUPER_SAMPLE_NONE; // No supersampling (fastest, lowest quality)
141 case 1:
142 return SuperSample::SUPER_SAMPLE_2X; // 2x supersampling (2x2 grid = 4 samples per pixel)
143 case 2:
144 return SuperSample::SUPER_SAMPLE_4X; // 4x supersampling (4x4 grid = 16 samples per pixel)
145 case 3:
146 return SuperSample::SUPER_SAMPLE_8X; // 8x supersampling (8x8 grid = 64 samples per pixel, slowest)
147 default:
148 return SuperSample::SUPER_SAMPLE_NONE; // Default fallback
149 }
150}
151
152// Create a ripple effect at a random position within the central area of the display
153void triggerRipple() {
154 // Define a margin percentage to keep ripples away from the edges
155 float perc = .15f;
156
157 // Calculate the boundaries for the ripple (15% from each edge)
158 uint8_t min_x = perc * WIDTH; // Left boundary
159 uint8_t max_x = (1 - perc) * WIDTH; // Right boundary
160 uint8_t min_y = perc * HEIGHT; // Top boundary
161 uint8_t max_y = (1 - perc) * HEIGHT; // Bottom boundary
162
163 // Generate a random position within these boundaries
164 int x = random(min_x, max_x);
165 int y = random(min_y, max_y);
166
167 // Set a wave peak at this position in both wave layers
168 // The value 1.0 represents the maximum height of the wave
169 waveFxLower.setf(x, y, 1); // Create ripple in lower layer
170 waveFxUpper.setf(x, y, 1); // Create ripple in upper layer
171}
172
173// Create a fancy cross-shaped effect that expands from the center
174void applyFancyEffect(uint32_t now, bool button_active) {
175 // Calculate the total animation duration based on the speed slider
176 // Higher fancySpeed value = shorter duration (faster animation)
177 uint32_t total =
178 map(fancySpeed.as<uint32_t>(), 0, fancySpeed.getMax(), 1000, 100);
179
180 // Create a static TimeRamp to manage the animation timing
181 // TimeRamp handles the transition from start to end over time
182 static TimeRamp pointTransition = TimeRamp(total, 0, 0);
183
184 // If the button is active, start/restart the animation
185 if (button_active) {
186 pointTransition.trigger(now, total, 0, 0);
187 }
188
189 // If the animation isn't currently active, exit early
190 if (!pointTransition.isActive(now)) {
191 // no need to draw
192 return;
193 }
194
195 // Find the center of the display
196 int mid_x = WIDTH / 2;
197 int mid_y = HEIGHT / 2;
198
199 // Calculate the maximum distance from center (half the width)
200 int amount = WIDTH / 2;
201
202 // Calculate the start and end coordinates for the cross
203 int start_x = mid_x - amount; // Leftmost point
204 int end_x = mid_x + amount; // Rightmost point
205 int start_y = mid_y - amount; // Topmost point
206 int end_y = mid_y + amount; // Bottommost point
207
208 // Get the current animation progress (0-255)
209 int curr_alpha = pointTransition.update8(now);
210
211 // Map the animation progress to the four points of the expanding cross
212 // As curr_alpha increases from 0 to 255, these points move from center to edges
213 int left_x = map(curr_alpha, 0, 255, mid_x, start_x); // Center to left
214 int down_y = map(curr_alpha, 0, 255, mid_y, start_y); // Center to top
215 int right_x = map(curr_alpha, 0, 255, mid_x, end_x); // Center to right
216 int up_y = map(curr_alpha, 0, 255, mid_y, end_y); // Center to bottom
217
218 // Convert the 0-255 alpha to 0.0-1.0 range
219 float curr_alpha_f = curr_alpha / 255.0f;
220
221 // Calculate the wave height value - starts high and decreases as animation progresses
222 // This makes the waves stronger at the beginning of the animation
223 float valuef = (1.0f - curr_alpha_f) * fancyIntensity.value() / 255.0f;
224
225 // Calculate the width of the cross lines
226 int span = fancyParticleSpan.value() * WIDTH;
227
228 // Add wave energy along the four expanding lines of the cross
229 // Each line is a horizontal or vertical span of pixels
230
231 // Left-moving horizontal line
232 for (int x = left_x - span; x < left_x + span; x++) {
233 waveFxLower.addf(x, mid_y, valuef); // Add to lower layer
234 waveFxUpper.addf(x, mid_y, valuef); // Add to upper layer
235 }
236
237 // Right-moving horizontal line
238 for (int x = right_x - span; x < right_x + span; x++) {
239 waveFxLower.addf(x, mid_y, valuef);
240 waveFxUpper.addf(x, mid_y, valuef);
241 }
242
243 // Downward-moving vertical line
244 for (int y = down_y - span; y < down_y + span; y++) {
245 waveFxLower.addf(mid_x, y, valuef);
246 waveFxUpper.addf(mid_x, y, valuef);
247 }
248
249 // Upward-moving vertical line
250 for (int y = up_y - span; y < up_y + span; y++) {
251 waveFxLower.addf(mid_x, y, valuef);
252 waveFxUpper.addf(mid_x, y, valuef);
253 }
254}
255
256// Structure to hold the state of UI buttons
257struct ui_state {
258 bool button = false; // Regular ripple button state
259 bool bigButton = false; // Fancy effect button state
260};
261
262// Apply all UI settings to the wave effects and return button states
263ui_state ui() {
264 // Set the easing function based on the checkbox
265 // Easing controls how wave heights are calculated:
266 // - LINEAR: Simple linear mapping (sharper waves)
267 // - SQRT: Square root mapping (more natural, rounded waves)
268 U8EasingFunction easeMode = easeModeSqrt
271
272 // Apply all settings from UI controls to the lower wave layer
273 waveFxLower.setSpeed(speedLower); // Wave propagation speed
274 waveFxLower.setDampening(dampeningLower); // How quickly waves lose energy
275 waveFxLower.setHalfDuplex(halfDuplexLower); // Whether waves can go negative
276 waveFxLower.setSuperSample(getSuperSample()); // Anti-aliasing quality
277 waveFxLower.setEasingMode(easeMode); // Wave height calculation method
278 waveFxLower.setUseChangeGrid(useChangeGrid); // Performance optimization vs visual quality
279
280 // Apply all settings from UI controls to the upper wave layer
281 waveFxUpper.setSpeed(speedUpper); // Wave propagation speed
282 waveFxUpper.setDampening(dampeningUpper); // How quickly waves lose energy
283 waveFxUpper.setHalfDuplex(halfDuplexUpper); // Whether waves can go negative
284 waveFxUpper.setSuperSample(getSuperSample()); // Anti-aliasing quality
285 waveFxUpper.setEasingMode(easeMode); // Wave height calculation method
286 waveFxUpper.setUseChangeGrid(useChangeGrid); // Performance optimization vs visual quality
287
288 // Apply global blur settings to the blender
289 fxBlend.setGlobalBlurAmount(blurAmount); // Overall blur strength
290 fxBlend.setGlobalBlurPasses(blurPasses); // Number of blur passes
291
292 // Create parameter structures for each wave layer's blur settings
293 fl::Blend2dParams lower_params;
294 lower_params.blur_amount = blurAmountLower; // Blur amount for lower layer
295 lower_params.blur_passes = blurPassesLower; // Blur passes for lower layer
296
297 fl::Blend2dParams upper_params;
298 upper_params.blur_amount = blurAmountUpper; // Blur amount for upper layer
299 upper_params.blur_passes = blurPassesUpper; // Blur passes for upper layer
300
301 // Apply the layer-specific blur parameters
302 fxBlend.setParams(waveFxLower, lower_params);
303 fxBlend.setParams(waveFxUpper, upper_params);
304
305 // Return the current state of the UI buttons
306 ui_state state;
307 state.button = button; // Regular ripple button
308 state.bigButton = buttonFancy; // Fancy effect button
309 return state;
310}
311
312// Handle automatic triggering of ripples at random intervals
313void processAutoTrigger(uint32_t now) {
314 // Static variable to remember when the next auto-trigger should happen
315 static uint32_t nextTrigger = 0;
316
317 // Calculate time until next trigger
318 uint32_t trigger_delta = nextTrigger - now;
319
320 // Handle timer overflow (happens after ~49 days of continuous running)
321 if (trigger_delta > 10000) {
322 // If the delta is suspiciously large, we probably rolled over
323 trigger_delta = 0;
324 }
325
326 // Only proceed if auto-trigger is enabled
327 if (autoTrigger) {
328 // Check if it's time for the next trigger
329 if (now >= nextTrigger) {
330 // Create a ripple
332
333 // Calculate the next trigger time based on the speed slider
334 // Invert the speed value so higher slider = faster triggers
335 float speed = 1.0f - triggerSpeed.value();
336
337 // Calculate min and max random intervals
338 // Higher speed = shorter intervals between triggers
339 uint32_t min_rand = 400 * speed; // Minimum interval (milliseconds)
340 uint32_t max_rand = 2000 * speed; // Maximum interval (milliseconds)
341
342 // Ensure min is actually less than max (handles edge cases)
343 uint32_t min = MIN(min_rand, max_rand);
344 uint32_t max = MAX(min_rand, max_rand);
345
346 // Ensure min and max aren't equal (would cause random() to crash)
347 if (min == max) {
348 max += 1;
349 }
350
351 // Schedule the next trigger at a random time in the future
352 nextTrigger = now + random(min, max);
353 }
354 }
355}
356
357void wavefx_setup() {
358 // Create a screen map for visualization in the FastLED web compiler
359 auto screenmap = xyMap.toScreenMap();
360 screenmap.setDiameter(.2); // Set the size of the LEDs in the visualization
361
362 // Initialize the LED strip:
363 // - NEOPIXEL is the LED type
364 // - 2 is the data pin number (for real hardware)
365 // - setScreenMap connects our 2D coordinate system to the 1D LED array
366 FastLED.addLeds<NEOPIXEL, 2>(leds, NUM_LEDS).setScreenMap(screenmap);
367
368 // Set up UI groupings
369 // Main Controls
370 title.setGroup("Main Controls");
371 description.setGroup("Main Controls");
372 button.setGroup("Main Controls");
373 buttonFancy.setGroup("Main Controls");
374 autoTrigger.setGroup("Main Controls");
375 triggerSpeed.setGroup("Main Controls");
376
377 // Global Settings
378 xCyclical.setGroup("Global Settings");
379 easeModeSqrt.setGroup("Global Settings");
380 useChangeGrid.setGroup("Global Settings");
381 blurAmount.setGroup("Global Settings");
382 blurPasses.setGroup("Global Settings");
383 superSample.setGroup("Global Settings");
384
385 // Upper Wave Layer
386 speedUpper.setGroup("Upper Wave Layer");
387 dampeningUpper.setGroup("Upper Wave Layer");
388 halfDuplexUpper.setGroup("Upper Wave Layer");
389 blurAmountUpper.setGroup("Upper Wave Layer");
390 blurPassesUpper.setGroup("Upper Wave Layer");
391
392 // Lower Wave Layer
393 speedLower.setGroup("Lower Wave Layer");
394 dampeningLower.setGroup("Lower Wave Layer");
395 halfDuplexLower.setGroup("Lower Wave Layer");
396 blurAmountLower.setGroup("Lower Wave Layer");
397 blurPassesLower.setGroup("Lower Wave Layer");
398
399 // Fancy Effects
400 fancySpeed.setGroup("Fancy Effects");
401 fancyIntensity.setGroup("Fancy Effects");
402 fancyParticleSpan.setGroup("Fancy Effects");
403
404 // Add both wave layers to the blender
405 // The order matters - lower layer is added first (background)
406 fxBlend.add(waveFxLower);
407 fxBlend.add(waveFxUpper);
408}
409
410
411void wavefx_loop() {
412 // The main program loop that runs continuously
413
414 // Get the current time in milliseconds
415 uint32_t now = millis();
416
417 // set the x cyclical
418 waveFxLower.setXCylindrical(xCyclical.value()); // Set whether lower wave wraps around x-axis
419
420 // Apply all UI settings and get button states
421 ui_state state = ui();
422
423 // Check if the regular ripple button was pressed
424 if (state.button) {
425 triggerRipple(); // Create a single ripple
426 }
427
428 // Apply the fancy cross effect if its button is pressed
429 applyFancyEffect(now, state.bigButton);
430
431 // Handle automatic triggering of ripples
432 processAutoTrigger(now);
433
434 // Create a drawing context with the current time and LED array
435 Fx::DrawContext ctx(now, leds);
436
437 // Draw the blended result of both wave layers to the LED array
438 fxBlend.draw(ctx);
439
440 // Send the color data to the actual LEDs
441 FastLED.show();
442}
443
444
445#endif // SKETCH_HAS_LOTS_OF_MEMORY
CRGB leds[NUM_LEDS]
#define NUM_LEDS
int y
Definition simple.h:93
int x
Definition simple.h:92
#define IS_SERPINTINE
Definition simple.h:50
fl::XYMap xyMap
Definition ColorBoost.h:57
FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING_GLOBAL_CONSTRUCTORS CFastLED FastLED
Global LED strip management instance.
Definition FastLED.cpp:74
central include file for FastLED, defines the CFastLED class/object
uint16_t speed
Definition Noise.ino:63
UICheckbox autoTrigger("Auto Trigger", true)
UICheckbox xCyclical("X Is Cyclical", true)
#define WIDTH
Definition advanced.h:34
UITitle title("Audio Reactive Visualizations")
UIDescription description("Real-time audio visualizations with beat detection and multiple modes")
#define HEIGHT
Definition advanced.h:35
LED controller for WS2812 LEDs with GRB color order.
Definition FastLED.h:158
_DrawContext DrawContext
Definition fx.h:21
void trigger(u32 now) override
Call this when you want to (re)start the ramp cycle.
u8 update8(u32 now) override
Compute current 0–255 output based on how much time has elapsed since trigger().
bool isActive(u32 now) const override
WaveFxArgs Args
Definition wave.h:84
#define DEFINE_GRADIENT_PALETTE(X)
Defines a static RGB palette very compactly using a series of connected color gradients.
Definition colorutils.h:121
WaveFx::Args CreateArgsLower()
Definition wave.cpp:20
WaveFx::Args CreateArgsUpper()
Definition wave.cpp:31
void triggerRipple(WaveSimulation1D &waveSim, int x)
Definition Wave.h:49
UISlider superSample("SuperSampleExponent", 0.f, 0.f, 3.f, 1.f)
SuperSample getSuperSample()
Definition Wave.h:60
#define MIN(a, b)
Definition math_macros.h:49
#define MAX(a, b)
Definition math_macros.h:45
U8EasingFunction
@ WAVE_U8_MODE_LINEAR
@ WAVE_U8_MODE_SQRT
Slice< T > span
Definition span.h:8
shared_ptr< T > make_shared(Args &&... args)
Definition shared_ptr.h:348
SuperSample
Definition supersample.h:4
@ SUPER_SAMPLE_8X
Definition supersample.h:8
@ SUPER_SAMPLE_2X
Definition supersample.h:6
@ SUPER_SAMPLE_4X
Definition supersample.h:7
@ SUPER_SAMPLE_NONE
Definition supersample.h:5
IMPORTANT!
Definition crgb.h:20
uint8_t blur_passes
Definition blend.h:24
uint8_t blur_amount
Definition blend.h:23
UIButton button("Button")
Representation of an RGB pixel (Red, Green, Blue)
Definition crgb.h:86
float speed
Definition wave.h:74
WaveCrgbMapPtr crgbMap
Definition wave.h:78
bool auto_updates
Definition wave.h:73
SuperSample factor
Definition wave.h:71
float dampening
Definition wave.h:75
bool half_duplex
Definition wave.h:72
void wavefx_loop()
void wavefx_setup()