ht2 #5

Merged
dominic merged 4 commits from ht2 into master 2026-03-17 20:07:26 -04:00
2 changed files with 61 additions and 14 deletions
Showing only changes of commit 787f084b74 - Show all commits

View file

@ -58,6 +58,10 @@ extern volatile uint64_t last_clk_us;
extern volatile uint64_t last_valid_clk_us; extern volatile uint64_t last_valid_clk_us;
extern volatile bool EXTERNAL_CLOCK; 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}; 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) { inline const char* waveShapeToString(WaveShape shape) {

View file

@ -37,6 +37,10 @@ volatile float filteredBPM = 60.0f; // High-precision BPM for smooth LFOs
volatile uint64_t last_external_pulse_us = volatile uint64_t last_external_pulse_us =
0; // Tracks the last Arturia pulse for the watchdog 0; // Tracks the last Arturia pulse for the watchdog
const uint64_t CLOCK_TIMEOUT_US = 2000000; 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; bool external_pulse_received = false;
ModMatrix matrix; ModMatrix matrix;
@ -151,34 +155,63 @@ void handle_outs() {
} }
void gpio_callback(uint gpio, uint32_t events) { 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()); uint64_t now = to_us_since_boot(get_absolute_time());
// 1. Debounce
if (now - last_valid_clk_us < 1000) return; if (now - last_valid_clk_us < 1000) return;
uint16_t incomingPPQN = PPQNOPTS[EXTPPQNIdx]; // 2. BPM Math - Use 'last_external_clk_us' specifically
uint32_t ticks_per_pulse = 96 / incomingPPQN; 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. pulse_intervals[pulse_idx] = latest_diff;
// This prevents the sequencer from "racing" ahead. 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; MASTER_TICK = ((MASTER_TICK + (ticks_per_pulse / 2)) / ticks_per_pulse) * ticks_per_pulse;
// BPM Calc for (int i = 0; i < 8; i++) {
if (last_clk_us > 0) { if (outputs[i]->lastTriggerTick > MASTER_TICK) {
uint64_t diff = now - last_clk_us; outputs[i]->lastTriggerTick = 0xFFFFFFFF;
float calculatedBPM = 60000000.0f / (float)(diff * (float)incomingPPQN); }
filteredBPM = filteredBPM + (0.15f * (calculatedBPM - filteredBPM));
BPM = (uint8_t)(filteredBPM + 0.5f);
} }
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_valid_clk_us = now;
last_external_pulse_us = now; last_external_pulse_us = now;
EXTERNAL_CLOCK = true;
external_pulse_received = true; external_pulse_received = true;
EXTERNAL_CLOCK = true;
} }
// SWITCH // SWITCH
if (gpio == ENCODER_SW_PIN) { if (gpio == ENCODER_SW_PIN) {
uint64_t now = to_us_since_boot(get_absolute_time()); uint64_t now = to_us_since_boot(get_absolute_time());
static uint64_t last_sw_time = 0; static uint64_t last_sw_time = 0;
@ -317,6 +350,16 @@ int main() {
if (external_pulse_received) { if (external_pulse_received) {
external_pulse_received = false; 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. // This is purely for the screen/UI.
// The actual timing is being adjusted inside gpio_callback. // The actual timing is being adjusted inside gpio_callback.
BPM = (uint8_t)(filteredBPM + 0.5f); BPM = (uint8_t)(filteredBPM + 0.5f);