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:
33
firmware/src/animations/breathe.cpp
Normal file
33
firmware/src/animations/breathe.cpp
Normal 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 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
firmware/src/animations/chase.cpp
Normal file
52
firmware/src/animations/chase.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
firmware/src/animations/color_wash.cpp
Normal file
45
firmware/src/animations/color_wash.cpp
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
firmware/src/animations/gradient_sweep.cpp
Normal file
44
firmware/src/animations/gradient_sweep.cpp
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
firmware/src/animations/pulse.cpp
Normal file
29
firmware/src/animations/pulse.cpp
Normal 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 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
firmware/src/animations/rainbow.cpp
Normal file
32
firmware/src/animations/rainbow.cpp
Normal 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.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
firmware/src/animations/sparkle.cpp
Normal file
50
firmware/src/animations/sparkle.cpp
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
firmware/src/animations/strobe.cpp
Normal file
37
firmware/src/animations/strobe.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user