Replace example shows with numbered production shows and add sparkle flag

- Rename all show .txt files with NNN_ numeric prefix so order is explicit
  and controlled by filename (001_heartbeat_red through 006_party)
- Drop HOME_SHOW special-casing from convert_all.py; show 0 is simply the
  lowest-numbered file
- Add SHOW_FLAG_SPARKLE support: shows can declare '// flags: sparkle' to
  overlay random white flashes on top of the base color each frame
- Wire sparkle into led_controller and config.h (SPARKLE_CHANCE/FRAMES)
- Replace old placeholder/example shows with the six production shows

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 13:59:25 +02:00
parent a96f378c9c
commit ab2c1b34b4
30 changed files with 205 additions and 231 deletions
+9 -1
View File
@@ -43,6 +43,14 @@
// -- Active pattern ------------------------------------------
// Controls how the current color is mapped to the LEDs.
// Only PATTERN_SOLID is included; see DETAILS.md for adding more.
// See DETAILS.md for adding more patterns.
#define PATTERN_SOLID 0
#define ACTIVE_PATTERN PATTERN_SOLID
// -- Sparkle tuning ------------------------------------------
// Applied to shows that have the SHOW_FLAG_SPARKLE flag set.
// SPARKLE_CHANCE : 0255 probability of a new sparkle each frame (~60fps).
// 80 ≈ 31%, giving roughly 23 simultaneous sparkles on 60 LEDs.
// SPARKLE_FRAMES : how many frames each sparkle stays white (1 frame ≈ 16ms).
#define SPARKLE_CHANCE 80
#define SPARKLE_FRAMES 6
+4 -3
View File
@@ -6,7 +6,7 @@
* 1 tap — next show
* 2 taps — previous show
* 3 taps — lights off (any tap or hold resumes)
* hold — reset to show 0 (blue breath)
* hold — reset to show 0 (red heartbeat)
*
* To add or update shows:
* 1. Add or edit a .txt file in converter/shows/
@@ -79,7 +79,7 @@ void setup() {
s_button.setPressMs(800);
s_button.attachClick([]() { load_show((s_show_index + 1) % SHOW_COUNT); });
s_button.attachDoubleClick([]() { load_show((s_show_index + SHOW_COUNT - 1) % SHOW_COUNT); });
s_button.attachMultiClick([]() { if (s_button.getNumberClicks() == 3) { s_lights_off = true; leds_apply_color(CRGB::Black); leds_show(); } });
s_button.attachMultiClick([]() { if (s_button.getNumberClicks() == 3) { s_lights_off = true; leds_apply_color(CRGB::Black, false); leds_show(); } });
s_button.attachLongPressStart([]() { load_show(0); });
}
@@ -97,7 +97,8 @@ void loop() {
CRGB to = CRGB(step.r, step.g, step.b);
uint8_t t8 = step_progress(step, now);
leds_apply_color(blend(s_from_color, to, t8));
bool sparkle = (s_show.flags & SHOW_FLAG_SPARKLE) != 0;
leds_apply_color(blend(s_from_color, to, t8), sparkle);
leds_show();
if (t8 == 255) advance_step(to, now);
+12 -5
View File
@@ -10,14 +10,21 @@ void leds_begin() {
FastLED.clear(true); // clear + show: strip starts fully off
}
void leds_apply_color(CRGB color) {
void leds_apply_color(CRGB color, bool sparkle) {
#if ACTIVE_PATTERN == PATTERN_SOLID
// All LEDs show the same color.
fill_solid(leds, NUM_LEDS, color);
// To add a new pattern, add a new #define in config.h and a new branch here.
// The pattern receives 'leds', 'NUM_LEDS', and the blended 'color' for this frame.
#endif
if (!sparkle) return;
static uint8_t sparkle_timer[NUM_LEDS];
if (random8() < SPARKLE_CHANCE) {
sparkle_timer[random8(NUM_LEDS)] = SPARKLE_FRAMES;
}
for (uint8_t i = 0; i < NUM_LEDS; i++) {
if (sparkle_timer[i] > 0) {
leds[i] = CRGB::White;
sparkle_timer[i]--;
}
}
}
void leds_show() {
+2 -1
View File
@@ -7,8 +7,9 @@
void leds_begin();
// Apply 'color' to all LEDs using the pattern defined in config.h (ACTIVE_PATTERN).
// If 'sparkle' is true, randomly flash individual LEDs white on top of the base fill.
// Call leds_show() afterwards to push the update to the physical strip.
void leds_apply_color(CRGB color);
void leds_apply_color(CRGB color, bool sparkle = false);
// Flush the LED buffer to the physical strip. Call once per frame after leds_apply_color().
void leds_show();
@@ -16,6 +16,9 @@
#define SHOW_LOOP 0 // repeat the show indefinitely
#define SHOW_SINGLE 1 // play once, then auto-advance to the next show
// Show flags (bitfield for ShowDef.flags).
#define SHOW_FLAG_SPARKLE 0x01 // overlay random white sparkles on this show
// One step in a lightshow: a target color and the time to reach it.
struct Step {
uint8_t r, g, b; // Target RGB color (0255 each)
@@ -27,6 +30,7 @@ struct ShowDef {
const Step* steps; // Pointer to a PROGMEM Step array
uint16_t length; // Number of steps in the array
uint8_t mode; // SHOW_LOOP or SHOW_SINGLE
uint8_t flags; // Bitfield: see SHOW_FLAG_* above
};
// Read one Step from PROGMEM into a regular struct.
@@ -47,5 +51,6 @@ inline ShowDef read_show_def(const ShowDef* ptr) {
sd.steps = (const Step*)pgm_read_word(&ptr->steps);
sd.length = pgm_read_word(&ptr->length);
sd.mode = pgm_read_byte(&ptr->mode);
sd.flags = pgm_read_byte(&ptr->flags);
return sd;
}
@@ -1,24 +1,23 @@
// Generated by convert_all.py from: 001_heartbeat.txt
// Generated by convert_all.py from: 001_heartbeat_red.txt
// Do not edit manually — edit the .txt file and run: make shows
// SPDX-License-Identifier: BSD-2-Clause
#pragma once
#include "lightshow_format.h"
const Step SHOW_001_HEARTBEAT[] PROGMEM = {
const Step SHOW_001_HEARTBEAT_RED[] PROGMEM = {
{ 5, 0, 0, 0}, // #050000, 0ms
{209, 14, 41, 120}, // #D10E29, 120ms
{ 48, 0, 0, 100}, // #300000, 100ms
{209, 14, 41, 100}, // #D10E29, 100ms
{ 5, 0, 0, 700}, // #050000, 700ms
{ 5, 0, 0, 600}, // #050000, 600ms
{209, 14, 41, 120}, // #D10E29, 120ms
{ 48, 0, 0, 100}, // #300000, 100ms
{209, 14, 41, 100}, // #D10E29, 100ms
{ 5, 0, 0, 700}, // #050000, 700ms
{ 5, 0, 0, 600}, // #050000, 600ms
{209, 14, 41, 120}, // #D10E29, 120ms
{ 48, 0, 0, 100}, // #300000, 100ms
{209, 14, 41, 100}, // #D10E29, 100ms
{ 5, 0, 0, 700}, // #050000, 700ms
{153, 40, 58, 2000}, // #99283A, 2000ms
{ 5, 0, 0, 600}, // #050000, 600ms
};
const uint16_t SHOW_001_HEARTBEAT_LENGTH = 14;
const uint16_t SHOW_001_HEARTBEAT_RED_LENGTH = 13;
@@ -1,13 +0,0 @@
// Generated by convert_all.py from: 002_breath_red.txt
// Do not edit manually — edit the .txt file and run: make shows
// SPDX-License-Identifier: BSD-2-Clause
#pragma once
#include "lightshow_format.h"
const Step SHOW_002_BREATH_RED[] PROGMEM = {
{153, 40, 58, 0}, // #99283A, 0ms
{217, 30, 30, 2500}, // #D91E1E, 2500ms
{153, 40, 58, 2500}, // #99283A, 2500ms
};
const uint16_t SHOW_002_BREATH_RED_LENGTH = 3;
@@ -0,0 +1,12 @@
// Generated by convert_all.py from: 002_heartbeat_breathe.txt
// Do not edit manually — edit the .txt file and run: make shows
// SPDX-License-Identifier: BSD-2-Clause
#pragma once
#include "lightshow_format.h"
const Step SHOW_002_HEARTBEAT_BREATHE[] PROGMEM = {
{209, 14, 41, 3600}, // #D10E29, 3600ms
{ 5, 0, 0, 3600}, // #050000, 3600ms
};
const uint16_t SHOW_002_HEARTBEAT_BREATHE_LENGTH = 2;
@@ -1,15 +1,15 @@
// Generated by convert_all.py from: blue_breath.txt
// Generated by convert_all.py from: 003_blue_sparkle.txt
// Do not edit manually — edit the .txt file and run: make shows
// SPDX-License-Identifier: BSD-2-Clause
#pragma once
#include "lightshow_format.h"
const Step SHOW_BLUE_BREATH[] PROGMEM = {
const Step SHOW_003_BLUE_SPARKLE[] PROGMEM = {
{ 5, 0, 24, 0}, // #050018, 0ms
{ 32, 32, 255, 2500}, // #2020FF, 2500ms
{ 32, 32, 255, 400}, // #2020FF, 400ms
{ 5, 0, 24, 2500}, // #050018, 2500ms
{ 5, 0, 24, 800}, // #050018, 800ms
};
const uint16_t SHOW_BLUE_BREATH_LENGTH = 5;
const uint16_t SHOW_003_BLUE_SPARKLE_LENGTH = 5;
@@ -1,12 +0,0 @@
// Generated by convert_all.py from: 003_solid_red.txt
// Do not edit manually — edit the .txt file and run: make shows
// SPDX-License-Identifier: BSD-2-Clause
#pragma once
#include "lightshow_format.h"
const Step SHOW_003_SOLID_RED[] PROGMEM = {
{153, 40, 58, 0}, // #99283A, 0ms
{153, 40, 58, 30000}, // #99283A, 30000ms
};
const uint16_t SHOW_003_SOLID_RED_LENGTH = 2;
@@ -0,0 +1,13 @@
// Generated by convert_all.py from: 004_pulse_yellow.txt
// Do not edit manually — edit the .txt file and run: make shows
// SPDX-License-Identifier: BSD-2-Clause
#pragma once
#include "lightshow_format.h"
const Step SHOW_004_PULSE_YELLOW[] PROGMEM = {
{ 32, 32, 0, 0}, // #202000, 0ms
{255, 255, 0, 1200}, // #FFFF00, 1200ms
{ 32, 32, 0, 1200}, // #202000, 1200ms
};
const uint16_t SHOW_004_PULSE_YELLOW_LENGTH = 3;
@@ -0,0 +1,12 @@
// Generated by convert_all.py from: 005_green_static.txt
// Do not edit manually — edit the .txt file and run: make shows
// SPDX-License-Identifier: BSD-2-Clause
#pragma once
#include "lightshow_format.h"
const Step SHOW_005_GREEN_STATIC[] PROGMEM = {
{ 0, 204, 0, 0}, // #00CC00, 0ms
{ 0, 204, 0, 5000}, // #00CC00, 5000ms
};
const uint16_t SHOW_005_GREEN_STATIC_LENGTH = 2;
@@ -1,11 +1,11 @@
// Generated by convert_all.py from: example_party.txt
// Generated by convert_all.py from: 006_party.txt
// Do not edit manually — edit the .txt file and run: make shows
// SPDX-License-Identifier: BSD-2-Clause
#pragma once
#include "lightshow_format.h"
const Step SHOW_EXAMPLE_PARTY[] PROGMEM = {
const Step SHOW_006_PARTY[] PROGMEM = {
{255, 0, 0, 0}, // #FF0000, 0ms
{255, 0, 0, 150}, // #FF0000, 150ms
{255, 136, 0, 0}, // #FF8800, 0ms
@@ -24,4 +24,4 @@ const Step SHOW_EXAMPLE_PARTY[] PROGMEM = {
{255, 255, 255, 150}, // #FFFFFF, 150ms
{ 0, 0, 0, 500}, // #000000, 500ms
};
const uint16_t SHOW_EXAMPLE_PARTY_LENGTH = 17;
const uint16_t SHOW_006_PARTY_LENGTH = 17;
@@ -1,16 +0,0 @@
// Generated by convert_all.py from: example_fade.txt
// Do not edit manually — edit the .txt file and run: make shows
// SPDX-License-Identifier: BSD-2-Clause
#pragma once
#include "lightshow_format.h"
const Step SHOW_EXAMPLE_FADE[] PROGMEM = {
{255, 0, 0, 0}, // #FF0000, 0ms
{ 0, 255, 0, 3000}, // #00FF00, 3000ms
{ 0, 0, 255, 0}, // #0000FF, 0ms
{255, 0, 255, 2000}, // #FF00FF, 2000ms
{255, 128, 0, 1000}, // #FF8000, 1000ms
{ 0, 0, 0, 5000}, // #000000, 5000ms
};
const uint16_t SHOW_EXAMPLE_FADE_LENGTH = 6;
@@ -1,13 +0,0 @@
// Generated by convert_all.py from: example_pulse.txt
// Do not edit manually — edit the .txt file and run: make shows
// SPDX-License-Identifier: BSD-2-Clause
#pragma once
#include "lightshow_format.h"
const Step SHOW_EXAMPLE_PULSE[] PROGMEM = {
{ 32, 0, 0, 0}, // #200000, 0ms
{255, 0, 0, 1200}, // #FF0000, 1200ms
{ 32, 0, 0, 1200}, // #200000, 1200ms
};
const uint16_t SHOW_EXAMPLE_PULSE_LENGTH = 3;
+15 -17
View File
@@ -1,10 +1,10 @@
// =====================================================================
// shows.h — Master show index.
// Generated by: make shows (converter/convert_all.py)
// Do not edit manually — add .txt files to converter/shows/ instead.
// Do not edit manually — add NNN_<name>.txt files to converter/shows/ instead.
// =====================================================================
//
// Show 0 is always the home/reset show (blue breath).
// Show 0 is the lowest-numbered .txt file (home/reset show).
// Holding the button resets back to show 0.
//
// SPDX-License-Identifier: BSD-2-Clause
@@ -13,23 +13,21 @@
#include "lightshow_format.h"
// ---- Individual show data ----------------------------------------------
#include "show_blue_breath.h"
#include "show_001_heartbeat.h"
#include "show_002_breath_red.h"
#include "show_003_solid_red.h"
#include "show_example_fade.h"
#include "show_example_party.h"
#include "show_example_pulse.h"
#include "show_001_heartbeat_red.h"
#include "show_002_heartbeat_breathe.h"
#include "show_003_blue_sparkle.h"
#include "show_004_pulse_yellow.h"
#include "show_005_green_static.h"
#include "show_006_party.h"
// ---- Show index (PROGMEM) ----------------------------------------------
const ShowDef SHOWS[] PROGMEM = {
{SHOW_BLUE_BREATH, SHOW_BLUE_BREATH_LENGTH, SHOW_LOOP}, // 0 — home show
{SHOW_001_HEARTBEAT, SHOW_001_HEARTBEAT_LENGTH, SHOW_SINGLE}, // 1
{SHOW_002_BREATH_RED, SHOW_002_BREATH_RED_LENGTH, SHOW_LOOP}, // 2
{SHOW_003_SOLID_RED, SHOW_003_SOLID_RED_LENGTH, SHOW_LOOP}, // 3
{SHOW_EXAMPLE_FADE, SHOW_EXAMPLE_FADE_LENGTH, SHOW_LOOP}, // 4
{SHOW_EXAMPLE_PARTY, SHOW_EXAMPLE_PARTY_LENGTH, SHOW_LOOP}, // 5
{SHOW_EXAMPLE_PULSE, SHOW_EXAMPLE_PULSE_LENGTH, SHOW_LOOP}, // 6
{SHOW_001_HEARTBEAT_RED, SHOW_001_HEARTBEAT_RED_LENGTH, SHOW_SINGLE, 0}, // 0 — home show
{SHOW_002_HEARTBEAT_BREATHE, SHOW_002_HEARTBEAT_BREATHE_LENGTH, SHOW_LOOP, 0}, // 1
{SHOW_003_BLUE_SPARKLE, SHOW_003_BLUE_SPARKLE_LENGTH, SHOW_LOOP, SHOW_FLAG_SPARKLE}, // 2
{SHOW_004_PULSE_YELLOW, SHOW_004_PULSE_YELLOW_LENGTH, SHOW_LOOP, 0}, // 3
{SHOW_005_GREEN_STATIC, SHOW_005_GREEN_STATIC_LENGTH, SHOW_LOOP, 0}, // 4
{SHOW_006_PARTY, SHOW_006_PARTY_LENGTH, SHOW_SINGLE, 0}, // 5
};
const uint8_t SHOW_COUNT = 7;
const uint8_t SHOW_COUNT = 6;
+34 -32
View File
@@ -10,8 +10,12 @@ Usage:
python converter/convert_all.py
(or via Makefile: make shows)
Show 0 is always 'blue_breath' — the home/reset show.
All other shows are sorted alphabetically and follow after it.
Show files must be named NNN_<name>.txt (e.g. 001_heartbeat_red.txt).
They run in numeric order; show 0 (the home/reset show) is the lowest-numbered file.
Show file directives (in comment lines):
// mode: single — play once, then advance to the next show (default: loop)
// flags: sparkle — overlay random white sparkles on this show
SPDX-License-Identifier: BSD-2-Clause
"""
@@ -27,9 +31,6 @@ ROOT_DIR = SCRIPT_DIR.parent
SHOWS_DIR = SCRIPT_DIR / "shows"
SKETCH_DIR = ROOT_DIR / "arduino" / "cosplay_lights"
# The special first show — always placed at index 0.
HOME_SHOW = "blue_breath"
# Regex to match a valid step line: #RRGGBB, duration_ms
STEP_PATTERN = re.compile(r'^\s*#([0-9A-Fa-f]{6})\s*,\s*(\d+)')
@@ -46,21 +47,29 @@ def hex_to_rgb(hex_str: str) -> tuple[int, int, int]:
return int(hex_str[0:2], 16), int(hex_str[2:4], 16), int(hex_str[4:6], 16)
def parse_show_file(filepath: Path) -> tuple[list[tuple[int, int, int, int]], str]:
def parse_show_file(filepath: Path) -> tuple[list[tuple[int, int, int, int]], str, int]:
"""
Parse a .txt show file in one pass. Returns (steps, mode_constant).
Parse a .txt show file in one pass. Returns (steps, mode_constant, flags).
steps: list of (r, g, b, duration_ms) tuples
mode_constant: 'SHOW_LOOP' or 'SHOW_SINGLE' (default SHOW_LOOP if not set)
flags: integer bitmask (0x01 = sparkle, matches SHOW_FLAG_SPARKLE in lightshow_format.h)
Raises ValueError on malformed lines or empty files.
"""
steps = []
mode = "SHOW_LOOP"
flags = 0
with open(filepath) as f:
for lineno, raw_line in enumerate(f, 1):
m = re.match(r'^\s*//\s*mode:\s*(\w+)', raw_line, re.IGNORECASE)
if m:
if re.match(r'^\s*//\s*mode:\s*(\w+)', raw_line, re.IGNORECASE):
m = re.match(r'^\s*//\s*mode:\s*(\w+)', raw_line, re.IGNORECASE)
mode = "SHOW_SINGLE" if m.group(1).lower() == "single" else "SHOW_LOOP"
continue
mf = re.match(r'^\s*//\s*flags:\s*(.+)', raw_line, re.IGNORECASE)
if mf:
flag_tokens = [t.strip().lower() for t in mf.group(1).split(",")]
if "sparkle" in flag_tokens:
flags |= 0x01
continue
line = raw_line.split("//")[0].strip()
if not line:
continue
@@ -75,7 +84,7 @@ def parse_show_file(filepath: Path) -> tuple[list[tuple[int, int, int, int]], st
steps.append((r, g, b, duration))
if not steps:
raise ValueError(f"{filepath.name}: file contains no steps.")
return steps, mode
return steps, mode, flags
def render_show_header(steps: list, source_name: str, symbol: str) -> str:
@@ -101,23 +110,23 @@ def render_show_header(steps: list, source_name: str, symbol: str) -> str:
return "\n".join(lines)
def render_shows_index(ordered: list[tuple[str, str]]) -> str:
"""Render the master shows.h index file. ordered = [(stem, mode_constant), ...]"""
includes = "\n".join(f'#include "show_{stem}.h"' for stem, _ in ordered)
def render_shows_index(ordered: list[tuple[str, str, int]]) -> str:
"""Render the master shows.h index file. ordered = [(stem, mode_constant, flags), ...]"""
includes = "\n".join(f'#include "show_{stem}.h"' for stem, _, __ in ordered)
entries = "\n".join(
f" {{{filename_to_symbol(s)}, {filename_to_symbol(s)}_LENGTH, {mode}}},"
f" {{{filename_to_symbol(s)}, {filename_to_symbol(s)}_LENGTH, {mode}, {'SHOW_FLAG_SPARKLE' if flags & 0x01 else '0'}}},"
+ (" // 0 — home show" if i == 0 else f" // {i}")
for i, (s, mode) in enumerate(ordered)
for i, (s, mode, flags) in enumerate(ordered)
)
count = len(ordered)
return f"""\
// =====================================================================
// shows.h — Master show index.
// Generated by: make shows (converter/convert_all.py)
// Do not edit manually — add .txt files to converter/shows/ instead.
// Do not edit manually — add NNN_<name>.txt files to converter/shows/ instead.
// =====================================================================
//
// Show 0 is always the home/reset show (blue breath).
// Show 0 is the lowest-numbered .txt file (home/reset show).
// Holding the button resets back to show 0.
//
// SPDX-License-Identifier: BSD-2-Clause
@@ -140,20 +149,12 @@ const uint8_t SHOW_COUNT = {count};
# ---- Main --------------------------------------------------------------
def main() -> None:
txt_files = sorted(SHOWS_DIR.glob("*.txt"), key=lambda p: p.stem.lower())
txt_files = sorted(SHOWS_DIR.glob("*.txt"), key=lambda p: p.name.lower())
if not txt_files:
print(f"No .txt files found in {SHOWS_DIR}")
sys.exit(1)
# Separate the home show from the rest; sort the rest alphabetically.
home_file = SHOWS_DIR / f"{HOME_SHOW}.txt"
other_files = [f for f in txt_files if f.stem != HOME_SHOW]
if not home_file.exists():
print(f"Warning: home show '{HOME_SHOW}.txt' not found — show 0 will be {txt_files[0].name}")
ordered_files = txt_files
else:
ordered_files = [home_file] + other_files
ordered_files = txt_files
SKETCH_DIR.mkdir(parents=True, exist_ok=True)
@@ -164,12 +165,13 @@ def main() -> None:
stem = txt_path.stem
symbol = filename_to_symbol(stem)
try:
steps, mode = parse_show_file(txt_path)
steps, mode, flags = parse_show_file(txt_path)
header = render_show_header(steps, txt_path.name, symbol)
out = SKETCH_DIR / f"show_{stem}.h"
out.write_text(header, encoding="utf-8")
print(f" OK {txt_path.name} → show_{stem}.h ({len(steps)} steps, {mode})")
converted.append((stem, mode))
flag_str = " sparkle" if flags & 0x01 else ""
print(f" OK {txt_path.name} → show_{stem}.h ({len(steps)} steps, {mode}{flag_str})")
converted.append((stem, mode, flags))
except ValueError as e:
print(f" ERR {e}")
errors.append(stem)
@@ -182,7 +184,7 @@ def main() -> None:
sys.exit(1)
# Remove stale show_*.h files no longer in the converted list.
expected = {SKETCH_DIR / f"show_{stem}.h" for stem, _ in converted}
expected = {SKETCH_DIR / f"show_{stem}.h" for stem, _, __ in converted}
for stale in SKETCH_DIR.glob("show_*.h"):
if stale not in expected:
stale.unlink()
@@ -191,7 +193,7 @@ def main() -> None:
# Regenerate shows.h
index_path = SKETCH_DIR / "shows.h"
index_path.write_text(render_shows_index(converted), encoding="utf-8")
stems = [s for s, _ in converted]
stems = [s for s, _, __ in converted]
print(f"\n OK shows.h updated ({len(converted)} shows, show 0 = {stems[0]})")
print( " Run 'make upload' to compile and send to the Arduino.")
-29
View File
@@ -1,29 +0,0 @@
// Monster Hunter cosplay — heartbeat intro
// Three heartbeats in bright red, then fades into the base color of the breathing show.
// Auto-advances to 002_breath_red when done.
//
// mode: single
// Format: #RRGGBB, duration_ms
#050000, 0 // start near black
// beat 1
#d10e29, 120 // lub — fast rise to bright red
#300000, 100 // dip between beats
#d10e29, 100 // dub
#050000, 700 // rest
// beat 2
#d10e29, 120 // lub
#300000, 100 // dip
#d10e29, 100 // dub
#050000, 700 // rest
// beat 3
#d10e29, 120 // lub
#300000, 100 // dip
#d10e29, 100 // dub
#050000, 700 // rest
// 2-second fade into the base color of the breathing show
#99283a, 2000
+23
View File
@@ -0,0 +1,23 @@
// Red Heartbeat × 2 then Red Breathing — 2 lub-dub beats, then slow breathing, repeat.
//
// mode: single
#050000, 0 // start near black
// beat 1
#d10e29, 120 // lub — fast rise to bright red
#300000, 100 // dip between beats
#d10e29, 100 // dub
#050000, 600 // rest
// beat 2
#d10e29, 120 // lub
#300000, 100 // dip
#d10e29, 100 // dub
#050000, 600 // rest into breathing
// beat 3
#d10e29, 120 // lub
#300000, 100 // dip
#d10e29, 100 // dub
#050000, 600 // rest into breathing
-10
View File
@@ -1,10 +0,0 @@
// Monster Hunter cosplay — breathing red
// Slow breathing between dark maroon (#99283a) and bright red (#d91e1e).
// One breath cycle lasts 5 seconds. Loops continuously.
//
// mode: loop
// Format: #RRGGBB, duration_ms
#99283a, 0 // snap to dim base color
#d91e1e, 2500 // breathe in — slow rise to bright red
#99283a, 2500 // breathe out — slow fall back to dim
@@ -0,0 +1,7 @@
// Red Heartbeat × 2 then Red Breathing — 2 lub-dub beats, then slow breathing, repeat.
//
// mode: loop
// breathing
#d10e29, 3600 // fade up to bright red
#050000, 3600 // fade back down to near black
+10
View File
@@ -0,0 +1,10 @@
// Blue breath with sparkle overlay
//
// flags: sparkle
// mode: loop
#050018, 0 // snap to near-black blue
#2020FF, 2500 // breathe in — slow rise to bright blue
#2020FF, 400 // hold at peak
#050018, 2500 // breathe out — slow fall to near-black
#050018, 800 // pause before next breath
-8
View File
@@ -1,8 +0,0 @@
// Monster Hunter cosplay — solid deep red
// Constant dark maroon (#99283a). Stays on until the button is pressed.
//
// mode: loop
// Format: #RRGGBB, duration_ms
#99283a, 0 // snap on instantly
#99283a, 30000 // hold (30s loop is invisible — same color each cycle)
+7
View File
@@ -0,0 +1,7 @@
// Yellow pulse — slow breathing effect
//
// mode: loop
#202000, 0 // start at dim yellow
#FFFF00, 1200 // fade up to bright yellow
#202000, 1200 // fade back down to dim yellow
+6
View File
@@ -0,0 +1,6 @@
// Solid green
//
// mode: loop
#00CC00, 0 // snap to green
#00CC00, 5000 // hold (fade from green to green is invisible)
+22
View File
@@ -0,0 +1,22 @@
// Party mode — rapid rainbow color flashes
// Plays once then auto-advances to the next show.
//
// mode: single
#FF0000, 0 // snap to red
#FF0000, 150 // hold red
#FF8800, 0 // snap to orange
#FF8800, 150 // hold orange
#FFFF00, 0 // snap to yellow
#FFFF00, 150 // hold yellow
#00FF00, 0 // snap to green
#00FF00, 150 // hold green
#00FFFF, 0 // snap to cyan
#00FFFF, 150 // hold cyan
#0000FF, 0 // snap to blue
#0000FF, 150 // hold blue
#FF00FF, 0 // snap to magenta
#FF00FF, 150 // hold magenta
#FFFFFF, 0 // snap to white
#FFFFFF, 150 // hold white
#000000, 500 // fade to off — brief pause before advancing
-12
View File
@@ -1,12 +0,0 @@
// Blue breath — home / reset show (always index 0)
// A slow, natural breathing effect in deep blue.
// Loops continuously as the default show.
//
// mode: loop
// Format: #RRGGBB, duration_ms
#050018, 0 // snap to near-black blue — sets the starting point
#2020FF, 2500 // breathe in — slow rise to bright blue
#2020FF, 400 // hold at peak
#050018, 2500 // breathe out — slow fall to near-black
#050018, 800 // pause before next breath
-12
View File
@@ -1,12 +0,0 @@
// Example: slow color cycle
// Smooth fades between colors with different speeds.
//
// Format: #RRGGBB, duration_ms
// duration_ms = time to fade FROM the previous color TO this one.
#FF0000, 0 // snap to red immediately
#00FF00, 3000 // 3 second fade to green
#0000FF, 0 // snap to blue
#FF00FF, 2000 // 2 second fade to purple
#FF8000, 1000 // 1 second fade to orange
#000000, 5000 // 5 second slow fade to off
-24
View File
@@ -1,24 +0,0 @@
// Example: party mode — rapid color flashes
//
// Trick: to HOLD a color for N ms, add a second step with the same color
// and set that step's duration to N. The "fade from red to red" is invisible.
//
// Format: #RRGGBB, duration_ms
#FF0000, 0 // snap to red
#FF0000, 150 // hold red 150ms
#FF8800, 0 // snap to orange
#FF8800, 150 // hold orange 150ms
#FFFF00, 0 // snap to yellow
#FFFF00, 150 // hold yellow 150ms
#00FF00, 0 // snap to green
#00FF00, 150 // hold green 150ms
#00FFFF, 0 // snap to cyan
#00FFFF, 150 // hold cyan 150ms
#0000FF, 0 // snap to blue
#0000FF, 150 // hold blue 150ms
#FF00FF, 0 // snap to magenta
#FF00FF, 150 // hold magenta 150ms
#FFFFFF, 0 // snap to white
#FFFFFF, 150 // hold white 150ms
#000000, 500 // fade to off — brief pause before looping
-10
View File
@@ -1,10 +0,0 @@
// Example: slow red pulse / heartbeat effect
//
// Fades from dim red to bright red and back, repeating.
// Shows how to create an organic-feeling breathing effect.
//
// Format: #RRGGBB, duration_ms
#200000, 0 // start at dim red (no fade — sets the initial state)
#FF0000, 1200 // fade up to bright red
#200000, 1200 // fade back down to dim red