ht2 #5
2 changed files with 61 additions and 14 deletions
|
|
@ -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) {
|
||||||
|
|
|
||||||
71
src/main.cpp
71
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 =
|
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,30 +155,59 @@ 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;
|
||||||
MASTER_TICK = ((MASTER_TICK + (ticks_per_pulse / 2)) / ticks_per_pulse) * ticks_per_pulse;
|
|
||||||
|
|
||||||
// BPM Calc
|
uint64_t sum = 0;
|
||||||
if (last_clk_us > 0) {
|
uint8_t count = 0;
|
||||||
uint64_t diff = now - last_clk_us;
|
for (int i = 0; i < AVG_SAMPLES; i++) {
|
||||||
float calculatedBPM = 60000000.0f / (float)(diff * (float)incomingPPQN);
|
if (pulse_intervals[i] > 0) {
|
||||||
filteredBPM = filteredBPM + (0.15f * (calculatedBPM - filteredBPM));
|
sum += pulse_intervals[i];
|
||||||
BPM = (uint8_t)(filteredBPM + 0.5f);
|
count++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
last_clk_us = now;
|
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;
|
||||||
|
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
if (outputs[i]->lastTriggerTick > MASTER_TICK) {
|
||||||
|
outputs[i]->lastTriggerTick = 0xFFFFFFFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue