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