From cba4b1789f9bfe56251e962405d15b34d653b0ca Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 13:28:12 +0200 Subject: [PATCH] =?UTF-8?q?feat(01-03):=208=20animation=20implementations?= =?UTF-8?q?=20=E2=80=94=20Chase,=20Pulse,=20Rainbow,=20Strobe,=20Color=20W?= =?UTF-8?q?ash,=20Breathe,=20Sparkle,=20Gradient=20Sweep?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - chase.cpp: moving window of ledCount/10 pixels, speed scales position advance per tick - pulse.cpp: sine wave brightness oscillation on solid palette color - rainbow.cpp: HSV rainbow via HsbColor, hue offset advances with tick, no palette (generates own) - strobe.cpp: period = 20/speed ticks, on/off alternation, high speed = rapid flash - color_wash.cpp: linear blend between palette colors, hold_ticks = 50/speed - breathe.cpp: sine envelope, 5% min brightness (never fully off, D-11), slower tempo than pulse - sparkle.cpp: dim base + random bright pixels via esp_random() hardware RNG - gradient_sweep.cpp: 2-color gradient sweeps across strip, offset advances with tick - All 8 handle isRGBW branch, use params.speed/intensity/colors[], call applyBrightnessW/R --- firmware/src/animations/breathe.cpp | 33 ++++++++++++++ firmware/src/animations/chase.cpp | 52 ++++++++++++++++++++++ firmware/src/animations/color_wash.cpp | 45 +++++++++++++++++++ firmware/src/animations/gradient_sweep.cpp | 44 ++++++++++++++++++ firmware/src/animations/pulse.cpp | 29 ++++++++++++ firmware/src/animations/rainbow.cpp | 32 +++++++++++++ firmware/src/animations/sparkle.cpp | 50 +++++++++++++++++++++ firmware/src/animations/strobe.cpp | 37 +++++++++++++++ 8 files changed, 322 insertions(+) create mode 100644 firmware/src/animations/breathe.cpp create mode 100644 firmware/src/animations/chase.cpp create mode 100644 firmware/src/animations/color_wash.cpp create mode 100644 firmware/src/animations/gradient_sweep.cpp create mode 100644 firmware/src/animations/pulse.cpp create mode 100644 firmware/src/animations/rainbow.cpp create mode 100644 firmware/src/animations/sparkle.cpp create mode 100644 firmware/src/animations/strobe.cpp diff --git a/firmware/src/animations/breathe.cpp b/firmware/src/animations/breathe.cpp new file mode 100644 index 0000000..00698c3 --- /dev/null +++ b/firmware/src/animations/breathe.cpp @@ -0,0 +1,33 @@ +#include +#include "animation_base.h" +#include "led_driver.h" +#include "config.h" +#include "animation_engine.h" +#include + +// --------------------------------------------------------------------------- +// Breathe: all LEDs set to palette color[0], brightness rises and falls. +// Uses sine envelope, slower default tempo than Pulse. +// envelope = (sin(tick * speed * 0.05) + 1.0) * 0.5 (range 0–1) +// Minimum brightness = 5% to create "alive" look (per D-11 — never fully off). +// --------------------------------------------------------------------------- +void animBreathe(uint32_t tick, const AnimParams& params, uint16_t ledCount, bool isRGBW) { + float envelope = (sinf((float)tick * params.speed * 0.05f) + 1.0f) * 0.5f; + // Minimum 5% brightness — never fully off (D-11: "alive" look) + float bm = 0.05f + envelope * 0.95f; + float bright = bm * params.intensity * g_brightness; + + uint8_t r = params.colorCount > 0 ? params.colors[0][0] : 0; + uint8_t g = params.colorCount > 0 ? params.colors[0][1] : 60; + uint8_t b = params.colorCount > 0 ? params.colors[0][2] : 120; + uint8_t w = params.colorCount > 0 ? params.colors[0][3] : 0; + + for (uint16_t i = 0; i < ledCount; i++) { + if (isRGBW) { + uint8_t ww = (w == 0) ? autoWhite(r, g, b) : w; + stripWand.SetPixelColor(i, applyBrightnessW(RgbwColor(r, g, b, ww), bright)); + } else { + stripSchrank.SetPixelColor(i, applyBrightnessR(RgbColor(r, g, b), bright)); + } + } +} diff --git a/firmware/src/animations/chase.cpp b/firmware/src/animations/chase.cpp new file mode 100644 index 0000000..e0960dd --- /dev/null +++ b/firmware/src/animations/chase.cpp @@ -0,0 +1,52 @@ +#include +#include "animation_base.h" +#include "led_driver.h" +#include "config.h" +#include "animation_engine.h" + +// --------------------------------------------------------------------------- +// Chase: a window of pixels moves forward along the strip. +// Window size: ledCount / 10 (10% of strip). +// Speed: higher params.speed = more positions skipped per tick. +// --------------------------------------------------------------------------- +void animChase(uint32_t tick, const AnimParams& params, uint16_t ledCount, bool isRGBW) { + // speed_factor: how many positions to advance per tick + uint32_t speedFactor = (uint32_t)(params.speed * 5.0f); + if (speedFactor < 1) speedFactor = 1; + + uint16_t windowSize = ledCount / 10; + if (windowSize < 1) windowSize = 1; + + uint16_t pos = (uint32_t)(tick * speedFactor) % ledCount; + + float bright = params.intensity * g_brightness; + + // Color from palette (wrap with modulo) + uint8_t ci = 0; // chase uses single palette color + uint8_t r = params.colors[ci][0]; + uint8_t g = params.colors[ci][1]; + uint8_t b = params.colors[ci][2]; + uint8_t w = params.colors[ci][3]; + + for (uint16_t i = 0; i < ledCount; i++) { + // Check if LED is within the window (handle wrap-around) + bool inWindow = false; + if (pos + windowSize <= ledCount) { + inWindow = (i >= pos && i < pos + windowSize); + } else { + // Window wraps around strip end + inWindow = (i >= pos || i < (pos + windowSize) % ledCount); + } + + if (isRGBW) { + uint8_t ww = (w == 0) ? autoWhite(r, g, b) : w; + RgbwColor c = inWindow ? applyBrightnessW(RgbwColor(r, g, b, ww), bright) + : RgbwColor(0, 0, 0, 0); + stripWand.SetPixelColor(i, c); + } else { + RgbColor c = inWindow ? applyBrightnessR(RgbColor(r, g, b), bright) + : RgbColor(0, 0, 0); + stripSchrank.SetPixelColor(i, c); + } + } +} diff --git a/firmware/src/animations/color_wash.cpp b/firmware/src/animations/color_wash.cpp new file mode 100644 index 0000000..0ac10d6 --- /dev/null +++ b/firmware/src/animations/color_wash.cpp @@ -0,0 +1,45 @@ +#include +#include "animation_base.h" +#include "led_driver.h" +#include "config.h" +#include "animation_engine.h" + +// --------------------------------------------------------------------------- +// Color Wash: solid fill that fades between palette colors. +// Each color is held for hold_ticks = (uint32_t)(50.0f / speed) ticks. +// Linear blend: blends from palette[i] to palette[(i+1)%colorCount]. +// --------------------------------------------------------------------------- +void animColorWash(uint32_t tick, const AnimParams& params, uint16_t ledCount, bool isRGBW) { + float bright = params.intensity * g_brightness; + uint8_t colorCount = params.colorCount > 0 ? params.colorCount : 1; + + float speed = (params.speed > 0.0f) ? params.speed : 0.01f; + uint32_t holdTicks = (uint32_t)(50.0f / speed); + if (holdTicks < 1) holdTicks = 1; + + uint32_t totalTicks = (uint32_t)holdTicks * colorCount; + uint32_t phase = tick % totalTicks; + uint8_t ci = (uint8_t)(phase / holdTicks) % colorCount; + uint8_t next = (ci + 1) % colorCount; + float t = (float)(phase % holdTicks) / (float)holdTicks; + + uint8_t r1 = params.colors[ci][0], g1 = params.colors[ci][1]; + uint8_t b1 = params.colors[ci][2], w1 = params.colors[ci][3]; + uint8_t r2 = params.colors[next][0], g2 = params.colors[next][1]; + uint8_t b2 = params.colors[next][2], w2 = params.colors[next][3]; + + // Linear interpolation + uint8_t r = (uint8_t)(r1 + (r2 - (int)r1) * t); + uint8_t g = (uint8_t)(g1 + (g2 - (int)g1) * t); + uint8_t b = (uint8_t)(b1 + (b2 - (int)b1) * t); + uint8_t w = (uint8_t)(w1 + (w2 - (int)w1) * t); + + for (uint16_t i = 0; i < ledCount; i++) { + if (isRGBW) { + uint8_t ww = (w == 0) ? autoWhite(r, g, b) : w; + stripWand.SetPixelColor(i, applyBrightnessW(RgbwColor(r, g, b, ww), bright)); + } else { + stripSchrank.SetPixelColor(i, applyBrightnessR(RgbColor(r, g, b), bright)); + } + } +} diff --git a/firmware/src/animations/gradient_sweep.cpp b/firmware/src/animations/gradient_sweep.cpp new file mode 100644 index 0000000..72a0582 --- /dev/null +++ b/firmware/src/animations/gradient_sweep.cpp @@ -0,0 +1,44 @@ +#include +#include "animation_base.h" +#include "led_driver.h" +#include "config.h" +#include "animation_engine.h" + +// --------------------------------------------------------------------------- +// Gradient Sweep: smooth gradient between palette colors[0] and colors[1] +// sweeps across the strip. Offset advances with tick. +// offset = (tick * speed_factor) % ledCount +// At each position i: blend = ((i + offset) % ledCount) / (float)ledCount +// --------------------------------------------------------------------------- +void animGradientSweep(uint32_t tick, const AnimParams& params, uint16_t ledCount, bool isRGBW) { + float bright = params.intensity * g_brightness; + + uint32_t speedFactor = (uint32_t)(params.speed * 3.0f); + if (speedFactor < 1) speedFactor = 1; + uint32_t offset = (tick * speedFactor) % ledCount; + + // Palette: use colors[0] and colors[1] (fallback to colors[0] if only 1 color) + uint8_t n = params.colorCount > 0 ? params.colorCount : 1; + uint8_t r0 = params.colors[0][0], g0 = params.colors[0][1]; + uint8_t b0 = params.colors[0][2], w0 = params.colors[0][3]; + uint8_t r1 = params.colors[n > 1 ? 1 : 0][0]; + uint8_t g1 = params.colors[n > 1 ? 1 : 0][1]; + uint8_t b1 = params.colors[n > 1 ? 1 : 0][2]; + uint8_t w1 = params.colors[n > 1 ? 1 : 0][3]; + + for (uint16_t i = 0; i < ledCount; i++) { + float blend = (float)((i + offset) % ledCount) / (float)ledCount; + + uint8_t r = (uint8_t)(r0 + (r1 - (int)r0) * blend); + uint8_t g = (uint8_t)(g0 + (g1 - (int)g0) * blend); + uint8_t b = (uint8_t)(b0 + (b1 - (int)b0) * blend); + uint8_t w = (uint8_t)(w0 + (w1 - (int)w0) * blend); + + if (isRGBW) { + uint8_t ww = (w == 0) ? autoWhite(r, g, b) : w; + stripWand.SetPixelColor(i, applyBrightnessW(RgbwColor(r, g, b, ww), bright)); + } else { + stripSchrank.SetPixelColor(i, applyBrightnessR(RgbColor(r, g, b), bright)); + } + } +} diff --git a/firmware/src/animations/pulse.cpp b/firmware/src/animations/pulse.cpp new file mode 100644 index 0000000..578cf04 --- /dev/null +++ b/firmware/src/animations/pulse.cpp @@ -0,0 +1,29 @@ +#include +#include "animation_base.h" +#include "led_driver.h" +#include "config.h" +#include "animation_engine.h" +#include + +// --------------------------------------------------------------------------- +// Pulse: all LEDs set to palette color[0], brightness pulses as sine wave. +// brightness_mult = sin(tick * speed * 0.1) * 0.5 + 0.5 (range 0–1) +// --------------------------------------------------------------------------- +void animPulse(uint32_t tick, const AnimParams& params, uint16_t ledCount, bool isRGBW) { + float bm = sinf((float)tick * params.speed * 0.1f) * 0.5f + 0.5f; + float bright = bm * params.intensity * g_brightness; + + uint8_t r = params.colorCount > 0 ? params.colors[0][0] : 255; + uint8_t g = params.colorCount > 0 ? params.colors[0][1] : 255; + uint8_t b = params.colorCount > 0 ? params.colors[0][2] : 255; + uint8_t w = params.colorCount > 0 ? params.colors[0][3] : 0; + + for (uint16_t i = 0; i < ledCount; i++) { + if (isRGBW) { + uint8_t ww = (w == 0) ? autoWhite(r, g, b) : w; + stripWand.SetPixelColor(i, applyBrightnessW(RgbwColor(r, g, b, ww), bright)); + } else { + stripSchrank.SetPixelColor(i, applyBrightnessR(RgbColor(r, g, b), bright)); + } + } +} diff --git a/firmware/src/animations/rainbow.cpp b/firmware/src/animations/rainbow.cpp new file mode 100644 index 0000000..9cb1004 --- /dev/null +++ b/firmware/src/animations/rainbow.cpp @@ -0,0 +1,32 @@ +#include +#include "animation_base.h" +#include "led_driver.h" +#include "config.h" +#include "animation_engine.h" + +// --------------------------------------------------------------------------- +// Rainbow: HSV rainbow spread across all LEDs, offset advances with tick. +// Ignores palette — generates its own rainbow colors via HsbColor. +// hue = (i * 65535 / ledCount + tick * speed_factor) % 65536 +// --------------------------------------------------------------------------- +void animRainbow(uint32_t tick, const AnimParams& params, uint16_t ledCount, bool isRGBW) { + float bright = params.intensity * g_brightness; + uint32_t speedFactor = (uint32_t)(params.speed * 500.0f); + uint32_t offset = (tick * speedFactor); + + for (uint16_t i = 0; i < ledCount; i++) { + uint32_t hueRaw = ((uint32_t)i * 65535u / ledCount + offset) % 65536u; + float hue = (float)hueRaw / 65535.0f; // 0.0–1.0 + + // HsbColor: Hue [0,1], Saturation [0,1], Brightness [0,1] + HsbColor hsb(hue, 1.0f, bright); + RgbColor rgb(hsb); + + if (isRGBW) { + // Rainbow on RGBW: no white channel (purely saturated) + stripWand.SetPixelColor(i, RgbwColor(rgb.R, rgb.G, rgb.B, 0)); + } else { + stripSchrank.SetPixelColor(i, rgb); + } + } +} diff --git a/firmware/src/animations/sparkle.cpp b/firmware/src/animations/sparkle.cpp new file mode 100644 index 0000000..8ab37e7 --- /dev/null +++ b/firmware/src/animations/sparkle.cpp @@ -0,0 +1,50 @@ +#include +#include "animation_base.h" +#include "led_driver.h" +#include "config.h" +#include "animation_engine.h" +#include + +// --------------------------------------------------------------------------- +// Sparkle: random LEDs flash bright palette color briefly. +// All pixels set to dim base color each tick, then sparkle pixels set bright. +// sparkle_count = (uint32_t)(ledCount * intensity * 0.1) +// Uses esp_random() for ESP32 hardware RNG. +// --------------------------------------------------------------------------- +void animSparkle(uint32_t tick, const AnimParams& params, uint16_t ledCount, bool isRGBW) { + float bright = params.intensity * g_brightness; + float dimFactor = bright * 0.15f; // dim base — 15% of bright + + uint8_t r = params.colorCount > 0 ? params.colors[0][0] : 255; + uint8_t g = params.colorCount > 0 ? params.colors[0][1] : 255; + uint8_t b = params.colorCount > 0 ? params.colors[0][2] : 255; + uint8_t w = params.colorCount > 0 ? params.colors[0][3] : 0; + + // Set all pixels to dim base color + if (isRGBW) { + uint8_t ww = (w == 0) ? autoWhite(r, g, b) : w; + RgbwColor dimColor = applyBrightnessW(RgbwColor(r, g, b, ww), dimFactor); + for (uint16_t i = 0; i < ledCount; i++) { + stripWand.SetPixelColor(i, dimColor); + } + } else { + RgbColor dimColor = applyBrightnessR(RgbColor(r, g, b), dimFactor); + for (uint16_t i = 0; i < ledCount; i++) { + stripSchrank.SetPixelColor(i, dimColor); + } + } + + // Light up sparkle pixels bright + uint32_t sparkleCount = (uint32_t)((float)ledCount * params.intensity * 0.1f); + if (sparkleCount < 1) sparkleCount = 1; + + for (uint32_t s = 0; s < sparkleCount; s++) { + uint16_t pos = esp_random() % ledCount; + if (isRGBW) { + uint8_t ww = (w == 0) ? autoWhite(r, g, b) : w; + stripWand.SetPixelColor(pos, applyBrightnessW(RgbwColor(r, g, b, ww), bright)); + } else { + stripSchrank.SetPixelColor(pos, applyBrightnessR(RgbColor(r, g, b), bright)); + } + } +} diff --git a/firmware/src/animations/strobe.cpp b/firmware/src/animations/strobe.cpp new file mode 100644 index 0000000..6984419 --- /dev/null +++ b/firmware/src/animations/strobe.cpp @@ -0,0 +1,37 @@ +#include +#include "animation_base.h" +#include "led_driver.h" +#include "config.h" +#include "animation_engine.h" + +// --------------------------------------------------------------------------- +// Strobe: all LEDs alternate between palette color[0] and black. +// Period = (uint32_t)(20.0f / params.speed) ticks. +// On for half period, off for half period. High speed = rapid flash. +// --------------------------------------------------------------------------- +void animStrobe(uint32_t tick, const AnimParams& params, uint16_t ledCount, bool isRGBW) { + float speed = (params.speed > 0.0f) ? params.speed : 0.01f; + uint32_t period = (uint32_t)(20.0f / speed); + if (period < 2) period = 2; + + bool on = (tick % period) < (period / 2); + float bright = params.intensity * g_brightness; + + uint8_t r = params.colorCount > 0 ? params.colors[0][0] : 255; + uint8_t g = params.colorCount > 0 ? params.colors[0][1] : 255; + uint8_t b = params.colorCount > 0 ? params.colors[0][2] : 255; + uint8_t w = params.colorCount > 0 ? params.colors[0][3] : 0; + + for (uint16_t i = 0; i < ledCount; i++) { + if (isRGBW) { + uint8_t ww = (w == 0) ? autoWhite(r, g, b) : w; + RgbwColor c = on ? applyBrightnessW(RgbwColor(r, g, b, ww), bright) + : RgbwColor(0, 0, 0, 0); + stripWand.SetPixelColor(i, c); + } else { + RgbColor c = on ? applyBrightnessR(RgbColor(r, g, b), bright) + : RgbColor(0, 0, 0); + stripSchrank.SetPixelColor(i, c); + } + } +}