ht2 #5
4 changed files with 177 additions and 158 deletions
|
|
@ -60,6 +60,7 @@ public:
|
||||||
|
|
||||||
WaveShape shape = SQUARE;
|
WaveShape shape = SQUARE;
|
||||||
uint32_t startTick = 0;
|
uint32_t startTick = 0;
|
||||||
|
uint32_t stopTick = 0;
|
||||||
uint32_t pulseWidthTicks = 0;
|
uint32_t pulseWidthTicks = 0;
|
||||||
bool sticky = false;
|
bool sticky = false;
|
||||||
int8_t modifierSelectionIndex;
|
int8_t modifierSelectionIndex;
|
||||||
|
|
|
||||||
|
|
@ -41,10 +41,13 @@ void gpio_callback(uint gpio, uint32_t events);
|
||||||
|
|
||||||
// TIME BASED
|
// TIME BASED
|
||||||
extern volatile bool PLAY;
|
extern volatile bool PLAY;
|
||||||
extern volatile uint8_t BPM;
|
extern volatile float BPM;
|
||||||
static constexpr uint32_t MINUTE_US = 60000000;
|
static constexpr uint32_t MINUTE_US = 60000000;
|
||||||
static constexpr uint8_t PPQN = 96;
|
static constexpr uint8_t PPQN = 96;
|
||||||
extern volatile uint32_t MASTER_TICK;
|
extern volatile uint32_t MASTER_TICK;
|
||||||
|
extern volatile float filteredBPM;
|
||||||
|
extern volatile uint64_t last_external_pulse_us;
|
||||||
|
extern const uint64_t CLOCK_TIMEOUT_US;
|
||||||
|
|
||||||
extern volatile bool RUN;
|
extern volatile bool RUN;
|
||||||
|
|
||||||
|
|
|
||||||
230
src/Gate.cpp
230
src/Gate.cpp
|
|
@ -4,9 +4,16 @@
|
||||||
#include "Settings.h"
|
#include "Settings.h"
|
||||||
#include "globals.h"
|
#include "globals.h"
|
||||||
#include "hardware/pwm.h"
|
#include "hardware/pwm.h"
|
||||||
|
#include <cstdint>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
#include <pico/types.h>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#ifndef max
|
||||||
|
#define max(a,b) (((a) > (b)) ? (a) : (b))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
Gate::Gate(uint8_t pin, uint8_t idx, uint8_t slotIdx1, uint8_t slotIdx2) : Output(pin, idx, slotIdx1, slotIdx2) {
|
Gate::Gate(uint8_t pin, uint8_t idx, uint8_t slotIdx1, uint8_t slotIdx2) : Output(pin, idx, slotIdx1, slotIdx2) {
|
||||||
|
|
@ -231,157 +238,148 @@ void Gate::calculatePulseWidth() {
|
||||||
pulseWidthTicks = 0;
|
pulseWidthTicks = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->pulseWidthTicks =
|
|
||||||
(uint32_t)((float)this->tickInterval * (this->width / 100.0f));
|
|
||||||
|
|
||||||
|
// Cap the width at 99% so it doesn't bleed into the next trigger
|
||||||
|
float effectiveWidth = (float)this->width;
|
||||||
|
if (effectiveWidth > 99.0f) effectiveWidth = 99.0f;
|
||||||
|
|
||||||
|
this->pulseWidthTicks = (uint32_t)((float)this->tickInterval * (effectiveWidth / 100.0f));
|
||||||
|
|
||||||
|
// Minimum 1 tick so we actually see a pulse
|
||||||
if (this->width > 0 && this->pulseWidthTicks == 0) {
|
if (this->width > 0 && this->pulseWidthTicks == 0) {
|
||||||
this->pulseWidthTicks = 1;
|
this->pulseWidthTicks = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gate::turnOn() {
|
void Gate::turnOn() {
|
||||||
if (!isEnabled || tickInterval == 0)
|
if (!isEnabled || tickInterval == 0) return;
|
||||||
return;
|
|
||||||
|
|
||||||
if (MASTER_TICK % tickInterval == 0) {
|
// Trigger on the interval, ensuring we don't double-trigger on the same tick
|
||||||
if (MASTER_TICK != lastTriggerTick) {
|
if (MASTER_TICK % tickInterval == 0 && MASTER_TICK != lastTriggerTick) {
|
||||||
lastTriggerTick = MASTER_TICK;
|
lastTriggerTick = MASTER_TICK;
|
||||||
|
|
||||||
float baseP = (float)this->p;
|
// Probability
|
||||||
|
float effectiveP = (float)this->p + (this->pMod * 100.0f);
|
||||||
|
if ((rand() % 100) + 1 > (uint8_t)effectiveP) {
|
||||||
|
scheduledTick = 0xFFFFFFFF;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
float effectiveP = baseP + (this->pMod * 100.0f);
|
// Swing
|
||||||
|
triggerCount++;
|
||||||
|
int32_t swingOffset = (int32_t)((float)tickInterval * ((float)swing - 50.0f) / 100.0f);
|
||||||
|
|
||||||
if (effectiveP > 100.0f) effectiveP = 100.0f;
|
if (triggerCount % 2 == 0) {
|
||||||
if (effectiveP < 0.0f) effectiveP = 0.0f;
|
// Use max(0, offset) to prevent early triggers from breaking the state machine
|
||||||
|
scheduledTick = MASTER_TICK + (uint32_t)max(0, swingOffset);
|
||||||
if ((rand() % 100) + 1 > (uint8_t)effectiveP) {
|
} else {
|
||||||
scheduledTick = 0xFFFFFFFF;
|
scheduledTick = MASTER_TICK;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// swing
|
|
||||||
triggerCount++;
|
|
||||||
|
|
||||||
uint32_t swingDelayTicks =
|
|
||||||
(uint32_t)((float)tickInterval * ((float)swing - 50.0f) / 100.0f);
|
|
||||||
|
|
||||||
if (triggerCount % 2 == 0) {
|
|
||||||
scheduledTick = MASTER_TICK + swingDelayTicks;
|
|
||||||
} else {
|
|
||||||
scheduledTick = MASTER_TICK;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Execution
|
||||||
if (MASTER_TICK >= scheduledTick && !state) {
|
if (MASTER_TICK >= scheduledTick && !state) {
|
||||||
state = 1;
|
state = 1;
|
||||||
startTick = MASTER_TICK;
|
startTick = MASTER_TICK;
|
||||||
startTimeUs = time_us_64();
|
|
||||||
scheduledTick = 0xFFFFFFFF;
|
calculatePulseWidth();
|
||||||
|
stopTick = startTick + pulseWidthTicks;
|
||||||
|
|
||||||
|
scheduledTick = 0xFFFFFFFF;
|
||||||
currentRandomVal = (float)rand() / (float)RAND_MAX;
|
currentRandomVal = (float)rand() / (float)RAND_MAX;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gate::update() {
|
void Gate::update() {
|
||||||
|
// 1. EXIT EARLY IF OFF
|
||||||
if (!state && !sticky) {
|
if (!state && !sticky) {
|
||||||
lastOutVal = 0.0f;
|
lastOutVal = 0.0f;
|
||||||
return;
|
// Make sure we actually write 0 if we aren't sticky
|
||||||
}
|
writeAnalog(0);
|
||||||
|
|
||||||
uint64_t now = time_us_64();
|
|
||||||
uint32_t elapsedUs = (uint32_t)(now - startTimeUs);
|
|
||||||
|
|
||||||
if (elapsedUs >= pulseDurationUs) {
|
|
||||||
state = 0;
|
|
||||||
if (width < 100) {
|
|
||||||
scheduledTick = 0xFFFFFFFF;
|
|
||||||
lastTriggerTick = 0xFFFFFFFF;
|
|
||||||
}
|
|
||||||
if (!sticky)
|
|
||||||
writeAnalog(0);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
float phase = (float)elapsedUs / (float)pulseDurationUs;
|
// 2. LIVE WIDTH MODULATION
|
||||||
float outVal = 0;
|
// We calculate the 'stopTick' every frame.
|
||||||
|
// IMPORTANT: Cap at 98% to ensure the gate has a "low" period before next beat.
|
||||||
|
float effectiveWidth = (float)width + (widthMod * 100.0f);
|
||||||
|
if (effectiveWidth > 98.0f) effectiveWidth = 98.0f;
|
||||||
|
if (effectiveWidth < 1.0f) effectiveWidth = 1.0f;
|
||||||
|
|
||||||
switch (shape) {
|
|
||||||
case SINE:
|
|
||||||
outVal = (sinf(phase * 2.0f * 3.14159f) * 0.5f) + 0.5f;
|
|
||||||
break;
|
|
||||||
case HALFSINE: // AKA HUMP
|
|
||||||
outVal = sinf(phase * 3.14159f);
|
|
||||||
break;
|
|
||||||
case TRIANGLE:
|
|
||||||
outVal = (phase < 0.5f) ? (phase * 2.0f) : (2.0f - (phase * 2.0f));
|
|
||||||
break;
|
|
||||||
case SAW:
|
|
||||||
outVal = 1.0f - phase;
|
|
||||||
break;
|
|
||||||
case RAMP:
|
|
||||||
outVal = phase;
|
|
||||||
break;
|
|
||||||
case EXP:
|
|
||||||
outVal = expf(-5.0f * phase);
|
|
||||||
break;
|
|
||||||
case REXP:
|
|
||||||
outVal = expf(5.0f * (phase - 1.0f));
|
|
||||||
break;
|
|
||||||
case LOG:
|
|
||||||
outVal = 1.0f - expf(-5.0f * phase);
|
|
||||||
break;
|
|
||||||
case SQUARE:
|
|
||||||
outVal = 1.0f;
|
|
||||||
break;
|
|
||||||
case BOUNCE:
|
|
||||||
outVal = fabsf(sinf(phase * 3.14159f * 2.0f));
|
|
||||||
break;
|
|
||||||
case SIGMO:
|
|
||||||
outVal = phase * phase * (3.0f - 2.0f * phase);
|
|
||||||
break;
|
|
||||||
case WOBBLE:
|
|
||||||
outVal = expf(-3.0f * phase) * cosf(phase * 3.14159f * 4.0f);
|
|
||||||
if (outVal < 0)
|
|
||||||
outVal = 0;
|
|
||||||
break;
|
|
||||||
case STEPDW:
|
|
||||||
outVal = 1.0f - (floorf(phase * 4.0f) / 3.0f);
|
|
||||||
break;
|
|
||||||
case STEPUP:
|
|
||||||
outVal = floorf(phase * 4.0f) / 3.0f;
|
|
||||||
break;
|
|
||||||
case SH:
|
|
||||||
outVal = currentRandomVal;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->lastOutVal = outVal;
|
|
||||||
|
|
||||||
// handle width mod
|
|
||||||
float effectiveWidth = (float)width + (widthMod * 100.0f);
|
|
||||||
|
|
||||||
if (effectiveWidth > 100.0f) effectiveWidth = 100.0f;
|
|
||||||
if (effectiveWidth < 1.0f) effectiveWidth = 1.0f;
|
|
||||||
|
|
||||||
double us_per_tick = 625000.0 / (double)BPM;
|
|
||||||
uint32_t modulatedTicks = (uint32_t)((float)this->tickInterval * (effectiveWidth / 100.0f));
|
uint32_t modulatedTicks = (uint32_t)((float)this->tickInterval * (effectiveWidth / 100.0f));
|
||||||
if (modulatedTicks < 1) modulatedTicks = 1;
|
if (modulatedTicks < 1) modulatedTicks = 1;
|
||||||
|
|
||||||
this->pulseDurationUs = (uint32_t)(us_per_tick * (double)modulatedTicks);
|
// This is our "Target" end point
|
||||||
|
this->stopTick = startTick + modulatedTicks;
|
||||||
|
|
||||||
float baseLevel = (float)this->level / 100.0f;
|
// 3. THE HARD SYNC (THE FIX)
|
||||||
|
// If the Master Tick reached stopTick, kill the gate.
|
||||||
|
if (MASTER_TICK >= stopTick) {
|
||||||
|
state = 0;
|
||||||
|
// Don't reset lastTriggerTick here, otherwise turnOn() might re-fire
|
||||||
|
// on the same tick that we just finished.
|
||||||
|
if (!sticky) {
|
||||||
|
lastOutVal = 0.0f;
|
||||||
|
writeAnalog(0);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
float normalizedMod = this->levelMod;
|
// 4. HYBRID SMOOTHNESS MATH
|
||||||
if (normalizedMod > 1.0f || normalizedMod < -1.0f) {
|
uint64_t now = time_us_64();
|
||||||
normalizedMod /= 100.0f;
|
// Ensure we don't underflow if now < last_clk_us (jitter)
|
||||||
}
|
uint64_t usSinceLastTick = (now > last_clk_us) ? (now - last_clk_us) : 0;
|
||||||
|
|
||||||
float finalLevel = baseLevel + normalizedMod;
|
double current_BPM_for_math = (double)filteredBPM;
|
||||||
|
if (current_BPM_for_math < 1.0) current_BPM_for_math = 1.0;
|
||||||
|
|
||||||
if (finalLevel > 1.0f) finalLevel = 1.0f;
|
double us_per_tick = 60000000.0 / (current_BPM_for_math * (double)PPQN);
|
||||||
if (finalLevel < 0.0f) finalLevel = 0.0f;
|
|
||||||
|
|
||||||
writeAnalog((uint16_t)(outVal * 1023.0f * finalLevel));
|
float subTick = (float)usSinceLastTick / (float)us_per_tick;
|
||||||
|
if (subTick > 0.98f) subTick = 0.98f;
|
||||||
|
|
||||||
|
// Calculate phase (0.0 to 1.0)
|
||||||
|
float elapsedTicks = (float)(MASTER_TICK - startTick) + subTick;
|
||||||
|
float totalDurationTicks = (float)(stopTick - startTick);
|
||||||
|
|
||||||
|
// Safety check for division by zero
|
||||||
|
if (totalDurationTicks < 1.0f) totalDurationTicks = 1.0f;
|
||||||
|
|
||||||
|
float phase = elapsedTicks / totalDurationTicks;
|
||||||
|
if (phase > 1.0f) phase = 1.0f;
|
||||||
|
if (phase < 0.0f) phase = 0.0f;
|
||||||
|
|
||||||
|
// 5. WAVEFORM GENERATION
|
||||||
|
float outVal = 0;
|
||||||
|
switch (shape) {
|
||||||
|
case SINE: outVal = (sinf(phase * 2.0f * 3.14159265f) * 0.5f) + 0.5f; break;
|
||||||
|
case HALFSINE: outVal = sinf(phase * 3.14159265f); break;
|
||||||
|
case TRIANGLE: outVal = (phase < 0.5f) ? (phase * 2.0f) : (2.0f - (phase * 2.0f)); break;
|
||||||
|
case SAW: outVal = 1.0f - phase; break;
|
||||||
|
case RAMP: outVal = phase; break;
|
||||||
|
case EXP: outVal = expf(-5.0f * phase); break;
|
||||||
|
case REXP: outVal = expf(5.0f * (phase - 1.0f)); break;
|
||||||
|
case LOG: outVal = 1.0f - expf(-5.0f * phase); break;
|
||||||
|
case SQUARE: outVal = 1.0f; break; // Square is simple ON
|
||||||
|
case BOUNCE: outVal = fabsf(sinf(phase * 3.14159265f * 2.0f)); break;
|
||||||
|
case SIGMO: outVal = phase * phase * (3.0f - 2.0f * phase); break;
|
||||||
|
case WOBBLE: outVal = expf(-3.0f * phase) * cosf(phase * 3.14159265f * 4.0f);
|
||||||
|
if (outVal < 0) outVal = 0; break;
|
||||||
|
case STEPDW: outVal = 1.0f - (floorf(phase * 4.0f) / 3.0f); break;
|
||||||
|
case STEPUP: outVal = floorf(phase * 4.0f) / 3.0f; break;
|
||||||
|
case SH: outVal = currentRandomVal; break;
|
||||||
|
default: outVal = 1.0f; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->lastOutVal = outVal;
|
||||||
|
|
||||||
|
// 6. FINAL LEVEL & MODULATION
|
||||||
|
float finalLevel = ((float)this->level / 100.0f) + (this->levelMod);
|
||||||
|
if (finalLevel > 1.0f) finalLevel = 1.0f;
|
||||||
|
if (finalLevel < 0.0f) finalLevel = 0.0f;
|
||||||
|
|
||||||
|
// Use (uint16_t) cast to ensure the PWM driver gets a clean integer
|
||||||
|
writeAnalog((uint16_t)(outVal * 1023.0f * finalLevel));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gate::writeAnalog(uint16_t val) { pwm_set_gpio_level(pin, val); }
|
void Gate::writeAnalog(uint16_t val) { pwm_set_gpio_level(pin, val); }
|
||||||
|
|
|
||||||
97
src/main.cpp
97
src/main.cpp
|
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
// Time based operations
|
// Time based operations
|
||||||
struct repeating_timer bpm_timer = {0};
|
struct repeating_timer bpm_timer = {0};
|
||||||
volatile uint8_t BPM = 60;
|
volatile float BPM = 60;
|
||||||
volatile bool PLAY = true;
|
volatile bool PLAY = true;
|
||||||
volatile uint32_t period_us = 0;
|
volatile uint32_t period_us = 0;
|
||||||
volatile uint32_t MASTER_TICK;
|
volatile uint32_t MASTER_TICK;
|
||||||
|
|
@ -33,7 +33,11 @@ volatile uint8_t EXTPPQNIdx = 0;
|
||||||
volatile uint64_t last_clk_us = 0;
|
volatile uint64_t last_clk_us = 0;
|
||||||
volatile uint64_t last_valid_clk_us;
|
volatile uint64_t last_valid_clk_us;
|
||||||
volatile bool EXTERNAL_CLOCK = false;
|
volatile bool EXTERNAL_CLOCK = false;
|
||||||
|
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;
|
||||||
|
bool external_pulse_received = false;
|
||||||
ModMatrix matrix;
|
ModMatrix matrix;
|
||||||
|
|
||||||
// Initialize Outputs
|
// Initialize Outputs
|
||||||
|
|
@ -58,6 +62,7 @@ static EncoderHandler encoder_handler(&display_handler);
|
||||||
|
|
||||||
bool timer_callback(struct repeating_timer *t) {
|
bool timer_callback(struct repeating_timer *t) {
|
||||||
if (PLAY == 1) {
|
if (PLAY == 1) {
|
||||||
|
last_clk_us = to_us_since_boot(get_absolute_time());
|
||||||
MASTER_TICK += 1;
|
MASTER_TICK += 1;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -69,7 +74,7 @@ void init_timer(uint32_t period_us) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void update_period() {
|
void update_period() {
|
||||||
period_us = (uint32_t)(MINUTE_US / (uint32_t)BPM / PPQN);
|
period_us = (uint32_t)(MINUTE_US / (float)BPM / PPQN);
|
||||||
init_timer(period_us);
|
init_timer(period_us);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,9 +87,9 @@ void update_BPM(bool up) {
|
||||||
|
|
||||||
update_period();
|
update_period();
|
||||||
|
|
||||||
for (auto g : outputs) {
|
// for (auto g : outputs) {
|
||||||
g->setWidth(g->width);
|
// g->setWidth(g->width);
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (!EXTERNAL_CLOCK) {
|
if (!EXTERNAL_CLOCK) {
|
||||||
init_timer(period_us);
|
init_timer(period_us);
|
||||||
|
|
@ -146,46 +151,34 @@ void handle_outs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void gpio_callback(uint gpio, uint32_t events) {
|
void gpio_callback(uint gpio, uint32_t events) {
|
||||||
// CLK LOGIC
|
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());
|
||||||
|
if (now - last_valid_clk_us < 1000) return;
|
||||||
|
|
||||||
if (now - last_valid_clk_us < 5000) {
|
uint16_t incomingPPQN = PPQNOPTS[EXTPPQNIdx];
|
||||||
return;
|
uint32_t ticks_per_pulse = 96 / incomingPPQN;
|
||||||
}
|
|
||||||
last_valid_clk_us = now;
|
|
||||||
|
|
||||||
uint16_t incomingPPQN;
|
// Instead of jumping to the NEXT beat, we snap to the CLOSEST beat.
|
||||||
|
// This prevents the sequencer from "racing" ahead.
|
||||||
|
MASTER_TICK = ((MASTER_TICK + (ticks_per_pulse / 2)) / ticks_per_pulse) * ticks_per_pulse;
|
||||||
|
|
||||||
|
// BPM Calc
|
||||||
if (last_clk_us > 0) {
|
if (last_clk_us > 0) {
|
||||||
uint64_t diff = now - last_clk_us;
|
uint64_t diff = now - last_clk_us;
|
||||||
incomingPPQN = PPQNOPTS[EXTPPQNIdx];
|
float calculatedBPM = 60000000.0f / (float)(diff * (float)incomingPPQN);
|
||||||
float calculatedBPM = 60000000.0f / (float)(diff * incomingPPQN);
|
filteredBPM = filteredBPM + (0.15f * (calculatedBPM - filteredBPM));
|
||||||
|
BPM = (uint8_t)(filteredBPM + 0.5f);
|
||||||
if (calculatedBPM >= 30 && calculatedBPM <= 255) {
|
|
||||||
if (fabsf((float)BPM - calculatedBPM) > 0.5f) {
|
|
||||||
BPM = (uint8_t)(calculatedBPM + 0.5f);
|
|
||||||
|
|
||||||
update_period();
|
|
||||||
for (auto g : outputs) {
|
|
||||||
g->setWidth(g->width);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
MASTER_TICK += (PPQN / incomingPPQN);
|
|
||||||
last_clk_us = now;
|
last_clk_us = now;
|
||||||
|
last_valid_clk_us = now;
|
||||||
|
last_external_pulse_us = now;
|
||||||
|
EXTERNAL_CLOCK = true;
|
||||||
|
external_pulse_received = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gpio == IN_RUN_PIN) {
|
|
||||||
if (RUN) {
|
|
||||||
if (events & GPIO_IRQ_EDGE_RISE) {
|
|
||||||
PLAY = true;
|
|
||||||
} else if (events & GPIO_IRQ_EDGE_FALL) {
|
|
||||||
PLAY = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
@ -308,19 +301,43 @@ int main() {
|
||||||
if (RUN) {
|
if (RUN) {
|
||||||
PLAY = false;
|
PLAY = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
uint64_t now = to_us_since_boot(get_absolute_time());
|
||||||
|
|
||||||
|
// 1. WATCHDOG: Return to internal clock if pulses stop
|
||||||
|
if (EXTERNAL_CLOCK && (now - last_external_pulse_us > CLOCK_TIMEOUT_US)) {
|
||||||
|
EXTERNAL_CLOCK = false;
|
||||||
|
BPM = globalSettings.bpm;
|
||||||
|
filteredBPM = (float)BPM;
|
||||||
|
update_period(); // Re-engages internal bpm_timer
|
||||||
|
printf("Clock Lost. Internal BPM Resumed.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. EXTERNAL PULSE UI UPDATES
|
||||||
|
if (external_pulse_received) {
|
||||||
|
external_pulse_received = false;
|
||||||
|
|
||||||
|
// This is purely for the screen/UI.
|
||||||
|
// The actual timing is being adjusted inside gpio_callback.
|
||||||
|
BPM = (uint8_t)(filteredBPM + 0.5f);
|
||||||
|
|
||||||
|
if (PLAY) {
|
||||||
|
for (Gate *g : outputs) {
|
||||||
|
g->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. REGULAR TASKS
|
||||||
update_cv();
|
update_cv();
|
||||||
encoder_handler.update();
|
encoder_handler.update();
|
||||||
|
|
||||||
if (PLAY) {
|
if (PLAY) {
|
||||||
handle_outs();
|
handle_outs(); // This runs at the frequency set by our PLL
|
||||||
} else {
|
} else {
|
||||||
for (Gate *g : outputs) {
|
for (Gate *g : outputs) {
|
||||||
g->turnOff();
|
g->turnOff();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lastPlayState = PLAY;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue