From 787f084b74c670a09fc6431f186b3345e2a64ad2 Mon Sep 17 00:00:00 2001 From: Dominic DiTaranto Date: Mon, 16 Mar 2026 22:32:32 -0400 Subject: [PATCH] working still... --- include/globals.h | 4 +++ src/main.cpp | 71 +++++++++++++++++++++++++++++++++++++---------- 2 files changed, 61 insertions(+), 14 deletions(-) diff --git a/include/globals.h b/include/globals.h index 0d85057..c7b8827 100644 --- a/include/globals.h +++ b/include/globals.h @@ -58,6 +58,10 @@ extern volatile uint64_t last_clk_us; extern volatile uint64_t last_valid_clk_us; extern volatile bool EXTERNAL_CLOCK; +#define AVG_SAMPLES 12 +extern uint64_t pulse_intervals[AVG_SAMPLES]; +extern uint8_t pulse_idx; + enum WaveShape { SQUARE, TRIANGLE, SAW, RAMP, EXP, HALFSINE, REXP, LOG, SINE, BOUNCE, SIGMO, WOBBLE, STEPDW, STEPUP, SH, SHAPE_COUNT}; inline const char* waveShapeToString(WaveShape shape) { diff --git a/src/main.cpp b/src/main.cpp index 863f103..f93b8ca 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -37,6 +37,10 @@ volatile float filteredBPM = 60.0f; // High-precision BPM for smooth LFOs volatile uint64_t last_external_pulse_us = 0; // Tracks the last Arturia pulse for the watchdog const uint64_t CLOCK_TIMEOUT_US = 2000000; +uint64_t pulse_intervals[AVG_SAMPLES] = {0}; +uint8_t pulse_idx = 0; +uint64_t last_external_clk_us = 0; + bool external_pulse_received = false; ModMatrix matrix; @@ -151,34 +155,63 @@ void handle_outs() { } void gpio_callback(uint gpio, uint32_t events) { - if (gpio == IN_CLK_PIN && (events & GPIO_IRQ_EDGE_RISE)) { +if (gpio == IN_CLK_PIN && (events & GPIO_IRQ_EDGE_RISE)) { uint64_t now = to_us_since_boot(get_absolute_time()); + + // 1. Debounce if (now - last_valid_clk_us < 1000) return; - uint16_t incomingPPQN = PPQNOPTS[EXTPPQNIdx]; - uint32_t ticks_per_pulse = 96 / incomingPPQN; + // 2. BPM Math - Use 'last_external_clk_us' specifically + if (last_external_clk_us > 0) { + uint64_t latest_diff = now - last_external_clk_us; - // Instead of jumping to the NEXT beat, we snap to the CLOSEST beat. - // This prevents the sequencer from "racing" ahead. + pulse_intervals[pulse_idx] = latest_diff; + pulse_idx = (pulse_idx + 1) % AVG_SAMPLES; + + uint64_t sum = 0; + uint8_t count = 0; + for (int i = 0; i < AVG_SAMPLES; i++) { + if (pulse_intervals[i] > 0) { + sum += pulse_intervals[i]; + count++; + } + } + + if (count > 0) { + double avg_diff = (double)sum / (double)count; + uint16_t incomingPPQN = PPQNOPTS[EXTPPQNIdx]; + + // Use 60,000,000.0 (double) to ensure high precision + double calculatedBPM = 60000000.0 / (avg_diff * (double)incomingPPQN); + + if (calculatedBPM > 20.0 && calculatedBPM < 300.0) { + // Slow down the LPF even more (0.05 instead of 0.1) + // This makes the screen feel "heavy" and professional like a real synth + filteredBPM = filteredBPM + (0.05f * ((float)calculatedBPM - filteredBPM)); + } + }} + + // 3. Sync Logic + uint32_t ticks_per_pulse = 96 / PPQNOPTS[EXTPPQNIdx]; MASTER_TICK = ((MASTER_TICK + (ticks_per_pulse / 2)) / ticks_per_pulse) * ticks_per_pulse; - // BPM Calc - if (last_clk_us > 0) { - uint64_t diff = now - last_clk_us; - float calculatedBPM = 60000000.0f / (float)(diff * (float)incomingPPQN); - filteredBPM = filteredBPM + (0.15f * (calculatedBPM - filteredBPM)); - BPM = (uint8_t)(filteredBPM + 0.5f); + for (int i = 0; i < 8; i++) { + if (outputs[i]->lastTriggerTick > MASTER_TICK) { + outputs[i]->lastTriggerTick = 0xFFFFFFFF; + } } - last_clk_us = now; + // 4. Update Timestamps + last_external_clk_us = now; // Store for next external pulse + last_clk_us = now; // Update for the Gate's sub-tick interpolation last_valid_clk_us = now; last_external_pulse_us = now; + external_pulse_received = true; EXTERNAL_CLOCK = true; - external_pulse_received = true; } - // SWITCH + // SWITCH if (gpio == ENCODER_SW_PIN) { uint64_t now = to_us_since_boot(get_absolute_time()); static uint64_t last_sw_time = 0; @@ -317,6 +350,16 @@ int main() { if (external_pulse_received) { external_pulse_received = false; + static uint8_t last_ppqn_idx = 0xFF; + if (EXTPPQNIdx != last_ppqn_idx) { + MASTER_TICK = 0; // Reset to start of bar + for (Gate *g : outputs) { + g->lastTriggerTick = 0xFFFFFFFF; + g->state = 0; // Kill any stuck gates + } + last_ppqn_idx = EXTPPQNIdx; + } + // This is purely for the screen/UI. // The actual timing is being adjusted inside gpio_callback. BPM = (uint8_t)(filteredBPM + 0.5f);