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 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) {
|
||||
|
|
|
|||
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 =
|
||||
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_CLOCK = true;
|
||||
external_pulse_received = true;
|
||||
EXTERNAL_CLOCK = 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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue