diff --git a/README.md b/README.md index b18f83e..b69391a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ A collection of programs using [ChaosNuggets/Diabolo_Light](https://github.com/C PlatformIO is configured to upload a `src/main.cpp` file. Choose which program to use by symlinking: - ln -Fs performance.cpp src/main.cpp + ln -Fs single_color.cpp src/main.cpp ## `src/color_lab.cpp` @@ -16,9 +16,9 @@ This program is used to pick colors that look good on the LED kit. This program shows a color on initial button press, and will turn off on the 2nd press. -## `src/performance.cpp` +## `src/genshin-fountain-*.cpp` -This program defines a performance consisting of scenes which are defined by colors and transitions between colors. Contains generic structs which I hope to pull out into a separate file eventually. +These programs are used to create a performance consisting of scenes which are defined by colors and transitions between colors. Contains generic structs which I hope to pull out into a separate file eventually. I use Final Cut Pro's Export XML functionality and a custom script `src/read_fcp_xml.py` to facilitate definition of scenes for this program. Not enough time to document now, but here's a screenshot: diff --git a/src/color_lab.cpp b/src/color_lab.cpp index 3499261..37d8ab0 100644 --- a/src/color_lab.cpp +++ b/src/color_lab.cpp @@ -12,6 +12,7 @@ typedef uint32_t Color; static Color colors[] = { pixels.Color(255, 0 , 0 ), // red + pixels.Color(255, 32 , 0 ), // orange pixels.Color(255, 64 , 0 ), // orange pixels.Color(255, 128, 0 ), // warm yellow pixels.Color(255, 192, 0 ), // yellow-green diff --git a/src/genshin-fountain-1.cpp b/src/genshin-fountain-1.cpp new file mode 100644 index 0000000..93189a8 --- /dev/null +++ b/src/genshin-fountain-1.cpp @@ -0,0 +1,146 @@ +#include +#include +#include + +Adafruit_NeoPixel pixels( + Diabolo_Light::NUM_LEDS, + Diabolo_Light::LED_PIN, + Diabolo_Light::LED_TYPE +); + +typedef uint8_t DiodeBrightness; +typedef int8_t DiodeBrightnessOffset; + +struct Color { + const DiodeBrightness r; + const DiodeBrightness g; + const DiodeBrightness b; + + Color(DiodeBrightness r, DiodeBrightness g, DiodeBrightness b) + : r(r), g(g), b(b) {} + + bool operator==(const Color& other) const { + return r == other.r && g == other.g && b == other.b; + } +}; + +const static Color WHITE_1 = Color(20 , 20 , 20 ); +const static Color WHITE_3 = Color(255, 255, 255); +const static Color RED_3 = Color(255, 0 , 0 ); +const static Color GREEN_3 = Color(128, 255, 0 ); +const static Color BLUE_3 = Color(0 , 64 , 255); +const static Color YELLOW_3 = Color(255, 128, 0 ); +const static Color ORANGE_3 = Color(255, 32 , 0 ); +const static Color BLUE_2 = Color(0 , 128, 255); + +struct Scene { + const Color& start; + const Color& end; + uint32_t duration; + // TODO: add types of transitions. R/G/B balanced transition or R+G+B combined transition + + Scene(const Color& start, const Color& end, uint32_t duration) + : start(start), end(end), duration(duration) {} +}; + +Scene get_current_scene(uint8_t scene_num) { + switch (scene_num) { + case 1: return Scene(WHITE_1, WHITE_1, 6683); + case 2: return Scene(WHITE_1, YELLOW_3, 1166); + case 3: return Scene(YELLOW_3, YELLOW_3, 20966); + case 4: return Scene(YELLOW_3, BLUE_2, 816); // toss up + case 5: return Scene(BLUE_2, YELLOW_3, 816); // toss down + case 6: return Scene(YELLOW_3, YELLOW_3, 1016); // before umbrella + case 7: return Scene(YELLOW_3, BLUE_2, 816); // toss up + case 8: return Scene(BLUE_2, YELLOW_3, 816); // toss down + case 9: return Scene(YELLOW_3, YELLOW_3, 933); + case 10: return Scene(YELLOW_3, WHITE_3, 1283); // before umbrella + case 11: return Scene(WHITE_3, WHITE_3, 1250); // before umbrella + case 12: return Scene(WHITE_3, BLUE_2, 816); // toss up + case 13: return Scene(BLUE_2, WHITE_3, 816); // toss down + case 14: return Scene(WHITE_3, WHITE_3, 950); // before umbrella + case 15: return Scene(WHITE_3, WHITE_3, 5333); // flower + case 16: return Scene(RED_3, RED_3, 333); // umbrella + case 17: return Scene(YELLOW_3, YELLOW_3, 316); // umbrella + case 18: return Scene(RED_3, RED_3, 333); // umbrella + case 19: return Scene(YELLOW_3, YELLOW_3, 316); // umbrella + case 20: return Scene(RED_3, RED_3, 333); // umbrella + case 21: return Scene(YELLOW_3, YELLOW_3, 316); // umbrella + case 22: return Scene(RED_3, RED_3, 333); // umbrella + case 23: return Scene(YELLOW_3, YELLOW_3, 316); // umbrella + case 24: return Scene(RED_3, RED_3, 333); // umbrella + case 25: return Scene(YELLOW_3, YELLOW_3, 316); // umbrella + case 26: return Scene(RED_3, RED_3, 333); // umbrella + case 27: return Scene(YELLOW_3, YELLOW_3, 316); // umbrella + case 28: return Scene(RED_3, RED_3, 333); // umbrella + case 29: return Scene(YELLOW_3, YELLOW_3, 316); // umbrella + case 30: return Scene(RED_3, ORANGE_3, 650); // umbrella + case 31: return Scene(ORANGE_3, ORANGE_3, 21200); // after umbrella + case 32: return Scene(ORANGE_3, BLUE_3, 2733); // transition + case 33: return Scene(BLUE_3, BLUE_3, 17950); // around the world + case 34: return Scene(BLUE_3, YELLOW_3, 2733); // transition + case 35: return Scene(YELLOW_3, RED_3, 1483); // transition + case 36: return Scene(RED_3, RED_3, 17850); // dragon + case 37: return Scene(RED_3, WHITE_3, 2916); // transition + case 38: return Scene(WHITE_3, WHITE_3, 251016); + } +} + +const static uint8_t TOTAL_SCENES = 38; +uint8_t scene_num = 1; +unsigned long past_scene_durations = 0; + +void set_all_pixels(uint32_t color) { + for (unsigned int i = 0; i < Diabolo_Light::NUM_LEDS; i++) { + pixels.setPixelColor(i, color); + } + pixels.show(); +} + +void setup() { + pixels.begin(); + + Diabolo_Light::begin(1, 0, [](){ + scene_num = 1; + past_scene_durations = 0; + }); +} + +void loop() { + Diabolo_Light::handle_button(); + + if (Diabolo_Light::get_current_mode() != 1) return; + + Scene scene = get_current_scene(scene_num); + + // Progress within current scene, from 0-1 + double progress = + static_cast(Diabolo_Light::awake_time() - past_scene_durations) + / scene.duration; + + // Go to next scene + if (progress >= 1) { + past_scene_durations = past_scene_durations + scene.duration; + scene_num++; + + // Turn off after last scene + if (scene_num > TOTAL_SCENES) { + Diabolo_Light::set_current_mode(0); + } + } + + if (scene.start == scene.end) { + set_all_pixels(pixels.Color( + scene.start.r, + scene.start.g, + scene.start.b + )); + } + else { + set_all_pixels(pixels.Color( + scene.start.r + static_cast((scene.end.r - scene.start.r) * progress), + scene.start.g + static_cast((scene.end.g - scene.start.g) * progress), + scene.start.b + static_cast((scene.end.b - scene.start.b) * progress) + )); + } +} diff --git a/src/genshin-fountain-2.cpp b/src/genshin-fountain-2.cpp new file mode 100644 index 0000000..2c8a9a1 --- /dev/null +++ b/src/genshin-fountain-2.cpp @@ -0,0 +1,146 @@ +#include +#include +#include + +Adafruit_NeoPixel pixels( + Diabolo_Light::NUM_LEDS, + Diabolo_Light::LED_PIN, + Diabolo_Light::LED_TYPE +); + +typedef uint8_t DiodeBrightness; +typedef int8_t DiodeBrightnessOffset; + +struct Color { + const DiodeBrightness r; + const DiodeBrightness g; + const DiodeBrightness b; + + Color(DiodeBrightness r, DiodeBrightness g, DiodeBrightness b) + : r(r), g(g), b(b) {} + + bool operator==(const Color& other) const { + return r == other.r && g == other.g && b == other.b; + } +}; + +const static Color WHITE_1 = Color(20 , 20 , 20 ); +const static Color WHITE_3 = Color(255, 255, 255); +const static Color RED_3 = Color(255, 0 , 0 ); +const static Color GREEN_3 = Color(128, 255, 0 ); +const static Color BLUE_3 = Color(0 , 64 , 255); +const static Color YELLOW_3 = Color(255, 128, 0 ); +const static Color ORANGE_3 = Color(255, 32 , 0 ); +const static Color BLUE_2 = Color(0 , 128, 255); + +struct Scene { + const Color& start; + const Color& end; + uint32_t duration; + // TODO: add types of transitions. R/G/B balanced transition or R+G+B combined transition + + Scene(const Color& start, const Color& end, uint32_t duration) + : start(start), end(end), duration(duration) {} +}; + +Scene get_current_scene(uint8_t scene_num) { + switch (scene_num) { + case 1: return Scene(WHITE_1, WHITE_1, 6683); + case 2: return Scene(WHITE_1, YELLOW_3, 1166); + case 3: return Scene(YELLOW_3, YELLOW_3, 20966); + case 4: return Scene(YELLOW_3, YELLOW_3, 1233); // before umbrella + case 5: return Scene(YELLOW_3, BLUE_2, 816); // toss up + case 6: return Scene(BLUE_2, YELLOW_3, 816); // toss down + case 7: return Scene(YELLOW_3, YELLOW_3, 1066); // before umbrella + case 8: return Scene(YELLOW_3, BLUE_2, 816); // toss up + case 9: return Scene(BLUE_2, YELLOW_3, 816); // toss down + case 10: return Scene(YELLOW_3, WHITE_3, 933); // before umbrella + case 11: return Scene(WHITE_3, WHITE_3, 1250); // before umbrella + case 12: return Scene(WHITE_3, BLUE_2, 816); // toss up + case 13: return Scene(BLUE_2, WHITE_3, 816); // toss down + case 14: return Scene(WHITE_3, WHITE_3, 950); // before umbrella + case 15: return Scene(WHITE_3, WHITE_3, 5333); // flower + case 16: return Scene(RED_3, RED_3, 333); // umbrella + case 17: return Scene(YELLOW_3, YELLOW_3, 316); // umbrella + case 18: return Scene(RED_3, RED_3, 333); // umbrella + case 19: return Scene(YELLOW_3, YELLOW_3, 316); // umbrella + case 20: return Scene(RED_3, RED_3, 333); // umbrella + case 21: return Scene(YELLOW_3, YELLOW_3, 316); // umbrella + case 22: return Scene(RED_3, RED_3, 333); // umbrella + case 23: return Scene(YELLOW_3, YELLOW_3, 316); // umbrella + case 24: return Scene(RED_3, RED_3, 333); // umbrella + case 25: return Scene(YELLOW_3, YELLOW_3, 316); // umbrella + case 26: return Scene(RED_3, RED_3, 333); // umbrella + case 27: return Scene(YELLOW_3, YELLOW_3, 316); // umbrella + case 28: return Scene(RED_3, RED_3, 333); // umbrella + case 29: return Scene(YELLOW_3, YELLOW_3, 316); // umbrella + case 30: return Scene(RED_3, ORANGE_3, 650); // umbrella + case 31: return Scene(ORANGE_3, ORANGE_3, 21200); // after umbrella + case 32: return Scene(ORANGE_3, BLUE_3, 2733); // transition + case 33: return Scene(BLUE_3, BLUE_3, 17950); // around the world + case 34: return Scene(BLUE_3, YELLOW_3, 2733); // transition + case 35: return Scene(YELLOW_3, RED_3, 1483); // transition + case 36: return Scene(RED_3, RED_3, 17850); // dragon + case 37: return Scene(RED_3, WHITE_3, 2916); // transition + case 38: return Scene(WHITE_3, WHITE_3, 251016); + } +} + +const static uint8_t TOTAL_SCENES = 38; +uint8_t scene_num = 1; +unsigned long past_scene_durations = 0; + +void set_all_pixels(uint32_t color) { + for (unsigned int i = 0; i < Diabolo_Light::NUM_LEDS; i++) { + pixels.setPixelColor(i, color); + } + pixels.show(); +} + +void setup() { + pixels.begin(); + + Diabolo_Light::begin(1, 0, [](){ + scene_num = 1; + past_scene_durations = 0; + }); +} + +void loop() { + Diabolo_Light::handle_button(); + + if (Diabolo_Light::get_current_mode() != 1) return; + + Scene scene = get_current_scene(scene_num); + + // Progress within current scene, from 0-1 + double progress = + static_cast(Diabolo_Light::awake_time() - past_scene_durations) + / scene.duration; + + // Go to next scene + if (progress >= 1) { + past_scene_durations = past_scene_durations + scene.duration; + scene_num++; + + // Turn off after last scene + if (scene_num > TOTAL_SCENES) { + Diabolo_Light::set_current_mode(0); + } + } + + if (scene.start == scene.end) { + set_all_pixels(pixels.Color( + scene.start.r, + scene.start.g, + scene.start.b + )); + } + else { + set_all_pixels(pixels.Color( + scene.start.r + static_cast((scene.end.r - scene.start.r) * progress), + scene.start.g + static_cast((scene.end.g - scene.start.g) * progress), + scene.start.b + static_cast((scene.end.b - scene.start.b) * progress) + )); + } +} diff --git a/src/performance.cpp b/src/performance.cpp deleted file mode 100644 index 49bf79b..0000000 --- a/src/performance.cpp +++ /dev/null @@ -1,135 +0,0 @@ -#include -#include -#include - -Adafruit_NeoPixel pixels( - Diabolo_Light::NUM_LEDS, - Diabolo_Light::LED_PIN, - Diabolo_Light::LED_TYPE -); - -typedef uint8_t DiodeBrightness; -typedef int8_t DiodeBrightnessOffset; - -struct Color { - const DiodeBrightness r; - const DiodeBrightness g; - const DiodeBrightness b; - - Color(DiodeBrightness r, DiodeBrightness g, DiodeBrightness b) - : r(r), g(g), b(b) {} - - bool operator==(const Color& other) const { - return r == other.r && g == other.g && b == other.b; - } -}; - -const static Color WHITE_1 = Color(20, 20, 20 ); -const static Color WHITE_3 = Color(255, 255, 255); -const static Color RED_3 = Color(255, 0, 0 ); -const static Color GREEN_3 = Color(128, 255, 0 ); -const static Color BLUE_3 = Color(0, 0, 255); -const static Color YELLOW_3 = Color(255, 128, 0 ); -const static Color ORANGE_3 = Color(255, 32, 0 ); - -struct Scene { - const Color start; - const Color end; - uint32_t duration; - // TODO: add types of transitions. R/G/B balanced transition or R+G+B combined transition - - Scene(const Color start, const Color end, double duration) - : start(start), end(end), duration(duration) {} -}; - -static Scene scenes[] = { -Scene(WHITE_1, WHITE_1, 6683), -Scene(WHITE_1, YELLOW_3, 1166), -Scene(YELLOW_3, YELLOW_3, 36633), -Scene(RED_3, RED_3, 333), // // umbrella -Scene(YELLOW_3, YELLOW_3, 316), // // umbrella -Scene(RED_3, RED_3, 333), // // umbrella -Scene(YELLOW_3, YELLOW_3, 316), // // umbrella -Scene(RED_3, RED_3, 333), // // umbrella -Scene(YELLOW_3, YELLOW_3, 316), // // umbrella -Scene(RED_3, RED_3, 333), // // umbrella -Scene(YELLOW_3, YELLOW_3, 316), // // umbrella -Scene(RED_3, RED_3, 333), // // umbrella -Scene(YELLOW_3, YELLOW_3, 316), // // umbrella -Scene(RED_3, RED_3, 333), // // umbrella -Scene(YELLOW_3, YELLOW_3, 316), // // umbrella -Scene(RED_3, RED_3, 333), // // umbrella -Scene(YELLOW_3, YELLOW_3, 316), // // umbrella -Scene(RED_3, ORANGE_3, 650), // // umbrella -Scene(ORANGE_3, ORANGE_3, 21200), // // after umbrella -Scene(ORANGE_3, BLUE_3, 2733), // // transition -Scene(BLUE_3, BLUE_3, 17950), // // around the world -Scene(BLUE_3, YELLOW_3, 2733), // // transition -Scene(YELLOW_3, RED_3, 1483), // // transition -Scene(RED_3, RED_3, 17850), // // dragon -Scene(RED_3, WHITE_3, 2916), // // transition -// Scene(WHITE_3, WHITE_3, 251016), // -}; - -uint8_t scene_num = 0; -unsigned long past_scene_durations = 0; - -void set_all_pixels(uint32_t color) { - for (unsigned int i = 0; i < Diabolo_Light::NUM_LEDS; i++) { - pixels.setPixelColor(i, color); - } - pixels.show(); -} - -void setup() { - pixels.begin(); - - Diabolo_Light::begin(1, 0, [](){ - scene_num = 0; - past_scene_durations = 0; - }); -} - -void loop() { - Diabolo_Light::handle_button(); - - if (Diabolo_Light::get_current_mode() != 1) return; - - Scene scene = scenes[scene_num]; - - // Progress within current scene, from 0-1 - double progress = - static_cast(Diabolo_Light::awake_time() - past_scene_durations) - / scene.duration; - - // Go to next scene - if (progress >= 1) { - past_scene_durations = past_scene_durations + scene.duration; - scene_num++; - - // Turn off after last scene - // if (scene_num >= sizeof(scenes) / sizeof(scenes[0])) { - // Diabolo_Light::set_current_mode(0); - // } - } - - if (scene_num >= sizeof(scenes) / sizeof(scenes[0])) { - // HACK - set_all_pixels(pixels.Color(255, 255, 255)); - return; - } - else if (scene.start == scene.end) { - set_all_pixels(pixels.Color( - scene.start.r, - scene.start.g, - scene.start.b - )); - } - else { - set_all_pixels(pixels.Color( - scene.start.r + static_cast((scene.end.r - scene.start.r) * progress), - scene.start.g + static_cast((scene.end.g - scene.start.g) * progress), - scene.start.b + static_cast((scene.end.b - scene.start.b) * progress) - )); - } -} diff --git a/src/read_fcp_xml.py b/src/read_fcp_xml.py index d8e13ee..66ec422 100755 --- a/src/read_fcp_xml.py +++ b/src/read_fcp_xml.py @@ -6,6 +6,7 @@ import re import sys +from textwrap import dedent import xml.etree.ElementTree as ET @@ -20,18 +21,52 @@ def generate_scenes(fcp_xml): with open(fcp_xml) as f: root = ET.fromstring(f.read()) - marker_pattern = re.compile(r'LED_MARKER ([A-Z]+_\d), ([A-Z]+_\d)( \/\/ .*)?') - - for video in root.iter('video'): - if video.attrib['name'].startswith('LED_MARKER'): - match = marker_pattern.search(video.attrib['name']) - if match: - color1 = match.group(1) - color2 = match.group(2) - comment = match.group(3) or '' - print(f"Scene({color1}, {color2}, {duration_to_ms(video.attrib['duration'])}), // {comment}") - else: - print(f"Scene(, , {duration_to_ms(video.attrib['duration'])}),") + marker_pattern = re.compile(r'LED_MARKER ([A-Z_\d]+), ([A-Z_\d]+)( \/\/ .*)?') + + scene_number = 1 + scene_template = 'case {number}: return Scene({color1}, {color2}, {duration}); {comment}' + scene_lines = [] + + for video in root.iter('video'): + disabled = ('enabled' in video.attrib) and (video.attrib['enabled'] == '0') + + if video.attrib['name'].startswith('LED_MARKER') and not disabled: + match = marker_pattern.search(video.attrib['name']) + if match: + color1 = match.group(1) + color2 = match.group(2) + comment = match.group(3) or '' + scene_lines.append( + scene_template.format( + number=scene_number, + color1=color1, + color2=color2, + duration=duration_to_ms(video.attrib['duration']), + comment=comment, + ) + ) + else: + scene_lines.append( + scene_template.format( + number=scene_number, + color1='', + color2='', + duration=duration_to_ms(video.attrib['duration']), + comment='', + ) + ) + scene_number += 1 + + print(dedent("""\ + Scene get_current_scene(uint8_t scene_num) {{ + switch (scene_num) {{ + {case_statements} + }} + }} + + const static uint8_t TOTAL_SCENES = {size}; + """).format(case_statements='\n '.join(scene_lines), + size=len(scene_lines))) def duration_to_ms(duration_str):