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