From 2c1fbe103b0cdbc455a2f7b9c171bd077f4481af Mon Sep 17 00:00:00 2001 From: TripleWhy Date: Mon, 30 Oct 2023 17:05:27 +0100 Subject: [PATCH 1/6] Spookier version of Halloween Eyes --- wled00/FX.cpp | 148 ++++++++++++++++++++++++++++++++++------------ wled00/FX_fcn.cpp | 2 +- 2 files changed, 110 insertions(+), 40 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 188022b03..afe0be43c 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -2600,9 +2600,27 @@ uint16_t mode_twinklecat() static const char _data_FX_MODE_TWINKLECAT[] PROGMEM = "Twinklecat@!,Twinkle rate,,,,Cool;;!"; -//inspired by https://www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/#LEDStripEffectBlinkingHalloweenEyes uint16_t mode_halloween_eyes() { + enum class eyeState : uint8_t { + initializeOn = 0, + on, + blink, + initializeOff, + off, + + count + }; + struct EyeData { + eyeState state; + uint8 color; + uint16_t startPos; + uint16_t duration; + uint32_t startTime; + uint8_t blinkDuration; + uint32_t blinkStartTime; + }; + if (SEGLEN == 1) return mode_static(); const uint16_t maxWidth = strip.isMatrix ? SEGMENT.virtualWidth() : SEGLEN; const uint16_t HALLOWEEN_EYE_SPACE = MAX(2, strip.isMatrix ? SEGMENT.virtualWidth()>>4: SEGLEN>>5); @@ -2610,57 +2628,109 @@ uint16_t mode_halloween_eyes() uint16_t eyeLength = (2*HALLOWEEN_EYE_WIDTH) + HALLOWEEN_EYE_SPACE; if (eyeLength >= maxWidth) return mode_static(); //bail if segment too short + if (!SEGENV.allocateData(sizeof(EyeData))) return mode_static(); //allocation failed + EyeData& data = *reinterpret_cast(SEGENV.data); + if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); //fill background - uint8_t state = SEGENV.aux1 >> 8; - uint16_t stateTime = SEGENV.call; - if (stateTime == 0) stateTime = 2000; + data.state = static_cast(static_cast(data.state) % static_cast(eyeState::count)); + uint16_t duration = data.duration; + duration = max(uint16_t{1u}, duration); + const uint32_t elapsedTime = strip.now - data.startTime; - if (state == 0) { //spawn eyes - SEGENV.aux0 = random16(0, maxWidth - eyeLength - 1); //start pos - SEGENV.aux1 = random8(); //color - if (strip.isMatrix) SEGMENT.offset = random16(SEGMENT.virtualHeight()-1); // a hack: reuse offset since it is not used in matrices - state = 1; - } + switch (data.state) { + case eyeState::initializeOn: { + data.startPos = random16(0, maxWidth - eyeLength - 1); + data.color = random8(); + if (strip.isMatrix) SEGMENT.offset = random16(SEGMENT.virtualHeight()-1); // a hack: reuse offset since it is not used in matrices + duration = 128u + random16(SEGMENT.intensity*64u); + data.duration = duration; + data.state = eyeState::on; + [[fallthrough]]; + } + case eyeState::on: { + uint16_t start2ndEye = data.startPos + HALLOWEEN_EYE_WIDTH + HALLOWEEN_EYE_SPACE; + duration = min(duration, static_cast(128u + (SEGMENT.intensity*64u))); - if (state < 2) { //fade eyes - uint16_t startPos = SEGENV.aux0; - uint16_t start2ndEye = startPos + HALLOWEEN_EYE_WIDTH + HALLOWEEN_EYE_SPACE; - - uint32_t fadestage = (strip.now - SEGENV.step)*255 / stateTime; - if (fadestage > 255) fadestage = 255; - uint32_t c = color_blend(SEGMENT.color_from_palette(SEGENV.aux1 & 0xFF, false, false, 0), SEGCOLOR(1), fadestage); - - for (int i = 0; i < HALLOWEEN_EYE_WIDTH; i++) { - if (strip.isMatrix) { - SEGMENT.setPixelColorXY(startPos + i, SEGMENT.offset, c); - SEGMENT.setPixelColorXY(start2ndEye + i, SEGMENT.offset, c); - } else { - SEGMENT.setPixelColor(startPos + i, c); - SEGMENT.setPixelColor(start2ndEye + i, c); + constexpr uint32_t minimumOnTimeBegin = 1024u; + constexpr uint32_t minimumOnTimeEnd = 1024u; + const uint32_t fadeInAnimationState = elapsedTime * static_cast(256*8) / duration; + const uint32_t backgroundColor = SEGCOLOR(1); + const uint32_t eyeColor = SEGMENT.color_from_palette(data.color, false, false, 0); + uint32_t c = eyeColor; + if (fadeInAnimationState < 256u) { + c = color_blend(backgroundColor, eyeColor, fadeInAnimationState); + } else if (elapsedTime > minimumOnTimeBegin) { + const uint32_t remainingTime = (elapsedTime >= duration) ? 0u : (duration - elapsedTime); + if (remainingTime > minimumOnTimeEnd) { + if (random8() < 4u) + { + c = backgroundColor; + data.state = eyeState::blink; + data.blinkDuration = random8(8, 128); + data.blinkStartTime = strip.now; + } + } } + + if (c != backgroundColor) { + for (int i = 0; i < HALLOWEEN_EYE_WIDTH; i++) { + if (strip.isMatrix) { + SEGMENT.setPixelColorXY(data.startPos + i, SEGMENT.offset, c); + SEGMENT.setPixelColorXY(start2ndEye + i, SEGMENT.offset, c); + } else { + SEGMENT.setPixelColor(data.startPos + i, c); + SEGMENT.setPixelColor(start2ndEye + i, c); + } + } + } + break; + } + case eyeState::blink: { + const uint32_t elapsedBlinkTime = strip.now - data.blinkStartTime; + if (elapsedBlinkTime >= data.blinkDuration) { + data.state = eyeState::on; + } + break; + } + case eyeState::initializeOff: { + const uint16_t eyeOffTimeBase = SEGMENT.speed*128u; + duration = eyeOffTimeBase + random16(eyeOffTimeBase); + data.duration = duration; + data.state = eyeState::off; + [[fallthrough]]; + } + case eyeState::off: { + const uint16_t eyeOffTimeBase = SEGMENT.speed*128u; + duration = min(duration, static_cast(2u * eyeOffTimeBase)); + break; + } + case eyeState::count: { + data.state = eyeState::initializeOn; + break; } } - if (strip.now - SEGENV.step > stateTime) { - state++; - if (state > 2) state = 0; - - if (state < 2) { - stateTime = 100 + SEGMENT.intensity*10; //eye fade time - } else { - uint16_t eyeOffTimeBase = (256 - SEGMENT.speed)*10; - stateTime = eyeOffTimeBase + random16(eyeOffTimeBase); + if (elapsedTime > duration) { + switch (data.state) { + case eyeState::initializeOn: + case eyeState::on: + case eyeState::blink: + data.state = eyeState::initializeOff; + break; + case eyeState::initializeOff: + case eyeState::off: + case eyeState::count: + default: + data.state = eyeState::initializeOn; + break; } - SEGENV.step = strip.now; - SEGENV.call = stateTime; + data.startTime = strip.now; } - SEGENV.aux1 = (SEGENV.aux1 & 0xFF) + (state << 8); //save state - return FRAMETIME; } -static const char _data_FX_MODE_HALLOWEEN_EYES[] PROGMEM = "Halloween Eyes@Duration,Eye fade time,,,,,Overlay;!,!;!;12"; +static const char _data_FX_MODE_HALLOWEEN_EYES[] PROGMEM = "Halloween Eyes@Eye off time,Eye on time,,,,,Overlay;!,!;!;12"; //Speed slider sets amount of LEDs lit, intensity sets unlit diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index dc4f32117..cd4a4f144 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1261,7 +1261,7 @@ void WS2812FX::service() { Segment::modeBlend(false); // unset semaphore } #endif - if (seg.mode != FX_MODE_HALLOWEEN_EYES) seg.call++; + seg.call++; if (seg.transitional && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition } From 3b6f499fc56ffc4394663717a688d615d34ba8fc Mon Sep 17 00:00:00 2001 From: TripleWhy Date: Thu, 2 Nov 2023 18:31:58 +0100 Subject: [PATCH 2/6] Reduce EyeData size by a few bytes --- wled00/FX.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index afe0be43c..0d340c8f9 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -2615,10 +2615,11 @@ uint16_t mode_halloween_eyes() eyeState state; uint8 color; uint16_t startPos; + // duration + endTime could theoretically be replaced by a single endTime, however we would lose + // the ability to end the animation early when the user reduces the animation time. uint16_t duration; uint32_t startTime; - uint8_t blinkDuration; - uint32_t blinkStartTime; + uint32_t blinkEndTime; }; if (SEGLEN == 1) return mode_static(); @@ -2667,8 +2668,7 @@ uint16_t mode_halloween_eyes() { c = backgroundColor; data.state = eyeState::blink; - data.blinkDuration = random8(8, 128); - data.blinkStartTime = strip.now; + data.blinkEndTime = strip.now + random8(8, 128); } } } @@ -2687,8 +2687,7 @@ uint16_t mode_halloween_eyes() break; } case eyeState::blink: { - const uint32_t elapsedBlinkTime = strip.now - data.blinkStartTime; - if (elapsedBlinkTime >= data.blinkDuration) { + if (strip.now >= data.blinkEndTime) { data.state = eyeState::on; } break; From f3831665d4689597da525e9b7c93640285a9089d Mon Sep 17 00:00:00 2001 From: TripleWhy Date: Sat, 4 Nov 2023 15:48:22 +0100 Subject: [PATCH 3/6] Fix type name --- wled00/FX.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 0d340c8f9..556da5599 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -2613,7 +2613,7 @@ uint16_t mode_halloween_eyes() }; struct EyeData { eyeState state; - uint8 color; + uint8_t color; uint16_t startPos; // duration + endTime could theoretically be replaced by a single endTime, however we would lose // the ability to end the animation early when the user reduces the animation time. From adcecf969a0ebbee69410d8d38862f074c9d3bb9 Mon Sep 17 00:00:00 2001 From: TripleWhy Date: Sat, 4 Nov 2023 15:54:21 +0100 Subject: [PATCH 4/6] Simplify casting a bit, where possible --- wled00/FX.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 556da5599..2e5689a93 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -2602,7 +2602,7 @@ static const char _data_FX_MODE_TWINKLECAT[] PROGMEM = "Twinklecat@!,Twinkle rat uint16_t mode_halloween_eyes() { - enum class eyeState : uint8_t { + enum eyeState : uint8_t { initializeOn = 0, on, blink, @@ -2634,7 +2634,7 @@ uint16_t mode_halloween_eyes() if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); //fill background - data.state = static_cast(static_cast(data.state) % static_cast(eyeState::count)); + data.state = static_cast(data.state % eyeState::count); uint16_t duration = data.duration; duration = max(uint16_t{1u}, duration); const uint32_t elapsedTime = strip.now - data.startTime; @@ -2651,11 +2651,11 @@ uint16_t mode_halloween_eyes() } case eyeState::on: { uint16_t start2ndEye = data.startPos + HALLOWEEN_EYE_WIDTH + HALLOWEEN_EYE_SPACE; - duration = min(duration, static_cast(128u + (SEGMENT.intensity*64u))); + duration = min(duration, static_cast(128u + (SEGMENT.intensity * 64u))); constexpr uint32_t minimumOnTimeBegin = 1024u; constexpr uint32_t minimumOnTimeEnd = 1024u; - const uint32_t fadeInAnimationState = elapsedTime * static_cast(256*8) / duration; + const uint32_t fadeInAnimationState = elapsedTime * uint32_t{256u * 8u} / duration; const uint32_t backgroundColor = SEGCOLOR(1); const uint32_t eyeColor = SEGMENT.color_from_palette(data.color, false, false, 0); uint32_t c = eyeColor; From 87b55656790b05a6fe9d9f7f1aec347b88bc18ae Mon Sep 17 00:00:00 2001 From: TripleWhy Date: Sat, 4 Nov 2023 16:12:10 +0100 Subject: [PATCH 5/6] Minor simplification --- wled00/FX.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 2e5689a93..53c2c8998 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -2635,8 +2635,7 @@ uint16_t mode_halloween_eyes() if (!SEGMENT.check2) SEGMENT.fill(SEGCOLOR(1)); //fill background data.state = static_cast(data.state % eyeState::count); - uint16_t duration = data.duration; - duration = max(uint16_t{1u}, duration); + uint16_t duration = max(uint16_t{1u}, data.duration); const uint32_t elapsedTime = strip.now - data.startTime; switch (data.state) { From 0925ea6f9cc973e7ec9bdfef0d6c05bbf9eddb55 Mon Sep 17 00:00:00 2001 From: TripleWhy Date: Sat, 4 Nov 2023 16:12:22 +0100 Subject: [PATCH 6/6] Add some comments --- wled00/FX.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 53c2c8998..1606f4ce5 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -2640,6 +2640,11 @@ uint16_t mode_halloween_eyes() switch (data.state) { case eyeState::initializeOn: { + // initialize the eyes-on state: + // - select eye position and color + // - select a duration + // - immediately switch to eyes on state. + data.startPos = random16(0, maxWidth - eyeLength - 1); data.color = random8(); if (strip.isMatrix) SEGMENT.offset = random16(SEGMENT.virtualHeight()-1); // a hack: reuse offset since it is not used in matrices @@ -2649,7 +2654,14 @@ uint16_t mode_halloween_eyes() [[fallthrough]]; } case eyeState::on: { + // eyes-on steate: + // - fade eyes in for some time + // - keep eyes on until the pre-selected duration is over + // - randomly switch to the blink (sub-)state, and initialize it with a blink duration (more precisely, a blink end time stamp) + // - never switch to the blink state if the animation just started or is about to end + uint16_t start2ndEye = data.startPos + HALLOWEEN_EYE_WIDTH + HALLOWEEN_EYE_SPACE; + // If the user reduces the input while in this state, limit the duration. duration = min(duration, static_cast(128u + (SEGMENT.intensity * 64u))); constexpr uint32_t minimumOnTimeBegin = 1024u; @@ -2673,6 +2685,7 @@ uint16_t mode_halloween_eyes() } if (c != backgroundColor) { + // render eyes for (int i = 0; i < HALLOWEEN_EYE_WIDTH; i++) { if (strip.isMatrix) { SEGMENT.setPixelColorXY(data.startPos + i, SEGMENT.offset, c); @@ -2686,12 +2699,19 @@ uint16_t mode_halloween_eyes() break; } case eyeState::blink: { + // eyes-on but currently blinking state: + // - wait until the blink time is over, then switch back to eyes-on + if (strip.now >= data.blinkEndTime) { data.state = eyeState::on; } break; } case eyeState::initializeOff: { + // initialize eyes-off state: + // - select a duration + // - immediately switch to eyes-off state + const uint16_t eyeOffTimeBase = SEGMENT.speed*128u; duration = eyeOffTimeBase + random16(eyeOffTimeBase); data.duration = duration; @@ -2699,17 +2719,23 @@ uint16_t mode_halloween_eyes() [[fallthrough]]; } case eyeState::off: { + // eyes-off state: + // - not much to do here + + // If the user reduces the input while in this state, limit the duration. const uint16_t eyeOffTimeBase = SEGMENT.speed*128u; duration = min(duration, static_cast(2u * eyeOffTimeBase)); break; } case eyeState::count: { + // Can't happen, not an actual state. data.state = eyeState::initializeOn; break; } } if (elapsedTime > duration) { + // The current state duration is over, switch to the next state. switch (data.state) { case eyeState::initializeOn: case eyeState::on: