From 3b92610bcf42b5e8a71a41f23f068ff9f56cf2ea Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Fri, 12 Sep 2025 19:16:28 +0200 Subject: [PATCH 1/3] adding Shimmer FX Sends a shimmer accross the strip at defined (or random) intervals Optional brightness modulators: sine or perlin noise Can be used as an overlay to other effects. --- wled00/FX.cpp | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++ wled00/FX.h | 1 + 2 files changed, 70 insertions(+) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 3d580bb9a5..40ee79cfa4 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -4875,6 +4875,74 @@ uint16_t mode_FlowStripe(void) { } // mode_FlowStripe() static const char _data_FX_MODE_FLOWSTRIPE[] PROGMEM = "Flow Stripe@Hue speed,Effect speed;;!;pal=11"; +/* + Shimmer effect: moves a gradient with optional modulators across the strip at a given interval, up to 60 seconds + It can be used as an overlay to other effects or standalone + by DedeHai (Damian Schneider), based on idea from @Charming-Lime (#4905) +*/ +uint16_t mode_shimmer() { + if(!SEGENV.allocateData(sizeof(uint32_t))) { return mode_static(); } + uint32_t* lastTime = reinterpret_cast(SEGENV.data); + + uint32_t radius = (SEGMENT.custom1 * SEGLEN >> 7) + 1; // [1, 2*SEGLEN+1] pixels + uint32_t traversalDistance = (SEGLEN + 2 * radius) << 8; // total subpixels to cross, 1 pixel = 256 subpixels + uint32_t traversalTime = 200 + (255 - SEGMENT.speed) * 80; // [200, 20600] ms + uint32_t speed = (((traversalDistance << 5) / traversalTime) << 5); // subpixels/"second" note: total shift is 10 to approximate seconds, split shifts to guarantee no overflows + int32_t position = static_cast(SEGENV.step); // current position in subpixels + uint16_t inputstate = SEGMENT.intensity + SEGMENT.custom1; // current user input state + + // init + if (SEGENV.call == 0 || inputstate != SEGENV.aux1) { + position = -(radius << 8); + SEGENV.aux0 = 0; // aux0 is pause timer + *lastTime = strip.now; + SEGENV.aux1 = SEGMENT.intensity + SEGMENT.custom1; // save user input state + } + + uint32_t deltaTime = strip.now - *lastTime; + *lastTime = strip.now; + + if (SEGENV.aux0 > 0) { + SEGENV.aux0 = (SEGENV.aux0 > deltaTime) ? SEGENV.aux0 - deltaTime : 0; + } else { + // calculate movement step and update position + int32_t step = 1 + ((speed * deltaTime) >> 10); // subpixels moved this frame. note >>10 approximates ~substeps/seconds + position += step; + int endposition = (SEGLEN + radius) << 8; + if (position > endposition) { + SEGENV.aux0 = SEGMENT.intensity * 236; // [0, 60180] ms pause + if(SEGMENT.check3) SEGENV.aux0 = hw_random(SEGENV.aux0 + 1000); // randomise interval, +1 second to affect low intensity values + position = -(radius << 8); // reset to start position (out of frame) + } + SEGENV.step = (uint32_t)position; // save back + } + + if (SEGMENT.check2) + position = (SEGLEN << 8) - position; // invert position (and direction) + + for (int i = 0; i < SEGLEN; i++) { + uint32_t dist = abs(position - (i << 8)); + if (dist <= (radius << 8)) { + uint32_t color = SEGMENT.color_from_palette(i * 255 / SEGLEN, false, false, 0); + uint8_t blend = (dist * 255) / (radius << 8); // linear gradient + if (SEGMENT.custom2) { + uint8_t modVal; // modulation value + if (SEGMENT.check1) { + modVal = (sin16_t((i * SEGMENT.custom2 << 6) + (strip.now * SEGMENT.custom3 << 5)) >> 8) + 128; // sine modulation: regular "Zebra" stripes + } else { + modVal = perlin16((i * SEGMENT.custom2 << 7), strip.now * SEGMENT.custom3 << 5) >> 8; // perlin noise modulation + } + color = color_fade(color, modVal, true); // dim by modulator value + } + SEGMENT.setPixelColor(i, color_blend(color, SEGCOLOR(1), blend)); // blend to background color + } else { + SEGMENT.setPixelColor(i, SEGCOLOR(1)); + } + } + + return FRAMETIME; +} +static const char _data_FX_MODE_SHIMMER[] PROGMEM = "Shimmer@Speed,Interval,Size,Granular,Flow,Zebra,Reverse,Sporadic;Fx,Bg,Cx;!;1;pal=15,sx=220,ix=10,c2=0,c3=0"; #ifndef WLED_DISABLE_2D /////////////////////////////////////////////////////////////////////////////// @@ -10805,6 +10873,7 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_FLOWSTRIPE, &mode_FlowStripe, _data_FX_MODE_FLOWSTRIPE); addEffect(FX_MODE_WAVESINS, &mode_wavesins, _data_FX_MODE_WAVESINS); addEffect(FX_MODE_ROCKTAVES, &mode_rocktaves, _data_FX_MODE_ROCKTAVES); + addEffect(FX_MODE_SHIMMER, &mode_shimmer, _data_FX_MODE_SHIMMER); // --- 2D effects --- #ifndef WLED_DISABLE_2D diff --git a/wled00/FX.h b/wled00/FX.h index 097c857caf..d80f93abec 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -317,6 +317,7 @@ extern byte realtimeMode; // used in getMappedPixelIndex() #define FX_MODE_DJLIGHT 159 #define FX_MODE_2DFUNKYPLANK 160 //#define FX_MODE_2DCENTERBARS 161 +#define FX_MODE_SHIMMER 161 // gap fill, non SR 1D effect #define FX_MODE_2DPULSER 162 #define FX_MODE_BLURZ 163 #define FX_MODE_2DDRIFT 164 From 219ea9b334d4e7189355fc5ce5d1c87566429ea9 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Fri, 12 Sep 2025 19:43:05 +0200 Subject: [PATCH 2/3] better input state cange handling --- wled00/FX.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 40ee79cfa4..c1fae6f775 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -4889,14 +4889,14 @@ uint16_t mode_shimmer() { uint32_t traversalTime = 200 + (255 - SEGMENT.speed) * 80; // [200, 20600] ms uint32_t speed = (((traversalDistance << 5) / traversalTime) << 5); // subpixels/"second" note: total shift is 10 to approximate seconds, split shifts to guarantee no overflows int32_t position = static_cast(SEGENV.step); // current position in subpixels - uint16_t inputstate = SEGMENT.intensity + SEGMENT.custom1; // current user input state + uint16_t inputstate = (uint16_t(SEGMENT.intensity) << 8) | uint16_t(SEGMENT.custom1); // current user input state // init if (SEGENV.call == 0 || inputstate != SEGENV.aux1) { position = -(radius << 8); SEGENV.aux0 = 0; // aux0 is pause timer *lastTime = strip.now; - SEGENV.aux1 = SEGMENT.intensity + SEGMENT.custom1; // save user input state + SEGENV.aux1 = inputstate; // save user input state } uint32_t deltaTime = strip.now - *lastTime; From 9f8dbf3091d3dd350830fdc62416897ab9363012 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 13 Sep 2025 08:42:01 +0200 Subject: [PATCH 3/3] improved integer overflows, added static mode --- wled00/FX.cpp | 52 +++++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index c1fae6f775..bcd1ab8e55 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -4884,11 +4884,11 @@ uint16_t mode_shimmer() { if(!SEGENV.allocateData(sizeof(uint32_t))) { return mode_static(); } uint32_t* lastTime = reinterpret_cast(SEGENV.data); - uint32_t radius = (SEGMENT.custom1 * SEGLEN >> 7) + 1; // [1, 2*SEGLEN+1] pixels - uint32_t traversalDistance = (SEGLEN + 2 * radius) << 8; // total subpixels to cross, 1 pixel = 256 subpixels - uint32_t traversalTime = 200 + (255 - SEGMENT.speed) * 80; // [200, 20600] ms - uint32_t speed = (((traversalDistance << 5) / traversalTime) << 5); // subpixels/"second" note: total shift is 10 to approximate seconds, split shifts to guarantee no overflows - int32_t position = static_cast(SEGENV.step); // current position in subpixels + uint32_t radius = (SEGMENT.custom1 * SEGLEN >> 7) + 1; // [1, 2*SEGLEN+1] pixels + uint32_t traversalDistance = (SEGLEN + 2 * radius) << 8; // total subpixels to cross, 1 pixel = 256 subpixels + uint32_t traversalTime = 200 + (255 - SEGMENT.speed) * 80; // [200, 20600] ms + uint32_t speed = ((traversalDistance << 5) / traversalTime); // subpixels/512ms + int32_t position = static_cast(SEGENV.step); // current position in subpixels uint16_t inputstate = (uint16_t(SEGMENT.intensity) << 8) | uint16_t(SEGMENT.custom1); // current user input state // init @@ -4899,32 +4899,36 @@ uint16_t mode_shimmer() { SEGENV.aux1 = inputstate; // save user input state } - uint32_t deltaTime = strip.now - *lastTime; - *lastTime = strip.now; + if(SEGMENT.speed) { + uint32_t deltaTime = (strip.now - *lastTime) & 0x7F; // clamp to 127ms to avoid overflows. note: speed*deltaTime can still overflow for segments > ~10k pixels + *lastTime = strip.now; - if (SEGENV.aux0 > 0) { - SEGENV.aux0 = (SEGENV.aux0 > deltaTime) ? SEGENV.aux0 - deltaTime : 0; - } else { - // calculate movement step and update position - int32_t step = 1 + ((speed * deltaTime) >> 10); // subpixels moved this frame. note >>10 approximates ~substeps/seconds - position += step; - int endposition = (SEGLEN + radius) << 8; - if (position > endposition) { - SEGENV.aux0 = SEGMENT.intensity * 236; // [0, 60180] ms pause - if(SEGMENT.check3) SEGENV.aux0 = hw_random(SEGENV.aux0 + 1000); // randomise interval, +1 second to affect low intensity values - position = -(radius << 8); // reset to start position (out of frame) + if (SEGENV.aux0 > 0) { + SEGENV.aux0 = (SEGENV.aux0 > deltaTime) ? SEGENV.aux0 - deltaTime : 0; + } else { + // calculate movement step and update position + int32_t step = 1 + ((speed * deltaTime) >> 5); // subpixels moved this frame. note >>5 as speed is in subpixels/512ms + position += step; + int endposition = (SEGLEN + radius) << 8; + if (position > endposition) { + SEGENV.aux0 = SEGMENT.intensity * 236; // [0, 60180] ms pause + if(SEGMENT.check3) SEGENV.aux0 = hw_random(SEGENV.aux0 + 1000); // randomise interval, +1 second to affect low intensity values + position = -(radius << 8); // reset to start position (out of frame) + } + SEGENV.step = (uint32_t)position; // save back } - SEGENV.step = (uint32_t)position; // save back - } - if (SEGMENT.check2) - position = (SEGLEN << 8) - position; // invert position (and direction) + if (SEGMENT.check2) + position = (SEGLEN << 8) - position; // invert position (and direction) + } else { + position = (SEGLEN << 7); // at speed=0, make it static in the center (this enables to use modulators only) + } for (int i = 0; i < SEGLEN; i++) { uint32_t dist = abs(position - (i << 8)); - if (dist <= (radius << 8)) { + if (dist < (radius << 8)) { uint32_t color = SEGMENT.color_from_palette(i * 255 / SEGLEN, false, false, 0); - uint8_t blend = (dist * 255) / (radius << 8); // linear gradient + uint8_t blend = dist / radius; // linear gradient note: dist is in subpixels, radius in pixels, result is [0, 255] since dist < radius*256 if (SEGMENT.custom2) { uint8_t modVal; // modulation value if (SEGMENT.check1) {