Initial commit — Amirine Cosplay Lights

Arduino Uno + WS2812B LED strip controller with a text-based lightshow
system. Shows are defined as .txt files (hex color + fade duration per step),
converted to PROGMEM headers by convert_all.py, and navigated at runtime
via a debounced button (tap/double-tap/hold). BSD 2-Clause license.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-21 10:16:56 +02:00
commit 11eb2584ef
24 changed files with 1320 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
/*
* Amirine Cosplay Lights
*
* Plays lightshows defined in shows.h on a WS2812B LED strip.
* A single button navigates between shows:
* 1 tap — next show
* 2 taps — previous show
* hold — reset to show 0 (slow blue pulse)
*
* To add or update shows:
* 1. Add or edit a .txt file in converter/shows/
* 2. Run: make shows
* 3. Run: make upload
*
* Required library: FastLED (Sketch > Include Library > Manage Libraries)
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "config.h"
#include "led_controller.h"
#include "button.h"
#include "shows.h"
// Milliseconds between LED updates. 16ms ≈ 60 refreshes/second.
#define UPDATE_INTERVAL_MS 16
// ---- Color math --------------------------------------------------------
// Interpolate a single 8-bit channel from a to b by ratio t (0.0 1.0).
static uint8_t lerp_channel(uint8_t a, uint8_t b, float t) {
return (uint8_t)(a + (b - a) * t);
}
// Blend two colors: t=0.0 returns 'from', t=1.0 returns 'to'.
static CRGB blend_colors(CRGB from, CRGB to, float t) {
return CRGB(
lerp_channel(from.r, to.r, t),
lerp_channel(from.g, to.g, t),
lerp_channel(from.b, to.b, t)
);
}
// ---- Playback state ----------------------------------------------------
static uint8_t s_show_index = 0; // which show is active (index into SHOWS[])
static ShowDef s_show; // active show, read from PROGMEM once on load
static uint16_t s_step_index = 0; // current step within the active show
static uint32_t s_step_start = 0; // millis() when this step began
static CRGB s_from_color = CRGB::Black; // color at the start of this transition
// Load show at 'index', resetting playback to the first step.
static void load_show(uint8_t index) {
s_show_index = index;
s_show = read_show_def(&SHOWS[index]);
s_step_index = 0;
s_step_start = millis();
s_from_color = CRGB::Black;
}
// How far through the current step we are (0.0 1.0).
// Returns 1.0 immediately for instant steps (duration_ms == 0).
static float step_progress(const Step& step) {
if (step.duration_ms == 0) return 1.0f;
uint32_t elapsed = millis() - s_step_start;
float t = (float)elapsed / step.duration_ms;
return (t < 1.0f) ? t : 1.0f;
}
// Complete the current step and advance to the next (loops at end of show).
static void advance_step(CRGB reached_color) {
s_from_color = reached_color;
s_step_index = (s_step_index + 1) % s_show.length;
s_step_start = millis();
}
// ---- Arduino entry points ----------------------------------------------
void setup() {
leds_begin();
button_begin(BUTTON_PIN);
load_show(0); // start on show 0 — slow blue pulse
}
void loop() {
// ---- Button navigation ----
ButtonEvent evt = button_update();
if (evt == BTN_TAP) {
// Next show, wrapping around to 0 after the last one.
load_show((s_show_index + 1) % SHOW_COUNT);
}
if (evt == BTN_DOUBLE_TAP) {
// Previous show, wrapping around to the last one from show 0.
load_show((s_show_index + SHOW_COUNT - 1) % SHOW_COUNT);
}
if (evt == BTN_HOLD) {
// Reset to show 0 (blue pulse) from any position.
load_show(0);
}
// ---- Playback ----
Step step = read_step(&s_show.steps[s_step_index]);
CRGB to = CRGB(step.r, step.g, step.b);
float t = step_progress(step);
leds_apply_color(blend_colors(s_from_color, to, t));
leds_show();
if (t >= 1.0f) {
advance_step(to);
}
delay(UPDATE_INTERVAL_MS);
}