Files
Amirine_Cosplay_Lights/arduino/cosplay_lights/cosplay_lights.ino
T
bgrolleman 11eb2584ef 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>
2026-05-21 10:16:56 +02:00

114 lines
3.6 KiB
Arduino
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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);
}