From b3f4eb1a8bb2d8d93118c7f1ad9f97042294786e Mon Sep 17 00:00:00 2001 From: Ashley Strahle Date: Fri, 3 Jul 2026 08:22:47 +0000 Subject: [PATCH] Add SPP (Song Position Pointer) detection - uses SPP on START to set correct tick position. Add beats_per_bar (default 4) with button combo: hold 10 + press 1=4/4, 2=3/4, 3=6/4 --- include/midi_transport.h | 2 ++ src/app_task.cpp | 14 ++++++++++++++ src/main.cpp | 7 ++++--- src/midi_transport.cpp | 26 ++++++++++++++++++++++++-- 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/include/midi_transport.h b/include/midi_transport.h index e2c8ece..d22a472 100644 --- a/include/midi_transport.h +++ b/include/midi_transport.h @@ -4,6 +4,8 @@ #include extern volatile uint32_t midi_tick_count; +extern volatile uint16_t last_spp_position; +extern volatile bool spp_valid; struct MidiEvent { enum Type { diff --git a/src/app_task.cpp b/src/app_task.cpp index eb90559..a24642f 100644 --- a/src/app_task.cpp +++ b/src/app_task.cpp @@ -1,6 +1,8 @@ #include "app_task.h" #include +extern volatile uint8_t beats_per_bar; + AppTask::AppTask(LedStub* led, SwitchStub* sw, UsbMidiTransport* midi) : led_driver(led), switch_driver(sw), midi_transport(midi) { @@ -124,6 +126,18 @@ void AppTask::process_midi_event(const MidiEvent& event) { } void AppTask::process_switch_event(uint8_t switch_id, bool pressed) { + // Time signature combo: hold pad 10 (switch 9) + press pad 1/2/3 + if (switch_id <= 2 && switch_driver->is_pressed(9)) { + if (pressed) { + switch (switch_id) { + case 0: beats_per_bar = 4; Serial.println("[APP] Time sig: 4/4"); break; + case 1: beats_per_bar = 3; Serial.println("[APP] Time sig: 3/4"); break; + case 2: beats_per_bar = 6; Serial.println("[APP] Time sig: 6/4"); break; + } + } + return; // suppress CC in combo mode + } + for (uint8_t i = 0; i < NUM_PADS; i++) { if (pad_mapping[i].physical_switch == switch_id) { uint8_t channel = pad_mapping[i].midi_channel; diff --git a/src/main.cpp b/src/main.cpp index 0a62824..ca4dadf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,6 +22,7 @@ AppTask controller(&led_driver, &switch_driver, &midi_transport); ExpressionPedal exp_pedal(4); static volatile uint32_t flash_latency = 55; // ms delay to compensate for Loopy Pro rendering +static volatile uint8_t beats_per_bar = 4; // change to match your project's time signature TaskHandle_t midi_task_handle = NULL; @@ -45,8 +46,8 @@ void midi_task(void* parameter) { if (current_beat != last_beat) { last_beat = current_beat; flash_start = now + flash_latency; - // Beat 1 = red on current_beat % 4 == 0, others = white - if (current_beat % 4 == 0) { + // Beat 1 = red on current_beat % beats_per_bar == 0, others = white + if (current_beat % beats_per_bar == 0) { mux.set_led_color(6, 255, 0, 0); } else { mux.set_led_color(6, 255, 255, 255); @@ -55,7 +56,7 @@ void midi_task(void* parameter) { if (flash_start > 0 && now >= flash_start) { mux.show(); - uint32_t hold = (last_beat % 4 == 0) ? 100 : 50; + uint32_t hold = (last_beat % beats_per_bar == 0) ? 100 : 50; uint32_t elapsed = now - flash_start; if (elapsed >= hold) { mux.set_led_color(6, 20, 20, 20); diff --git a/src/midi_transport.cpp b/src/midi_transport.cpp index 9291667..0d03d24 100644 --- a/src/midi_transport.cpp +++ b/src/midi_transport.cpp @@ -3,6 +3,8 @@ #include "Adafruit_TinyUSB.h" volatile uint32_t midi_tick_count = 0; +volatile uint16_t last_spp_position = 0; +volatile bool spp_valid = false; static Adafruit_USBD_MIDI usb_midi; @@ -58,9 +60,29 @@ void UsbMidiTransport::update() { if (packet[1] == 0xF8) { midi_tick_count++; } else if (packet[1] == 0xFA) { - midi_tick_count = 0xFFFFFFFF; - Serial.println("[CLK] START - next F8 = tick 0"); + if (spp_valid) { + midi_tick_count = last_spp_position * 6; + spp_valid = false; + Serial.printf("[CLK] START at SPP=%d -> tick %d\n", last_spp_position, midi_tick_count); + } else { + midi_tick_count = 0xFFFFFFFF; + Serial.println("[CLK] START (no SPP) - next F8 = tick 0"); + } + } else if (packet[1] == 0xFB) { + // CONTINUE - same as START for our purposes + if (spp_valid) { + midi_tick_count = last_spp_position * 6; + spp_valid = false; + Serial.printf("[CLK] CONTINUE at SPP=%d -> tick %d\n", last_spp_position, midi_tick_count); + } else { + Serial.println("[CLK] CONTINUE (no SPP)"); + } } + } else if (cin == 0x03 && packet[1] == 0xF2) { + // Song Position Pointer + last_spp_position = (packet[3] << 7) | packet[2]; + spp_valid = true; + Serial.printf("[CLK] SPP=%d\n", last_spp_position); } else { MidiEvent event; parse_midi_packet(packet, 4, event);