FastLED 3.9.15
Loading...
Searching...
No Matches
curr.h
Go to the documentation of this file.
1/*
2Festival Stick - fl::Corkscrew LED Mapping Demo
3
4This example demonstrates proper corkscrew LED mapping for a festival stick
5(19+ turns, 288 LEDs) using the new fl::Corkscrew fl::ScreenMap functionality.
6
7Key Features:
8- Uses fl::Corkscrew.toScreenMap() for accurate web interface visualization
9- Draws patterns into a rectangular grid (frameBuffer)
10- Maps the rectangular grid to the corkscrew LED positions using readFrom()
11- Supports both noise patterns and manual LED positioning
12- Proper color boost and brightness controls
13
14Workflow:
151. Draw patterns into frameBuffer (rectangular grid for easy 2D drawing)
162. Use corkscrew.readFrom(frameBuffer) to map grid to corkscrew LED positions
173. Display the corkscrew buffer directly via FastLED
184. Web interface shows actual corkscrew spiral shape via fl::ScreenMap
19
20*/
21
22
23#include "FastLED.h"
25
26
27#include "fl/stl/assert.h"
28#include "fl/gfx/corkscrew.h"
29#include "fl/math/grid.h"
30#include "fl/gfx/leds.h"
31#include "fl/math/math.h"
32#include "fl/math/screenmap.h"
33#include "fl/stl/sstream.h"
34#include "fl/log/log.h"
35#include "noise.h"
36#include "fl/stl/array.h"
37#include "fl/fx/2d/wave.h"
38#include "fl/fx/2d/blend.h"
39#include "fl/fx/fx_engine.h"
41
42// #include "fl::vec3.h"
43
44
45
46#ifndef PIN_DATA
47#define PIN_DATA 1 // Universally available pin
48#endif
49
50#ifndef PIN_CLOCK
51#define PIN_CLOCK 2 // Universally available pin
52#endif
53
54
55#ifdef TEST
56#define NUM_LEDS 4
57#define CORKSCREW_TURNS 2 // Default to 19 turns
58#else
59#define NUM_LEDS 288
60#define CORKSCREW_TURNS 19.25 // Default to 19 turns
61#endif
62
63// #define CM_BETWEEN_LEDS 1.0 // 1cm between LEDs
64// #define CM_LED_DIAMETER 0.5 // 0.5cm LED diameter
65
66fl::UITitle festivalStickTitle("Festival Stick - Advanced Version");
68 "# Festival Stick Demo\n\n"
69 "This example demonstrates **proper corkscrew LED mapping** for a festival stick using FastLED's advanced mapping capabilities.\n\n"
70 "## Key Features\n"
71 "- **19+ turns** with 288 LEDs total\n"
72 "- Uses `fl::Corkscrew.toScreenMap()` for accurate web interface visualization\n"
73 "- Multiple render modes: **Noise**, **Position**, **Fire**, **Wave**, and **fl::Animartrix** effects\n"
74 "- Real-time cylindrical surface mapping\n"
75 "- **Wave mode**: Cylindrical 2D wave simulation with ripple effects and configurable blur\n"
76 "- **fl::Animartrix mode**: Advanced 2D animation effects with polar coordinate patterns\n\n"
77 "## How It Works\n"
78 "1. Draws patterns into a rectangular grid (`frameBuffer`)\n"
79 "2. Maps the grid to corkscrew LED positions using `readFrom()`\n"
80 "3. Web interface shows the actual spiral shape via fl::ScreenMap\n\n"
81 "*Select different render modes and adjust parameters to see various effects!*");
82
83// UIHelp festivalStickHelp("Festival Stick - Advanced Guide");
84
85// UIHelp corkscrewMappingHelp("Understanding fl::Corkscrew Mapping");
86
87// UIHelp uiControlsHelp("UI Controls Guide");
88
89
90
91fl::UISlider speed("Speed", 0.1f, 0.01f, 1.0f, 0.01f);
92fl::UISlider positionCoarse("Position Coarse (10x)", 0.0f, 0.0f, 1.0f, 0.01f);
93fl::UISlider positionFine("Position Fine (1x)", 0.0f, 0.0f, 0.1f, 0.001f);
94fl::UISlider positionExtraFine("Position Extra Fine (0.1x)", 0.0f, 0.0f, 0.01f, 0.0001f);
95fl::UISlider brightness("Brightness", 255, 0, 255, 1);
96
97fl::UICheckbox autoAdvance("Auto Advance", true);
98fl::UICheckbox allWhite("All White", false);
99fl::UICheckbox splatRendering("Splat Rendering", true);
100
101// Noise controls (grouped under noiseGroup)
102
103fl::UISlider noiseScale("Noise Scale", 100, 10, 200, 5);
104fl::UISlider noiseSpeed("Noise Speed", 4, 1, 100, 1);
105
106// fl::UIDropdown examples - noise-related color palette
107fl::string paletteOptions[] = {"Party", "Heat", "Ocean", "Forest", "Rainbow"};
108fl::string renderModeOptions[] = { "Wave", "fl::Animartrix", "Noise", "Position", "Fire" };
109
110
113
114
115
117 "EASE_NONE",
118 "EASE_IN_QUAD",
119 "EASE_OUT_QUAD",
120 "EASE_IN_OUT_QUAD",
121 "EASE_IN_CUBIC",
122 "EASE_OUT_CUBIC",
123 "EASE_IN_OUT_CUBIC",
124 "EASE_IN_SINE",
125 "EASE_OUT_SINE",
126 "EASE_IN_OUT_SINE"
127};
128
130 if (value == "fl::EaseType::EASE_NONE") {
132 } else if (value == "EASE_IN_QUAD") {
134 } else if (value == "EASE_OUT_QUAD") {
136 } else if (value == "EASE_IN_OUT_QUAD") {
138 } else if (value == "EASE_IN_CUBIC") {
140 } else if (value == "EASE_OUT_CUBIC") {
142 } else if (value == "EASE_IN_OUT_CUBIC") {
144 } else if (value == "EASE_IN_SINE") {
146 } else if (value == "EASE_OUT_SINE") {
148 } else if (value == "EASE_IN_OUT_SINE") {
150 } else {
152 }
153}
154
155// Color boost controls
158
159// Fire-related UI controls (added for cylindrical fire effect)
160fl::UISlider fireScaleXY("Fire Scale", 8, 1, 100, 1);
161fl::UISlider fireSpeedY("Fire SpeedY", 1.3, 1, 6, .1);
162fl::UISlider fireScaleX("Fire ScaleX", .3, 0.1, 3, .01);
163fl::UISlider fireInvSpeedZ("Fire Inverse SpeedZ", 20, 1, 100, 1);
164UINumberField firePalette("Fire Palette", 0, 0, 2);
165
166// Wave-related UI controls (cylindrical wave effects)
167fl::UISlider waveSpeed("Wave Speed", 0.03f, 0.0f, 1.0f, 0.01f);
168fl::UISlider waveDampening("Wave Dampening", 9.1f, 0.0f, 20.0f, 0.1f);
169fl::UICheckbox waveHalfDuplex("Wave Half Duplex", true);
170fl::UICheckbox waveIsotropicStencil("Wave Isotropic stencil (rounder ripples)", false);
171fl::UICheckbox waveAutoTrigger("Wave Auto Trigger", true);
172fl::UISlider waveTriggerSpeed("Wave Trigger Speed", 0.5f, 0.0f, 1.0f, 0.01f);
174UINumberField wavePalette("Wave Palette", 0, 0, 2);
175
176// Wave blur controls (added for smoother wave effects)
177fl::UISlider waveBlurAmount("Wave Blur Amount", 50, 0, 172, 1);
178fl::UISlider waveBlurPasses("Wave Blur Passes", 1, 1, 10, 1);
179
180// Fire color palettes (from FireCylinder)
182 0, 0, 0, 0,
183 32, 255, 0, 0,
184 190, 255, 255, 0,
185 255, 255, 255, 255
186};
187
188DEFINE_GRADIENT_PALETTE(electricGreenFirePal){
189 0, 0, 0, 0,
190 32, 0, 70, 0,
191 190, 57, 255, 20,
192 255, 255, 255, 255
193};
194
195DEFINE_GRADIENT_PALETTE(electricBlueFirePal){
196 0, 0, 0, 0,
197 32, 0, 0, 70,
198 128, 20, 57, 255,
199 255, 255, 255, 255
200};
201
202// Wave color palettes (for cylindrical wave effects)
204 0, 0, 0, 0, // Black (no wave)
205 32, 0, 0, 70, // Dark blue (low wave)
206 128, 20, 57, 255, // Electric blue (medium wave)
207 255, 255, 255, 255 // White (high wave)
208};
209
211 0, 0, 0, 0, // Black (no wave)
212 8, 128, 64, 64, // Green with red tint (very low wave)
213 16, 255, 222, 222, // Pinkish red (low wave)
214 64, 255, 255, 255, // White (medium wave)
215 255, 255, 255, 255 // White (high wave)
216};
217
219 0, 255, 0, 0, // Red (no wave)
220 64, 255, 127, 0, // Orange (low wave)
221 128, 255, 255, 0, // Yellow (medium wave)
222 192, 0, 255, 0, // Green (high wave)
223 255, 0, 0, 255 // Blue (maximum wave)
224};
225
226// Create fl::UIGroup for noise controls using variadic constructor
227// This automatically assigns all specified controls to the "Noise Controls" group
234
235// fl::Animartrix-related UI controls
236UINumberField animartrixIndex("fl::Animartrix Animation", 5, 0, static_cast<int>(fl::AnimartrixAnim::NUM_ANIMATIONS) - 1);
237UINumberField animartrixColorOrder("fl::Animartrix Color Order", 0, 0, 5);
238fl::UISlider animartrixTimeSpeed("fl::Animartrix Time Speed", 1, -10, 10, .1);
239
241
242// Color palette for noise
243fl::CRGBPalette16 noisePalette = PartyColors_p;
244uint8_t colorLoop = 1;
245
246// Option 1: Runtime fl::Corkscrew (flexible, configurable at runtime)
248
249// Simple position tracking - one variable for both modes
250static float currentPosition = 0.0f;
251static uint32_t lastUpdateTime = 0;
252
253// Wave effect globals
254static uint32_t nextWaveTrigger = 0;
255
256
257
258
259// Option 2: Constexpr dimensions for compile-time fl::array sizing
260constexpr uint16_t CORKSCREW_WIDTH =
262constexpr uint16_t CORKSCREW_HEIGHT =
264
265// Now you can use these for fl::array initialization:
266// fl::CRGB frameBuffer[CORKSCREW_WIDTH * CORKSCREW_HEIGHT]; // Compile-time sized
267// fl::array
268
269// Create a corkscrew with:
270// - 30cm total length (300mm)
271// - 5cm width (50mm)
272// - 2mm LED inner diameter
273// - 24 LEDs per turn
274// fl::ScreenMap screenMap = makeCorkScrew(NUM_LEDS,
275// 300.0f, 50.0f, 2.0f, 24.0f);
276
277// fl::vector<vec3f> mapCorkScrew = makeCorkScrew(args);
280
281// Wave effect objects - declared here but initialized in setup()
282fl::WaveFxPtr waveFx;
283fl::Blend2dPtr waveBlend;
284
285// fl::Animartrix effect objects - declared here but initialized in setup()
289
290void setup() {
291 // Use constexpr dimensions (computed at compile time)
292 constexpr int width = CORKSCREW_WIDTH; // = 16
293 constexpr int height = CORKSCREW_HEIGHT; // = 18
294
295
296 // Noise controls are now automatically grouped by the fl::UIGroup constructor
297 // The noiseGroup variadic constructor automatically called setGroup() on all controls
298
299
300 // Or use runtime corkscrew for dynamic sizing
301 // int width = corkscrew.cylinder_width();
302 // int height = corkscrew.cylinder_height();
303
305
306 // Use the corkscrew's internal buffer for the LED strip
308 &FastLED.addLeds<APA102HD, PIN_DATA, PIN_CLOCK, BGR>(corkscrew.rawData(), NUM_LEDS);
309
310 // CLEDController *controller =
311 // &FastLED.addLeds<WS2812, 3, BGR>(stripLeds, NUM_LEDS);
312
313 // NEW: Create fl::ScreenMap directly from fl::Corkscrew using toScreenMap()
314 // This maps each LED index to its exact position on the corkscrew spiral
315 // instead of using a rectangular grid mapping
316 fl::ScreenMap corkscrewScreenMap = corkscrew.toScreenMap(0.2f);
317
318 // OLD WAY (rectangular grid - not accurate for corkscrew visualization):
319 // fl::ScreenMap screenMap = xyMap.toScreenMap();
320 // screenMap.setDiameter(.2f);
321
322 // Set the corkscrew screen map for the controller
323 // This allows the web interface to display the actual corkscrew spiral shape
324 controller->setScreenMap(corkscrewScreenMap);
325
326 // Initialize wave effects for cylindrical surface
327 fl::XYMap xyRect(width, height, false); // Rectangular grid for wave simulation
328 fl::WaveFx::Args waveArgs;
329 waveArgs.factor = fl::SuperSample::SUPER_SAMPLE_2X; // 2x supersampling for smoother waves
330 waveArgs.half_duplex = true; // Only positive waves
331 waveArgs.auto_updates = true; // Auto-update simulation
332 waveArgs.speed = 0.16f; // Wave propagation speed
333 waveArgs.dampening = 6.0f; // Wave energy loss
334 waveArgs.x_cyclical = true; // Enable cylindrical wrapping!
335 waveArgs.crgbMap = fl::make_shared<fl::WaveCrgbGradientMap>(waveBluepal); // Default color palette
336
337 // Create wave effect with cylindrical mapping
338 waveFx = fl::make_shared<fl::WaveFx>(xyRect, waveArgs);
339
340 // Create blender for wave effects (allows multiple wave layers in future)
342 waveBlend->add(waveFx);
343
344 // Initialize fl::Animartrix effect
345 fl::XYMap animartrixXyMap = fl::XYMap::constructRectangularGrid(width, height, 0);
346 animartrix.reset(new fl::Animartrix(animartrixXyMap, fl::AnimartrixAnim::POLAR_WAVES)); // ok bare allocation
347 fxEngine.reset(new fl::FxEngine(width * height)); // ok bare allocation
348 fxEngine->addFx(*animartrix);
349
350 // Demonstrate fl::UIGroup functionality for noise controls
351 FL_WARN("Noise UI Group initialized: " << noiseGroup.name());
352 FL_WARN(" This group contains noise pattern controls:");
353 FL_WARN(" - Use Noise Pattern toggle");
354 FL_WARN(" - Noise Scale and Speed sliders");
355 FL_WARN(" - Color Palette selection for noise");
356 FL_WARN(" fl::UIGroup automatically applied group membership via variadic constructor");
357
358 // Set initial dropdown selections
359 paletteDropdown.setSelectedIndex(0); // Party
360 renderModeDropdown.setSelectedIndex(0); // Fire (new default)
361
362 // Add onChange callbacks for dropdowns
363 paletteDropdown.onChanged([](fl::UIDropdown &dropdown) {
364 fl::string selectedPalette = dropdown.value();
365 FL_WARN("Noise palette changed to: " << selectedPalette);
366 if (selectedPalette == "Party") {
368 } else if (selectedPalette == "Heat") {
370 } else if (selectedPalette == "Ocean") {
372 } else if (selectedPalette == "Forest") {
374 } else if (selectedPalette == "Rainbow") {
376 }
377 });
378
379 renderModeDropdown.onChanged([](fl::UIDropdown &dropdown) {
380 fl::string mode = dropdown.value();
381 // Simple example of using getOption()
382 for(size_t i = 0; i < dropdown.getOptionCount(); i++) {
383 if(dropdown.getOption(i) == mode) {
384 FL_WARN("Render mode changed to: " << mode);
385 }
386 }
387 });
388
389 // Add onChange callback for animartrix color order
390 animartrixColorOrder.onChanged([](int value) {
391 EOrder order = RGB;
392 switch(value) {
393 case 0: order = RGB; break;
394 case 1: order = RBG; break;
395 case 2: order = GRB; break;
396 case 3: order = GBR; break;
397 case 4: order = BRG; break;
398 case 5: order = BGR; break;
399 }
400 if (animartrix.get()) {
401 animartrix->setColorOrder(order);
402 }
403 });
404
405
406 waveFx->setCrgbMap(crgMap);
407
408 frameBufferPtr = corkscrew.getOrCreateInputSurface();
409}
410
411
412FL_OPTIMIZATION_LEVEL_O0_BEGIN // Works around a compile bug in clang 19
413float get_position(uint32_t now) {
414 if (autoAdvance.value()) {
415 // Check if auto-advance was just enabled
416 // Auto-advance mode: increment smoothly from current position
417 float elapsedSeconds = float(now - lastUpdateTime) / 1000.0f;
418 float increment = elapsedSeconds * speed.value() *
419 0.3f; // Make it 1/20th the original speed
420 currentPosition = fl::fmodf(currentPosition + increment, 1.0f);
421 lastUpdateTime = now;
422 return currentPosition;
423 } else {
424 // Manual mode: use the dual slider control
425 float combinedPosition = positionCoarse.value() + positionFine.value() + positionExtraFine.value();
426 // Clamp to ensure we don't exceed 1.0
427 if (combinedPosition > 1.0f)
428 combinedPosition = 1.0f;
429 return combinedPosition;
430 }
431}
433
435 // Get current UI values
436 uint8_t noise_scale = noiseScale.value();
437 uint8_t noise_speed = noiseSpeed.value();
438
439 // Derive noise coordinates from current time instead of forward iteration
440 uint32_t now = fl::millis();
441 uint16_t noise_z = now * noise_speed / 10; // Primary time dimension
442 uint16_t noise_x = now * noise_speed / 80; // Slow drift in x
443 uint16_t noise_y = now * noise_speed / 160; // Even slower drift in y (opposite direction)
444
445 int width = frameBufferPtr->width();
446 int height = frameBufferPtr->height();
447
448 // Data smoothing for low speeds (from NoisePlusPalette example)
449 uint8_t dataSmoothing = 0;
450 if(noise_speed < 50) {
451 dataSmoothing = 200 - (noise_speed * 4);
452 }
453
454 // Generate noise for each pixel in the frame buffer using cylindrical mapping
455 for(int x = 0; x < width; x++) {
456 for(int y = 0; y < height; y++) {
457 // Convert rectangular coordinates to cylindrical coordinates
458 // Map x to angle (0 to 2*FL_PI), y remains as height
459 float angle = (float(x) / float(width)) * 2.0f * FL_PI;
460
461 // Convert cylindrical coordinates to cartesian for noise sampling
462 // Use the noise_scale to control the cylinder size in noise space
463 float cylinder_radius = noise_scale; // Use the existing noise_scale parameter
464
465 // Calculate cartesian coordinates on the cylinder surface
466 float noise_x_cyl = fl::cos(angle) * cylinder_radius;
467 float noise_y_cyl = fl::sin(angle) * cylinder_radius;
468 float noise_z_height = float(y) * noise_scale; // Height component
469
470 // Apply time-based offsets
471 int xoffset = int(noise_x_cyl) + noise_x;
472 int yoffset = int(noise_y_cyl) + noise_y;
473 int zoffset = int(noise_z_height) + noise_z;
474
475 // Generate 8-bit noise value using 3D Perlin noise with cylindrical coordinates
476 uint8_t data = inoise8(xoffset, yoffset, zoffset);
477
478 // Expand the range from ~16-238 to 0-255 (from NoisePlusPalette)
479 data = qsub8(data, 16);
480 data = qadd8(data, scale8(data, 39));
481
482 // Apply data smoothing if enabled
483 if(dataSmoothing) {
484 fl::CRGB oldColor = frameBufferPtr->at(x, y);
485 uint8_t olddata = (oldColor.r + oldColor.g + oldColor.b) / 3; // Simple brightness extraction
486 uint8_t newdata = scale8(olddata, dataSmoothing) + scale8(data, 256 - dataSmoothing);
487 data = newdata;
488 }
489
490 // Map noise to color using palette (adapted from NoisePlusPalette)
491 uint8_t index = data;
492 uint8_t bri = data;
493
494 // Add color cycling if enabled - also derive from time
495 uint8_t ihue = 0;
496 if(colorLoop) {
497 ihue = (now / 100) % 256; // Derive hue from time instead of incrementing
498 index += ihue;
499 }
500
501 // Enhance brightness (from NoisePlusPalette example)
502 // if(bri > 127) {
503 // //bri = 255;
504 // } else {
505 // //bri = dim8_raw(bri * 2);
506 // }
507
508 // Get color from palette and set pixel
509 fl::CRGB color = ColorFromPalette(noisePalette, index, bri);
510
511 // Apply color boost using ease functions
512 fl::EaseType sat_ease = getEaseType(saturationFunction.value());
513 fl::EaseType lum_ease = getEaseType(luminanceFunction.value());
514 color = color.colorBoost(sat_ease, lum_ease);
515
516 frameBufferPtr->at(x, y) = color;
517 }
518 }
519}
520
521void drawNoise(uint32_t now) {
522 FL_UNUSED(now);
524}
525
526void draw(float pos) {
527 if (splatRendering) {
528 fl::Tile2x2_u8_wrap pos_tile = corkscrew.at_wrap(pos);
529 //FL_WARN("pos_tile: " << pos_tile);
530 fl::CRGB color = fl::CRGB::Blue;
531 // Apply color boost using ease functions
532 fl::EaseType sat_ease = getEaseType(saturationFunction.value());
533 fl::EaseType lum_ease = getEaseType(luminanceFunction.value());
534 color = color.colorBoost(sat_ease, lum_ease);
535 // Draw each pixel in the 2x2 tile using the new wrapping API
536 for (int dx = 0; dx < 2; ++dx) {
537 for (int dy = 0; dy < 2; ++dy) {
538 fl::Tile2x2_u8_wrap::Entry data = pos_tile.at(dx, dy);
539 fl::vec2<fl::u16> wrapped_pos = data.first; // Already wrapped position
540 uint8_t alpha = data.second; // Alpha value
541
542 if (alpha > 0) { // Only draw if there's some alpha
543 fl::CRGB c = color;
544 c.nscale8(alpha); // Scale the color by the alpha value
545 frameBufferPtr->at(wrapped_pos.x, wrapped_pos.y) = c;
546 }
547 }
548 }
549 } else {
550 // None splat rendering, looks aweful.
551 fl::vec2f pos_vec2f = corkscrew.at_no_wrap(pos);
552 fl::vec2<fl::u16> pos_i16 = fl::vec2<fl::u16>(pos_vec2f.x, pos_vec2f.y);
553
554 fl::CRGB color = fl::CRGB::Blue;
555 // Apply color boost using ease functions
556 fl::EaseType sat_ease = getEaseType(saturationFunction.value());
557 fl::EaseType lum_ease = getEaseType(luminanceFunction.value());
558 color = color.colorBoost(sat_ease, lum_ease);
559
560 // Now map the cork screw position to the cylindrical buffer that we
561 // will draw.
562 frameBufferPtr->at(pos_i16.x, pos_i16.y) = color; // Draw a blue pixel at (w, h)
563 }
564}
565
566fl::CRGBPalette16 getFirePalette() {
567 int paletteIndex = (int)firePalette.value();
568 switch (paletteIndex) {
569 case 0:
570 return firepal;
571 case 1:
572 return electricGreenFirePal;
573 case 2:
574 return electricBlueFirePal;
575 default:
576 return firepal;
577 }
578}
579
580uint8_t getFirePaletteIndex(uint32_t millis32, int width, int max_width, int height, int max_height,
581 uint32_t y_speed) {
582 uint16_t scale = fireScaleXY.as<uint16_t>();
583
584 float xf = (float)width / (float)max_width;
585 uint8_t x = (uint8_t)(xf * 255);
586
587 uint32_t cosx = cos8(x);
588 uint32_t sinx = sin8(x);
589
590 float trig_scale = scale * fireScaleX.value();
591 cosx *= trig_scale;
592 sinx *= trig_scale;
593
594 uint32_t y = height * scale + y_speed;
595
596 uint16_t z = millis32 / fireInvSpeedZ.as<uint16_t>();
597
598 uint16_t noise16 = inoise16(cosx << 8, sinx << 8, y << 8, z << 8);
599
600 uint8_t noise_val = noise16 >> 8;
601
602 int8_t subtraction_factor = abs8(height - (max_height - 1)) * 255 /
603 (max_height - 1);
604
605 return qsub8(noise_val, subtraction_factor);
606}
607
608void fillFrameBufferFire(uint32_t now) {
609 fl::CRGBPalette16 myPal = getFirePalette();
610
611 // Calculate the current y-offset for animation (makes the fire move)
612 uint32_t y_speed = now * fireSpeedY.value();
613
614 int width = frameBufferPtr->width();
615 int height = frameBufferPtr->height();
616
617 // Loop through every pixel in our cylindrical matrix
618 for (int w = 0; w < width; w++) {
619 for (int h = 0; h < height; h++) {
620 // Calculate which color to use from our palette for this pixel
621 uint8_t palette_index =
622 getFirePaletteIndex(now, w, width, h, height, y_speed);
623
624 // Get the actual RGB color from the palette
625 fl::CRGB color = ColorFromPalette(myPal, palette_index, 255);
626
627 // Apply color boost using ease functions
628 fl::EaseType sat_ease = getEaseType(saturationFunction.value());
629 fl::EaseType lum_ease = getEaseType(luminanceFunction.value());
630 color = color.colorBoost(sat_ease, lum_ease);
631
632 // Set the pixel in the frame buffer
633 // Flip coordinates to make fire rise from bottom
634 frameBufferPtr->at((width - 1) - w, (height - 1) - h) = color;
635 }
636 }
637}
638
639void drawFire(uint32_t now) {
641}
642
643// Wave effect helper functions
644fl::CRGBPalette16 getWavePalette() {
645 int paletteIndex = (int)wavePalette.value();
646 switch (paletteIndex) {
647 case 0:
648 return waveBluepal; // Electric blue waves
649 case 1:
650 return waveGreenpal; // Green/red waves
651 case 2:
652 return waveRainbowpal; // Rainbow waves
653 default:
654 return waveBluepal; // Default to blue
655 }
656}
657
659 // Create a ripple at a random position within the central area
660 float perc = 0.15f; // 15% margin from edges
661 int width = corkscrew.cylinderWidth();
662 int height = corkscrew.cylinderHeight();
663
664 int min_x = perc * width;
665 int max_x = (1 - perc) * width;
666 int min_y = perc * height;
667 int max_y = (1 - perc) * height;
668
669 int x = random8(min_x, max_x);
670 int y = random8(min_y, max_y);
671
672 // Trigger a 2x2 wave ripple for more punch (compensates for blur reduction)
673 float ripple_strength = 1.5f; // Higher value for more impact
674 waveFx->setf(x, y, ripple_strength);
675 waveFx->setf(x + 1, y, ripple_strength);
676 waveFx->setf(x, y + 1, ripple_strength);
677 waveFx->setf(x + 1, y + 1, ripple_strength);
678
679 FL_WARN("Wave ripple triggered at (" << x << ", " << y << ") with 2x2 pattern");
680}
681
682void processWaveAutoTrigger(uint32_t now) {
683 // Handle automatic wave triggering
684 if (waveAutoTrigger.value()) {
685 if (now >= nextWaveTrigger) {
687
688 // Calculate next trigger time based on speed
689 float speed = 1.0f - waveTriggerSpeed.value();
690 uint32_t min_interval = (uint32_t)(500 * speed); // Minimum 500ms * speed
691 uint32_t max_interval = (uint32_t)(3000 * speed); // Maximum 3000ms * speed
692
693 // Ensure valid range
694 uint32_t min = fl::min(min_interval, max_interval);
695 uint32_t max = fl::max(min_interval, max_interval);
696 if (min >= max) max = min + 1;
697
699 }
700 }
701}
702
703
704
705void drawWave(uint32_t now) {
706 // Update wave parameters from UI
707 waveFx->setSpeed(waveSpeed.value());
708 waveFx->setDampening(waveDampening.value());
709 waveFx->setHalfDuplex(waveHalfDuplex.value());
710 waveFx->setXCylindrical(true); // Always keep cylindrical for corkscrew
711 waveFx->setStencil(waveIsotropicStencil
714
715 // Update wave color palette
716 fl::CRGBPalette16 currentPalette = getWavePalette();
717 crgMap->setGradient(currentPalette);
718
719
720
721 // Apply blur settings to the wave blend (for smoother wave effects)
722 waveBlend->setGlobalBlurAmount(waveBlurAmount.value());
723 waveBlend->setGlobalBlurPasses(waveBlurPasses.value());
724
725 // Check if manual trigger button was pressed
726 if (waveTriggerButton.value()) {
728 }
729
730 // Handle auto-triggering
732
733 // Draw the wave effect directly to the frame buffer
734 // Create a DrawContext for the wave renderer
735 fl::Fx::DrawContext waveContext(now, frameBufferPtr->span());
736 waveBlend->draw(waveContext);
737}
738
739void drawAnimartrix(uint32_t now) {
740 // Update animartrix parameters from UI
741 fxEngine->setSpeed(animartrixTimeSpeed.value());
742
743 // Handle animation index changes
744 static int lastAnimartrixIndex = -1; // okay static in header
745 if (animartrixIndex.value() != lastAnimartrixIndex) {
746 lastAnimartrixIndex = animartrixIndex.value();
747 animartrix->fxSet(animartrixIndex.value());
748 }
749
750 // Draw the animartrix effect directly to the frame buffer
751 fxEngine->draw(now, corkscrew.data());
752}
753
754void loop() {
755
756 delay(4);
757 uint32_t now = fl::millis();
758 frameBufferPtr->clear();
759
760 if (allWhite) {
761 fl::CRGB whiteColor = fl::CRGB(8, 8, 8);
762 for (fl::u32 x = 0; x < frameBufferPtr->width(); x++) {
763 for (fl::u32 y = 0; y < frameBufferPtr->height(); y++) {
764 frameBufferPtr->at(x, y) = whiteColor;
765 }
766 }
767 }
768
769
770 // Update the corkscrew mapping with auto-advance or manual position control
771 float combinedPosition = get_position(now);
772 float pos = combinedPosition * (corkscrew.size() - 1);
773
774
775 if (renderModeDropdown.value() == "Noise") {
776 drawNoise(now);
777 } else if (renderModeDropdown.value() == "Fire") {
778 drawFire(now);
779 } else if (renderModeDropdown.value() == "Wave") {
780
781 drawWave(now);
782 } else if (renderModeDropdown.value() == "fl::Animartrix") {
783 drawAnimartrix(now);
784 } else {
785 draw(pos);
786 }
787
788
789 // Use the new readFrom workflow:
790 // 1. Read directly from the frameBuffer fl::Grid into the corkscrew's internal buffer
791 // use_multi_sampling = true will use multi-sampling to sample from the source grid,
792 // this will give a little bit better accuracy and the screenmap will be more accurate.
793 const bool use_multi_sampling = splatRendering;
794 // corkscrew.readFrom(frameBuffer, use_multi_sampling);
795 corkscrew.draw(use_multi_sampling);
796
797 // The corkscrew's buffer is now populated and FastLED will display it directly
798
799 FastLED.setBrightness(brightness.value());
800 FastLED.show();
801}
fl::XYMap xyMap
fl::FxEngine fxEngine(NUM_LEDS)
#define NUM_LEDS
fl::Animartrix animartrix(xyMap, FIRST_ANIMATION)
#define PIN_DATA
fl::UISlider brightness("Brightness", BRIGHTNESS, 0, 255)
int y
Definition simple.h:93
int x
Definition simple.h:92
uint8_t pos
Definition Blur.ino:11
fl::UICheckbox allWhite("All White", false)
fl::UIDropdown saturationFunction("Saturation Function", easeOptions)
fl::UIDropdown luminanceFunction("Luminance Function", easeOptions)
CRGBPalette16 currentPalette
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
@ APA102HD
APA102 LED chipset with 5-bit gamma correction.
Definition FastLED.h:267
uint32_t z[NUM_LAYERS]
Definition Fire2023.h:93
CLEDController * controller
fl::UISlider scale("Scale", 4,.1, 4,.1)
uint16_t speed
Definition Noise.ino:66
int y_speed
::fl::DrawContext DrawContext
Definition fx.h:21
Manages and renders multiple visual effects (Fx) for LED strips.
Definition fx_engine.h:33
Entry & at(u16 x, u16 y)
fl::pair< vec2< u16 >, u8 > Entry
Definition tile2x2.h:103
fl::string getOption(fl::size index) const FL_NOEXCEPT
Definition dropdown.h:135
fl::string value() const FL_NOEXCEPT
Definition dropdown.h:126
fl::size getOptionCount() const FL_NOEXCEPT
Definition dropdown.h:134
WaveFxArgs Args
Definition wave.h:168
static XYMap constructRectangularGrid(u16 width, u16 height, u16 offset=0) FL_NOEXCEPT
Definition xymap.cpp.hpp:35
fl::CLEDController CLEDController
#define DEFINE_GRADIENT_PALETTE(X)
CRGB ColorFromPalette(const CRGBPalette16 &pal, fl::u8 index, fl::u8 brightness, TBlendType blendType)
fl::UISlider noiseSpeed("Noise Speed", 4, 1, 100, 1)
fl::UISlider waveTriggerSpeed("Wave Trigger Speed", 0.5f, 0.0f, 1.0f, 0.01f)
fl::UICheckbox splatRendering("Splat Rendering", true)
fl::UIDropdown renderModeDropdown("Render Mode", renderModeOptions)
fl::UICheckbox waveIsotropicStencil("Wave Isotropic stencil (rounder ripples)", false)
fl::UIGroup colorBoostGroup("Color Boost", saturationFunction, luminanceFunction)
fl::EaseType getEaseType(fl::string value)
Definition curr.h:129
fl::WaveCrgbGradientMapPtr crgMap
Definition curr.h:288
fl::Blend2dPtr waveBlend
Definition curr.h:283
fl::UIGroup animartrixGroup("fl::Animartrix Controls", animartrixIndex, animartrixTimeSpeed, animartrixColorOrder)
fl::UICheckbox waveHalfDuplex("Wave Half Duplex", true)
FL_OPTIMIZATION_LEVEL_O0_BEGIN float get_position(uint32_t now)
Definition curr.h:413
void draw(float pos)
Definition curr.h:526
FL_OPTIMIZATION_LEVEL_O0_END void fillFrameBufferNoise()
Definition curr.h:434
fl::UICheckbox autoAdvance("Auto Advance", true)
fl::UIGroup pointGraphicsGroup("Point Graphics Mode", speed, positionCoarse, positionFine, positionExtraFine, autoAdvance)
uint8_t colorLoop
Definition curr.h:244
fl::string paletteOptions[]
Definition curr.h:107
fl::UITitle festivalStickTitle("Festival Stick - Advanced Version")
void drawWave(uint32_t now)
Definition curr.h:705
fl::UISlider waveSpeed("Wave Speed", 0.03f, 0.0f, 1.0f, 0.01f)
fl::UISlider fireSpeedY("Fire SpeedY", 1.3, 1, 6,.1)
#define PIN_CLOCK
Definition curr.h:51
void setup()
Definition curr.h:290
UINumberField animartrixIndex("fl::Animartrix Animation", 5, 0, static_cast< int >(fl::AnimartrixAnim::NUM_ANIMATIONS) - 1)
fl::Corkscrew corkscrew(CORKSCREW_TURNS, NUM_LEDS)
fl::CRGBPalette16 noisePalette
Definition curr.h:243
fl::UIDropdown saturationFunction("Saturation Function", easeInfo)
void triggerWaveRipple()
Definition curr.h:658
fl::UISlider animartrixTimeSpeed("fl::Animartrix Time Speed", 1, -10, 10,.1)
fl::UISlider noiseScale("Noise Scale", 100, 10, 200, 5)
fl::CRGBPalette16 getFirePalette()
Definition curr.h:566
static float currentPosition
Definition curr.h:250
fl::UIGroup waveGroup("Wave Controls", waveSpeed, waveDampening, waveHalfDuplex, waveIsotropicStencil, waveAutoTrigger, waveTriggerSpeed, waveTriggerButton, wavePalette, waveBlurAmount, waveBlurPasses)
constexpr uint16_t CORKSCREW_HEIGHT
Definition curr.h:262
fl::shared_ptr< fl::Grid< fl::CRGB > > frameBufferPtr
Definition curr.h:279
void processWaveAutoTrigger(uint32_t now)
Definition curr.h:682
static uint32_t nextWaveTrigger
Definition curr.h:254
void drawNoise(uint32_t now)
Definition curr.h:521
fl::UIButton waveTriggerButton("Trigger Wave")
fl::UIDropdown paletteDropdown("Color Palette", paletteOptions)
fl::UISlider positionFine("Position Fine (1x)", 0.0f, 0.0f, 0.1f, 0.001f)
uint8_t getFirePaletteIndex(uint32_t millis32, int width, int max_width, int height, int max_height, uint32_t y_speed)
Definition curr.h:580
static uint32_t lastUpdateTime
Definition curr.h:251
fl::UIGroup renderGroup("Render Options", renderModeDropdown, splatRendering, allWhite, brightness)
fl::UISlider fireScaleX("Fire ScaleX",.3, 0.1, 3,.01)
fl::UISlider waveDampening("Wave Dampening", 9.1f, 0.0f, 20.0f, 0.1f)
void drawAnimartrix(uint32_t now)
Definition curr.h:739
fl::UIGroup fireGroup("Fire Controls", fireScaleXY, fireSpeedY, fireScaleX, fireInvSpeedZ, firePalette)
fl::string renderModeOptions[]
Definition curr.h:108
fl::UISlider fireScaleXY("Fire Scale", 8, 1, 100, 1)
constexpr uint16_t CORKSCREW_WIDTH
Definition curr.h:260
UINumberField wavePalette("Wave Palette", 0, 0, 2)
void drawFire(uint32_t now)
Definition curr.h:639
fl::UIDescription festivalStickDescription("# Festival Stick Demo\n\n" "This example demonstrates **proper corkscrew LED mapping** for a festival stick using FastLED's advanced mapping capabilities.\n\n" "## Key Features\n" "- **19+ turns** with 288 LEDs total\n" "- Uses `fl::Corkscrew.toScreenMap()` for accurate web interface visualization\n" "- Multiple render modes: **Noise**, **Position**, **Fire**, **Wave**, and **fl::Animartrix** effects\n" "- Real-time cylindrical surface mapping\n" "- **Wave mode**: Cylindrical 2D wave simulation with ripple effects and configurable blur\n" "- **fl::Animartrix mode**: Advanced 2D animation effects with polar coordinate patterns\n\n" "## How It Works\n" "1. Draws patterns into a rectangular grid (`frameBuffer`)\n" "2. Maps the grid to corkscrew LED positions using `readFrom()`\n" "3. Web interface shows the actual spiral shape via fl::ScreenMap\n\n" "*Select different render modes and adjust parameters to see various effects!*")
fl::UICheckbox waveAutoTrigger("Wave Auto Trigger", true)
fl::CRGBPalette16 getWavePalette()
Definition curr.h:644
UINumberField animartrixColorOrder("fl::Animartrix Color Order", 0, 0, 5)
fl::UISlider positionExtraFine("Position Extra Fine (0.1x)", 0.0f, 0.0f, 0.01f, 0.0001f)
fl::UIDropdown luminanceFunction("Luminance Function", easeInfo)
UINumberField firePalette("Fire Palette", 0, 0, 2)
fl::vector< fl::string > easeInfo
Definition curr.h:116
fl::WaveFxPtr waveFx
Definition curr.h:282
fl::UISlider waveBlurPasses("Wave Blur Passes", 1, 1, 10, 1)
fl::UISlider waveBlurAmount("Wave Blur Amount", 50, 0, 172, 1)
fl::UISlider fireInvSpeedZ("Fire Inverse SpeedZ", 20, 1, 100, 1)
void fillFrameBufferFire(uint32_t now)
Definition curr.h:608
fl::UIGroup noiseGroup("Noise Controls", noiseScale, noiseSpeed, paletteDropdown)
void loop()
Definition curr.h:754
fl::UISlider positionCoarse("Position Coarse (10x)", 0.0f, 0.0f, 1.0f, 0.01f)
constexpr EOrder BGR
Definition eorder.h:22
constexpr EOrder RBG
Definition eorder.h:18
fl::EOrder EOrder
Definition eorder.h:12
constexpr EOrder BRG
Definition eorder.h:21
constexpr EOrder GRB
Definition eorder.h:19
constexpr EOrder RGB
Definition eorder.h:17
constexpr EOrder GBR
Definition eorder.h:20
fl::ScreenMap screenMap
Definition Corkscrew.h:101
fl::UICheckbox splatRendering("Splat Rendering", true)
fl::Corkscrew corkscrew(CORKSCREW_TURNS, NUM_LEDS)
#define CORKSCREW_TURNS
Definition Corkscrew.h:70
fl::u16 inoise16(fl::u32 x, fl::u32 y, fl::u32 z, fl::u32 t)
fl::u8 inoise8(fl::u16 x, fl::u16 y, fl::u16 z)
const TProgmemRGBPalette16 OceanColors_p
Ocean colors, blues and whites.
const TProgmemRGBPalette16 HeatColors_p
Approximate "black body radiation" palette, akin to the FastLED HeatColor() function.
const TProgmemRGBPalette16 ForestColors_p
Forest colors, greens.
const TProgmemRGBPalette16 PartyColors_p
HSV color ramp: blue, purple, pink, red, orange, yellow (and back).
const TProgmemRGBPalette16 RainbowColors_p
HSV Rainbow.
LIB8STATIC fl::u16 random16() FL_NOEXCEPT
Generate a 16-bit random number.
Definition random8.h:63
LIB8STATIC fl::u8 random8() FL_NOEXCEPT
Generate an 8-bit random number.
Definition random8.h:53
#define FL_WARN(X)
Definition log.h:276
Centralized logging categories for FastLED hardware interfaces and subsystems.
#define FL_PI
Definition math.h:26
FL_DISABLE_WARNING_PUSH U constexpr common_type_t< T, U > min(T a, U b) FL_NOEXCEPT
Definition math.h:71
fl::CRGB CRGB
Definition video.h:15
constexpr common_type_t< T, U > max(T a, U b) FL_NOEXCEPT
Definition math.h:75
fl::u32 millis()
Universal millisecond timer - returns milliseconds since system startup.
vec2< float > vec2f
Definition geometry.h:333
constexpr fl::u16 calculateCorkscrewHeight(float totalTurns, fl::u16 numLeds)
Definition corkscrew.h:55
float fmodf(float x, float y) FL_NOEXCEPT
Definition math.h:336
shared_ptr< T > make_shared(Args &&... args) FL_NOEXCEPT
Definition shared_ptr.h:414
enable_if< is_fixed_point< T >::value, T >::type cos(T angle) FL_NOEXCEPT
constexpr fl::u16 calculateCorkscrewWidth(float totalTurns, fl::u16 numLeds)
Definition corkscrew.h:51
enable_if< is_fixed_point< T >::value, T >::type sin(T angle) FL_NOEXCEPT
EaseType
Definition ease.h:27
@ EASE_IN_OUT_CUBIC
Definition ease.h:34
@ EASE_OUT_QUAD
Definition ease.h:30
@ EASE_IN_QUAD
Definition ease.h:29
@ EASE_NONE
Definition ease.h:28
@ EASE_IN_OUT_QUAD
Definition ease.h:31
@ EASE_IN_CUBIC
Definition ease.h:32
@ EASE_IN_SINE
Definition ease.h:35
@ EASE_OUT_CUBIC
Definition ease.h:33
@ EASE_IN_OUT_SINE
Definition ease.h:37
@ EASE_OUT_SINE
Definition ease.h:36
#define FL_OPTIMIZATION_LEVEL_O0_END
#define FL_UNUSED(x)
#define FL_OPTIMIZATION_LEVEL_O0_BEGIN
2D wave simulation effect for LED matrices
Corkscrew LED strip projection and rendering.
CRGB colorBoost(EaseType saturation_function=EaseType::EASE_NONE, EaseType luminance_function=EaseType::EASE_NONE) const FL_NOEXCEPT
CRGB & nscale8(u8 scaledown) FL_NOEXCEPT
Scale down a RGB to N/256ths of its current brightness, using "plain math" dimming rules.
Definition crgb.cpp.hpp:88
@ Blue
<div style='background:#0000FF;width:4em;height:4em;'></div>
Definition crgb.h:512
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
bool x_cyclical
If true, waves wrap around the x-axis (cylindrical topology)
Definition wave.h:130
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
T1 first
Definition pair.h:16
T2 second
Definition pair.h:17
value_type y
Definition geometry.h:191
value_type x
Definition geometry.h:190