Adding loop function
This commit is contained in:
@@ -5,7 +5,7 @@
|
|||||||
* A single button navigates between shows:
|
* A single button navigates between shows:
|
||||||
* 1 tap — next show
|
* 1 tap — next show
|
||||||
* 2 taps — previous show
|
* 2 taps — previous show
|
||||||
* hold — reset to show 0 (slow blue pulse)
|
* hold — reset to show 0 (blue breath)
|
||||||
*
|
*
|
||||||
* To add or update shows:
|
* To add or update shows:
|
||||||
* 1. Add or edit a .txt file in converter/shows/
|
* 1. Add or edit a .txt file in converter/shows/
|
||||||
@@ -66,10 +66,19 @@ static float step_progress(const Step& step) {
|
|||||||
return (t < 1.0f) ? t : 1.0f;
|
return (t < 1.0f) ? t : 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Complete the current step and advance to the next (loops at end of show).
|
// Complete the current step and advance to the next.
|
||||||
|
// SHOW_LOOP wraps back to step 0; SHOW_SINGLE loads the next show when the last step ends.
|
||||||
static void advance_step(CRGB reached_color) {
|
static void advance_step(CRGB reached_color) {
|
||||||
s_from_color = reached_color;
|
s_from_color = reached_color;
|
||||||
s_step_index = (s_step_index + 1) % s_show.length;
|
uint16_t next = s_step_index + 1;
|
||||||
|
if (next >= s_show.length) {
|
||||||
|
if (s_show.mode == SHOW_SINGLE) {
|
||||||
|
load_show((s_show_index + 1) % SHOW_COUNT);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
next = 0;
|
||||||
|
}
|
||||||
|
s_step_index = next;
|
||||||
s_step_start = millis();
|
s_step_start = millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +87,7 @@ static void advance_step(CRGB reached_color) {
|
|||||||
void setup() {
|
void setup() {
|
||||||
leds_begin();
|
leds_begin();
|
||||||
button_begin(BUTTON_PIN);
|
button_begin(BUTTON_PIN);
|
||||||
load_show(0); // start on show 0 — slow blue pulse
|
load_show(0); // start on show 0 — blue breath
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
@@ -93,7 +102,7 @@ void loop() {
|
|||||||
load_show((s_show_index + SHOW_COUNT - 1) % SHOW_COUNT);
|
load_show((s_show_index + SHOW_COUNT - 1) % SHOW_COUNT);
|
||||||
}
|
}
|
||||||
if (evt == BTN_HOLD) {
|
if (evt == BTN_HOLD) {
|
||||||
// Reset to show 0 (blue pulse) from any position.
|
// Reset to show 0 (blue breath) from any position.
|
||||||
load_show(0);
|
load_show(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,16 +12,21 @@
|
|||||||
#include <avr/pgmspace.h>
|
#include <avr/pgmspace.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// Show playback modes.
|
||||||
|
#define SHOW_LOOP 0 // repeat the show indefinitely
|
||||||
|
#define SHOW_SINGLE 1 // play once, then auto-advance to the next show
|
||||||
|
|
||||||
// One step in a lightshow: a target color and the time to reach it.
|
// One step in a lightshow: a target color and the time to reach it.
|
||||||
struct Step {
|
struct Step {
|
||||||
uint8_t r, g, b; // Target RGB color (0–255 each)
|
uint8_t r, g, b; // Target RGB color (0–255 each)
|
||||||
uint16_t duration_ms; // Milliseconds to fade from the previous color (0 = instant)
|
uint16_t duration_ms; // Milliseconds to fade from the previous color (0 = instant)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Descriptor for one complete show: a pointer to its PROGMEM Step array and its length.
|
// Descriptor for one complete show.
|
||||||
struct ShowDef {
|
struct ShowDef {
|
||||||
const Step* steps; // Pointer to a PROGMEM Step array
|
const Step* steps; // Pointer to a PROGMEM Step array
|
||||||
uint16_t length; // Number of steps in the array
|
uint16_t length; // Number of steps in the array
|
||||||
|
uint8_t mode; // SHOW_LOOP or SHOW_SINGLE
|
||||||
};
|
};
|
||||||
|
|
||||||
// Read one Step from PROGMEM into a regular struct.
|
// Read one Step from PROGMEM into a regular struct.
|
||||||
@@ -41,5 +46,6 @@ inline ShowDef read_show_def(const ShowDef* ptr) {
|
|||||||
ShowDef sd;
|
ShowDef sd;
|
||||||
sd.steps = (const Step*)pgm_read_word(&ptr->steps);
|
sd.steps = (const Step*)pgm_read_word(&ptr->steps);
|
||||||
sd.length = pgm_read_word(&ptr->length);
|
sd.length = pgm_read_word(&ptr->length);
|
||||||
|
sd.mode = pgm_read_byte(&ptr->mode);
|
||||||
return sd;
|
return sd;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// Generated by convert_all.py from: blue_breath.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 = {
|
||||||
|
{ 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;
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
// Generated by convert_all.py from: blue_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_BLUE_PULSE[] PROGMEM = {
|
|
||||||
{ 5, 0, 37, 0}, // #050025, 0ms
|
|
||||||
{ 40, 40, 255, 3000}, // #2828FF, 3000ms
|
|
||||||
{ 5, 0, 37, 3000}, // #050025, 3000ms
|
|
||||||
};
|
|
||||||
const uint16_t SHOW_BLUE_PULSE_LENGTH = 3;
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
// Do not edit manually — add .txt files to converter/shows/ instead.
|
// Do not edit manually — add .txt files to converter/shows/ instead.
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
//
|
//
|
||||||
// Show 0 is always the home/reset show (slow blue pulse).
|
// Show 0 is always the home/reset show (blue breath).
|
||||||
// Holding the button resets back to show 0.
|
// Holding the button resets back to show 0.
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: BSD-2-Clause
|
// SPDX-License-Identifier: BSD-2-Clause
|
||||||
@@ -13,17 +13,17 @@
|
|||||||
#include "lightshow_format.h"
|
#include "lightshow_format.h"
|
||||||
|
|
||||||
// ---- Individual show data ----------------------------------------------
|
// ---- Individual show data ----------------------------------------------
|
||||||
#include "show_blue_pulse.h"
|
#include "show_blue_breath.h"
|
||||||
#include "show_example_fade.h"
|
#include "show_example_fade.h"
|
||||||
#include "show_example_party.h"
|
#include "show_example_party.h"
|
||||||
#include "show_example_pulse.h"
|
#include "show_example_pulse.h"
|
||||||
|
|
||||||
// ---- Show index (PROGMEM) ----------------------------------------------
|
// ---- Show index (PROGMEM) ----------------------------------------------
|
||||||
const ShowDef SHOWS[] PROGMEM = {
|
const ShowDef SHOWS[] PROGMEM = {
|
||||||
{SHOW_BLUE_PULSE, SHOW_BLUE_PULSE_LENGTH}, // 0 — home show
|
{SHOW_BLUE_BREATH, SHOW_BLUE_BREATH_LENGTH, SHOW_LOOP}, // 0 — home show
|
||||||
{SHOW_EXAMPLE_FADE, SHOW_EXAMPLE_FADE_LENGTH}, // 1
|
{SHOW_EXAMPLE_FADE, SHOW_EXAMPLE_FADE_LENGTH, SHOW_LOOP}, // 1
|
||||||
{SHOW_EXAMPLE_PARTY, SHOW_EXAMPLE_PARTY_LENGTH}, // 2
|
{SHOW_EXAMPLE_PARTY, SHOW_EXAMPLE_PARTY_LENGTH, SHOW_LOOP}, // 2
|
||||||
{SHOW_EXAMPLE_PULSE, SHOW_EXAMPLE_PULSE_LENGTH}, // 3
|
{SHOW_EXAMPLE_PULSE, SHOW_EXAMPLE_PULSE_LENGTH, SHOW_LOOP}, // 3
|
||||||
};
|
};
|
||||||
|
|
||||||
const uint8_t SHOW_COUNT = 4;
|
const uint8_t SHOW_COUNT = 4;
|
||||||
|
|||||||
+32
-13
@@ -28,7 +28,7 @@ SHOWS_DIR = SCRIPT_DIR / "shows"
|
|||||||
SKETCH_DIR = ROOT_DIR / "arduino" / "cosplay_lights"
|
SKETCH_DIR = ROOT_DIR / "arduino" / "cosplay_lights"
|
||||||
|
|
||||||
# The special first show — always placed at index 0.
|
# The special first show — always placed at index 0.
|
||||||
HOME_SHOW = "blue_pulse"
|
HOME_SHOW = "blue_breath"
|
||||||
|
|
||||||
# Regex to match a valid step line: #RRGGBB, duration_ms
|
# Regex to match a valid step line: #RRGGBB, duration_ms
|
||||||
STEP_PATTERN = re.compile(r'^\s*#([0-9A-Fa-f]{6})\s*,\s*(\d+)')
|
STEP_PATTERN = re.compile(r'^\s*#([0-9A-Fa-f]{6})\s*,\s*(\d+)')
|
||||||
@@ -36,6 +36,16 @@ STEP_PATTERN = re.compile(r'^\s*#([0-9A-Fa-f]{6})\s*,\s*(\d+)')
|
|||||||
|
|
||||||
# ---- Helpers -----------------------------------------------------------
|
# ---- Helpers -----------------------------------------------------------
|
||||||
|
|
||||||
|
def parse_mode(filepath: Path) -> str:
|
||||||
|
"""Return the C mode constant for this show. Defaults to SHOW_LOOP."""
|
||||||
|
with open(filepath) as f:
|
||||||
|
for line in f:
|
||||||
|
m = re.match(r'^\s*//\s*mode:\s*(\w+)', line, re.IGNORECASE)
|
||||||
|
if m:
|
||||||
|
return "SHOW_SINGLE" if m.group(1).lower() == "single" else "SHOW_LOOP"
|
||||||
|
return "SHOW_LOOP"
|
||||||
|
|
||||||
|
|
||||||
def filename_to_symbol(stem: str) -> str:
|
def filename_to_symbol(stem: str) -> str:
|
||||||
"""Convert a filename stem to an uppercase C array symbol. e.g. 'example_fade' → 'SHOW_EXAMPLE_FADE'"""
|
"""Convert a filename stem to an uppercase C array symbol. e.g. 'example_fade' → 'SHOW_EXAMPLE_FADE'"""
|
||||||
clean = re.sub(r'[^a-zA-Z0-9]', '_', stem)
|
clean = re.sub(r'[^a-zA-Z0-9]', '_', stem)
|
||||||
@@ -94,15 +104,15 @@ def render_show_header(steps: list, source_name: str, symbol: str) -> str:
|
|||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
def render_shows_index(ordered_stems: list[str]) -> str:
|
def render_shows_index(ordered: list[tuple[str, str]]) -> str:
|
||||||
"""Render the master shows.h index file."""
|
"""Render the master shows.h index file. ordered = [(stem, mode_constant), ...]"""
|
||||||
includes = "\n".join(f'#include "show_{stem}.h"' for stem in ordered_stems)
|
includes = "\n".join(f'#include "show_{stem}.h"' for stem, _ in ordered)
|
||||||
entries = "\n".join(
|
entries = "\n".join(
|
||||||
f" {{{filename_to_symbol(s)}, {filename_to_symbol(s)}_LENGTH}},"
|
f" {{{filename_to_symbol(s)}, {filename_to_symbol(s)}_LENGTH, {mode}}},"
|
||||||
+ (" // 0 — home show" if i == 0 else f" // {i}")
|
+ (" // 0 — home show" if i == 0 else f" // {i}")
|
||||||
for i, s in enumerate(ordered_stems)
|
for i, (s, mode) in enumerate(ordered)
|
||||||
)
|
)
|
||||||
count = len(ordered_stems)
|
count = len(ordered)
|
||||||
return f"""\
|
return f"""\
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
// shows.h — Master show index.
|
// shows.h — Master show index.
|
||||||
@@ -110,7 +120,7 @@ def render_shows_index(ordered_stems: list[str]) -> str:
|
|||||||
// Do not edit manually — add .txt files to converter/shows/ instead.
|
// Do not edit manually — add .txt files to converter/shows/ instead.
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
//
|
//
|
||||||
// Show 0 is always the home/reset show (slow blue pulse).
|
// Show 0 is always the home/reset show (blue breath).
|
||||||
// Holding the button resets back to show 0.
|
// Holding the button resets back to show 0.
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: BSD-2-Clause
|
// SPDX-License-Identifier: BSD-2-Clause
|
||||||
@@ -133,7 +143,7 @@ const uint8_t SHOW_COUNT = {count};
|
|||||||
# ---- Main --------------------------------------------------------------
|
# ---- Main --------------------------------------------------------------
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
txt_files = sorted(SHOWS_DIR.glob("*.txt"))
|
txt_files = sorted(SHOWS_DIR.glob("*.txt"), key=lambda p: p.stem.lower())
|
||||||
if not txt_files:
|
if not txt_files:
|
||||||
print(f"No .txt files found in {SHOWS_DIR}")
|
print(f"No .txt files found in {SHOWS_DIR}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -150,19 +160,20 @@ def main() -> None:
|
|||||||
|
|
||||||
SKETCH_DIR.mkdir(parents=True, exist_ok=True)
|
SKETCH_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
converted = []
|
converted = [] # list of (stem, mode_constant)
|
||||||
errors = []
|
errors = []
|
||||||
|
|
||||||
for txt_path in ordered_files:
|
for txt_path in ordered_files:
|
||||||
stem = txt_path.stem
|
stem = txt_path.stem
|
||||||
symbol = filename_to_symbol(stem)
|
symbol = filename_to_symbol(stem)
|
||||||
|
mode = parse_mode(txt_path)
|
||||||
try:
|
try:
|
||||||
steps = parse_show(txt_path)
|
steps = parse_show(txt_path)
|
||||||
header = render_show_header(steps, txt_path.name, symbol)
|
header = render_show_header(steps, txt_path.name, symbol)
|
||||||
out = SKETCH_DIR / f"show_{stem}.h"
|
out = SKETCH_DIR / f"show_{stem}.h"
|
||||||
out.write_text(header, encoding="utf-8")
|
out.write_text(header, encoding="utf-8")
|
||||||
print(f" OK {txt_path.name} → show_{stem}.h ({len(steps)} steps)")
|
print(f" OK {txt_path.name} → show_{stem}.h ({len(steps)} steps, {mode})")
|
||||||
converted.append(stem)
|
converted.append((stem, mode))
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
print(f" ERR {e}")
|
print(f" ERR {e}")
|
||||||
errors.append(stem)
|
errors.append(stem)
|
||||||
@@ -174,10 +185,18 @@ def main() -> None:
|
|||||||
print("No shows converted — shows.h not updated.")
|
print("No shows converted — shows.h not updated.")
|
||||||
sys.exit(1)
|
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}
|
||||||
|
for stale in SKETCH_DIR.glob("show_*.h"):
|
||||||
|
if stale not in expected:
|
||||||
|
stale.unlink()
|
||||||
|
print(f" RM {stale.name} (removed — no matching .txt file)")
|
||||||
|
|
||||||
# Regenerate shows.h
|
# Regenerate shows.h
|
||||||
index_path = SKETCH_DIR / "shows.h"
|
index_path = SKETCH_DIR / "shows.h"
|
||||||
index_path.write_text(render_shows_index(converted), encoding="utf-8")
|
index_path.write_text(render_shows_index(converted), encoding="utf-8")
|
||||||
print(f"\n OK shows.h updated ({len(converted)} shows, show 0 = {converted[0]})")
|
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.")
|
print( " Run 'make upload' to compile and send to the Arduino.")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// 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
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
// Blue pulse — home / reset show (always index 0)
|
|
||||||
// A slow, gentle breathing effect in deep blue.
|
|
||||||
// Loops continuously as the default show.
|
|
||||||
//
|
|
||||||
// Format: #RRGGBB, duration_ms
|
|
||||||
|
|
||||||
#050025, 0 // snap to dim blue — sets the starting point
|
|
||||||
#2828FF, 3000 // slow breathe up to bright blue
|
|
||||||
#050025, 3000 // slow breathe back down to dim
|
|
||||||
Reference in New Issue
Block a user