# Architecture Details — Amirine Cosplay Lights For anyone who wants to understand or modify how the project works. SPDX-License-Identifier: BSD-2-Clause --- ## How show playback works The Arduino sketch maintains a small amount of playback state: | Variable | Type | What it stores | |---|---|---| | `s_show_index` | `uint8_t` | Which show is active (index into `SHOWS[]`) | | `s_show` | `ShowDef` | Active show descriptor (read from PROGMEM once on load) | | `s_step_index` | `uint16_t` | Current step within the active show | | `s_step_start` | `uint32_t` | `millis()` when the current step began | | `s_from_color` | `CRGB` | Color the strip was showing when this step started | On every `loop()` call (~60 times per second): 1. Check the button for events; switch show if needed (`load_show()`). 2. Read the current step from `SHOW[].steps` in PROGMEM. 3. Compute progress `t` (0–255) as `elapsed * 255 / duration_ms`, clamped. 4. Blend `s_from_color` toward the target color using FastLED's `blend()`. 5. Push the blended color to the LEDs. 6. If `t == 255`, advance to the next step. For `SHOW_LOOP` shows this wraps; for `SHOW_SINGLE` shows this loads the next show. To **hold** a color, add two steps with the same color — the second transition is from the color to itself, which is invisible. ### Why PROGMEM? The Arduino Uno has only 2KB of RAM. Storing all show data in normal arrays would exhaust it quickly. `PROGMEM` stores data in flash memory (32KB), which the Uno has much more of. Both the `Step` arrays and the `ShowDef` index (`SHOWS[]`) live in PROGMEM; the `read_step()` and `read_show_def()` helpers in `lightshow_format.h` extract values safely. Each `Step` is 5 bytes (3 bytes RGB + 2 bytes duration). A `ShowDef` is 6 bytes (2-byte pointer + 2-byte length + 1-byte mode + 1 byte padding). The sketch currently uses roughly 25% of flash — there is room for many more shows. --- ## How the button works Button handling uses the [OneButton](https://github.com/mathertel/OneButton) library. A `OneButton` instance is created in `cosplay_lights.ino` with callbacks attached in `setup()`: - **Single tap** → next show - **Double tap** → previous show - **Triple tap** → lights off; any subsequent tap or hold resumes the current show - **Long press** → reset to show 0 The key timing values (configured via `setClickMs` and `setPressMs`): | Setting | Value | Effect | |---------|-------|--------| | `setClickMs(400)` | 400ms | Single tap confirmed after 400ms of silence (to distinguish from a double tap) | | `setPressMs(800)` | 800ms | Hold duration before long press fires | The button is polled every `loop()` iteration (not just once per frame), so it responds immediately even during the 16ms LED update window. OneButton handles debouncing internally. --- ## File generation `shows.h` and `show_*.h` are **generated files** — they are the output of `converter/convert_all.py`. The source of truth is the `.txt` files in `converter/shows/`. The generation pipeline: 1. Reads every `.txt` in `converter/shows/`. 2. Converts `blue_breath.txt` first (always index 0), then all others alphabetically. 3. Writes one `show_.h` per file into `arduino/cosplay_lights/`. 4. Regenerates `shows.h` with updated `#include` lines and `SHOWS[]` table. Running `make shows` triggers this. `make upload` runs `make shows` first automatically. --- ## Adding a new LED pattern The current `ACTIVE_PATTERN` is `PATTERN_SOLID` — all LEDs show the same blended color. To add, for example, a chase pattern: **Step 1** — Add a `#define` in `config.h`: ```cpp #define PATTERN_SOLID 0 #define PATTERN_CHASE 1 #define ACTIVE_PATTERN PATTERN_CHASE ``` **Step 2** — Add the pattern logic in `led_controller.cpp`: ```cpp #elif ACTIVE_PATTERN == PATTERN_CHASE // One lit LED chases along the strip using the blended show color. static uint16_t head = 0; fill_solid(leds, NUM_LEDS, CRGB::Black); leds[head] = color; head = (head + 1) % NUM_LEDS; ``` The `color` parameter is always the show's current blended color, so patterns automatically follow lightshow transitions. --- ## Changing the LED strip type Edit `config.h`: ```cpp // WS2812B (default) #define LED_TYPE WS2812B #define COLOR_ORDER GRB #define LED_DATA_PIN 5 // APA102 / Dotstar (two-wire) // #define LED_TYPE APA102 // #define COLOR_ORDER BGR // #define LED_DATA_PIN 5 // #define LED_CLOCK_PIN 7 ``` For APA102, also change the `FastLED.addLeds` call in `led_controller.cpp` to the two-pin form: ```cpp FastLED.addLeds(leds, NUM_LEDS); ``` --- ## Multiple strips To drive two strips with the same show, add a second array and register it in `led_controller.cpp`: ```cpp static CRGB leds_a[NUM_LEDS]; static CRGB leds_b[NUM_LEDS]; void leds_begin() { FastLED.addLeds(leds_a, NUM_LEDS); FastLED.addLeds(leds_b, NUM_LEDS); FastLED.setBrightness(MAX_BRIGHTNESS); FastLED.clear(true); } void leds_apply_color(CRGB color) { fill_solid(leds_a, NUM_LEDS, color); fill_solid(leds_b, NUM_LEDS, color); } ``` `FastLED.show()` pushes all registered strips at once — `leds_show()` needs no changes. --- ## Power budget reference | Scenario | Current draw (approx.) | |----------|----------------------| | 60 LEDs, full white, brightness 255 | ~3.6A | | 60 LEDs, full white, brightness 150 | ~2.1A | | 60 LEDs, single color, brightness 150 | ~0.7A | | 60 LEDs, all off | ~20mA (Arduino only) | Always use a 5V supply rated for at least **1A more** than your calculated draw, and add a 1000µF capacitor on the supply lines near the strip connector.