feat(01-03): 8 animation implementations — Chase, Pulse, Rainbow, Strobe, Color Wash, Breathe, Sparkle, Gradient Sweep

- 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
This commit is contained in:
Claude
2026-04-03 13:28:12 +02:00
parent 273015a6eb
commit cba4b1789f
8 changed files with 322 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
#include <NeoPixelBus.h>
#include "animation_base.h"
#include "led_driver.h"
#include "config.h"
#include "animation_engine.h"
#include <math.h>
// ---------------------------------------------------------------------------
// 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 01)
// 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));
}
}
}

View File

@@ -0,0 +1,52 @@
#include <NeoPixelBus.h>
#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);
}
}
}

View File

@@ -0,0 +1,45 @@
#include <NeoPixelBus.h>
#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));
}
}
}

View File

@@ -0,0 +1,44 @@
#include <NeoPixelBus.h>
#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));
}
}
}

View File

@@ -0,0 +1,29 @@
#include <NeoPixelBus.h>
#include "animation_base.h"
#include "led_driver.h"
#include "config.h"
#include "animation_engine.h"
#include <math.h>
// ---------------------------------------------------------------------------
// 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 01)
// ---------------------------------------------------------------------------
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));
}
}
}

View File

@@ -0,0 +1,32 @@
#include <NeoPixelBus.h>
#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.01.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);
}
}
}

View File

@@ -0,0 +1,50 @@
#include <NeoPixelBus.h>
#include "animation_base.h"
#include "led_driver.h"
#include "config.h"
#include "animation_engine.h"
#include <esp_random.h>
// ---------------------------------------------------------------------------
// 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));
}
}
}

View File

@@ -0,0 +1,37 @@
#include <NeoPixelBus.h>
#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);
}
}
}