From 5dc10eea879aaa3f5c6f1e14113f0f4d424b2765 Mon Sep 17 00:00:00 2001 From: Ashley Strahle Date: Thu, 2 Jul 2026 07:15:53 +0000 Subject: [PATCH] Add MIDI clock-driven LED pulsing - New MidiClock module: tracks 24 PPQN clock ticks, computes beat phase and smooth sine pulse from incoming MIDI Clock (0xF8) - midi_transport: detect CIN=0xF real-time packets, route 0xF8 to MidiClock::tick(), 0xFA/FB to start(), 0xFC to stop() - led_stub update(): every 20ms, modulates active LED brightness using pulse curve (50%-100%), pixel 6 always throbs when clock is running even if no loops active - Aligned set_led_state() all paths to use pad_base_colors - Removed dead activity flash code --- include/led_stub.h | 2 -- include/midi_clock.h | 19 ++++++++++++++++ src/led_stub.cpp | 48 ++++++++++++++++++++++++++------------- src/midi_clock.cpp | 51 ++++++++++++++++++++++++++++++++++++++++++ src/midi_transport.cpp | 15 +++++++++++++ 5 files changed, 118 insertions(+), 17 deletions(-) create mode 100644 include/midi_clock.h create mode 100644 src/midi_clock.cpp diff --git a/include/led_stub.h b/include/led_stub.h index c44274a..8c0ea1c 100644 --- a/include/led_stub.h +++ b/include/led_stub.h @@ -33,8 +33,6 @@ private: static const uint8_t NUM_LEDS = 10; LedState led_states[NUM_LEDS]; bool initialized; - uint32_t activity_off_time = 0; - uint8_t saved_r = 0, saved_g = 0, saved_b = 0; public: DefaultLedStub(); diff --git a/include/midi_clock.h b/include/midi_clock.h new file mode 100644 index 0000000..edafe94 --- /dev/null +++ b/include/midi_clock.h @@ -0,0 +1,19 @@ +#pragma once +#include + +class MidiClock { +public: + static void tick(); + static void start(); + static void stop(); + static float get_phase(); + static float get_pulse(); + static bool is_running(); + +private: + static uint32_t last_tick_time; + static uint32_t tick_count; + static float tick_interval_ms; + static bool running; + static const uint8_t TICKS_PER_BEAT = 24; +}; diff --git a/src/led_stub.cpp b/src/led_stub.cpp index d980445..3e9ac27 100644 --- a/src/led_stub.cpp +++ b/src/led_stub.cpp @@ -1,5 +1,6 @@ #include "led_stub.h" #include "pixel_stomp_mux.h" +#include "midi_clock.h" #include static PixelStompMux* mux_ptr = nullptr; @@ -251,11 +252,7 @@ void DefaultLedStub::set_led_state(uint8_t note, uint8_t channel, uint8_t veloci led_states[i].channel = channel; led_states[i].velocity = velocity; led_states[i].timestamp = millis(); - uint32_t color = velocity_to_color(velocity); - uint8_t r = (color >> 16) & 0xFF; - uint8_t g = (color >> 8) & 0xFF; - uint8_t b = color & 0xFF; - mux_ptr->set_led_color(i, r, g, b); + apply_pad_color(i, velocity); mux_ptr->show(); Serial.printf("[LED] Updated LED %d: note=%d ch=%d vel=%d\n", i, note, channel, velocity); return; @@ -269,11 +266,7 @@ void DefaultLedStub::set_led_state(uint8_t note, uint8_t channel, uint8_t veloci led_states[i].channel = channel; led_states[i].velocity = velocity; led_states[i].timestamp = millis(); - uint32_t color = velocity_to_color(velocity); - uint8_t r = (color >> 16) & 0xFF; - uint8_t g = (color >> 8) & 0xFF; - uint8_t b = color & 0xFF; - mux_ptr->set_led_color(i, r, g, b); + apply_pad_color(i, velocity); mux_ptr->show(); Serial.printf("[LED] Activated LED %d: note=%d ch=%d vel=%d\n", i, note, channel, velocity); return; @@ -311,11 +304,36 @@ void DefaultLedStub::update() { if (!initialized || !mux_ptr) return; uint32_t now = millis(); + static uint32_t last_pulse_update = 0; - // Turn off activity flash - if (activity_off_time > 0 && now >= activity_off_time) { - mux_ptr->set_led_color(0, saved_r, saved_g, saved_b); - mux_ptr->show(); - activity_off_time = 0; + if (now - last_pulse_update < 20) return; + last_pulse_update = now; + + bool clock_running = MidiClock::is_running(); + float pulse = 1.0f; + if (clock_running) { + pulse = MidiClock::get_pulse(); } + + for (int i = 0; i < NUM_LEDS; i++) { + if (led_states[i].active) { + float brightness = clock_running ? (0.5f + 0.5f * pulse) : 1.0f; + uint32_t base = pad_base_colors[i]; + uint8_t r = (uint8_t)(((base >> 16) & 0xFF) * brightness); + uint8_t g = (uint8_t)(((base >> 8) & 0xFF) * brightness); + uint8_t b = (uint8_t)((base & 0xFF) * brightness); + mux_ptr->set_led_color(i, r, g, b); + } else if (clock_running && i == 6) { + float brightness = 0.3f + 0.7f * pulse; + uint32_t base = pad_base_colors[6]; + uint8_t r = (uint8_t)(((base >> 16) & 0xFF) * brightness); + uint8_t g = (uint8_t)(((base >> 8) & 0xFF) * brightness); + uint8_t b = (uint8_t)((base & 0xFF) * brightness); + mux_ptr->set_led_color(6, r, g, b); + } else { + apply_pad_color(i, 0); + } + } + + mux_ptr->show(); } diff --git a/src/midi_clock.cpp b/src/midi_clock.cpp new file mode 100644 index 0000000..f02be0c --- /dev/null +++ b/src/midi_clock.cpp @@ -0,0 +1,51 @@ +#include "midi_clock.h" +#include +#include + +uint32_t MidiClock::last_tick_time = 0; +uint32_t MidiClock::tick_count = 0; +float MidiClock::tick_interval_ms = 0; +bool MidiClock::running = false; + +void MidiClock::tick() { + uint32_t now = millis(); + if (last_tick_time != 0) { + float interval = now - last_tick_time; + if (tick_interval_ms == 0) { + tick_interval_ms = interval; + } else { + tick_interval_ms = tick_interval_ms * 0.9f + interval * 0.1f; + } + } + last_tick_time = now; + tick_count++; + running = true; +} + +void MidiClock::start() { + tick_count = 0; + last_tick_time = 0; + tick_interval_ms = 0; + running = true; +} + +void MidiClock::stop() { + running = false; +} + +float MidiClock::get_phase() { + if (!running || tick_interval_ms == 0) return 0; + uint32_t now = millis(); + float time_since_tick = now - last_tick_time; + float sub_phase = time_since_tick / tick_interval_ms; + if (sub_phase > 1.0f) sub_phase = 1.0f; + return ((tick_count % TICKS_PER_BEAT) + sub_phase) / (float)TICKS_PER_BEAT; +} + +float MidiClock::get_pulse() { + return sinf(M_PI * get_phase()); +} + +bool MidiClock::is_running() { + return running; +} diff --git a/src/midi_transport.cpp b/src/midi_transport.cpp index 8276b18..4d645c9 100644 --- a/src/midi_transport.cpp +++ b/src/midi_transport.cpp @@ -1,4 +1,5 @@ #include "midi_transport.h" +#include "midi_clock.h" #include #include "Adafruit_TinyUSB.h" @@ -51,6 +52,20 @@ void UsbMidiTransport::update() { while (usb_midi.available()) { uint8_t packet[4]; if (usb_midi.readPacket(packet)) { + uint8_t cin = packet[0] & 0x0F; + + if (cin == 0x0F) { + uint8_t rt = packet[1]; + if (rt == 0xF8) { + MidiClock::tick(); + } else if (rt == 0xFA || rt == 0xFB) { + MidiClock::start(); + } else if (rt == 0xFC) { + MidiClock::stop(); + } + continue; + } + MidiEvent event; parse_midi_packet(packet, 4, event);