diff --git a/include/Gate.h b/include/Gate.h index 3bbbebd..71e56cb 100644 --- a/include/Gate.h +++ b/include/Gate.h @@ -60,6 +60,7 @@ public: WaveShape shape = SQUARE; uint32_t startTick = 0; + uint32_t stopTick = 0; uint32_t pulseWidthTicks = 0; bool sticky = false; int8_t modifierSelectionIndex; diff --git a/include/globals.h b/include/globals.h index dd9fe71..c7b8827 100644 --- a/include/globals.h +++ b/include/globals.h @@ -41,10 +41,13 @@ void gpio_callback(uint gpio, uint32_t events); // TIME BASED extern volatile bool PLAY; -extern volatile uint8_t BPM; +extern volatile float BPM; static constexpr uint32_t MINUTE_US = 60000000; static constexpr uint8_t PPQN = 96; 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; @@ -55,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/DisplayHandler.cpp b/src/DisplayHandler.cpp index 2cd0cf9..1486e70 100644 --- a/src/DisplayHandler.cpp +++ b/src/DisplayHandler.cpp @@ -397,7 +397,7 @@ void DisplayHandler::render() { } void DisplayHandler::renderMainPage() { - std::string bpm_string = "BPM: " + std::to_string(BPM); + std::string bpm_string = "BPM: " + std::to_string((uint8_t)BPM); if (cursorPosition == 0) { if (cursorClick == 1) { diff --git a/src/Gate.cpp b/src/Gate.cpp index 9088c7f..a280dea 100644 --- a/src/Gate.cpp +++ b/src/Gate.cpp @@ -4,16 +4,22 @@ #include "Settings.h" #include "globals.h" #include "hardware/pwm.h" +#include #include #include #include +#include +#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) { this->pin = pin; - this->idx = idx; - this->slotIdx1 = slotIdx1; - this->slotIdx2 = slotIdx2; + this->idx = idx; + this->slotIdx1 = slotIdx1; + this->slotIdx2 = slotIdx2; state = 0; @@ -30,48 +36,48 @@ Gate::Gate(uint8_t pin, uint8_t idx, uint8_t slotIdx1, uint8_t slotIdx2) : Outpu p = 100; // probability of a pulse level = 100; - modDest1 = (idx + 1) % 8; - modDest2 = (idx + 2) % 8; + modDest1 = (idx + 1) % 8; + modDest2 = (idx + 2) % 8; } - void Gate::pack(OutputConfig &cfg) { - cfg.type = TYPE_GATE; + cfg.type = TYPE_GATE; - GateSettings* s = (GateSettings*)cfg.data; + GateSettings *s = (GateSettings *)cfg.data; - s->modifierSelectionIndex = this->modifierSelectionIndex; - s->divideMode = this->divideMode; - s->modifier = this->modifier; - s->width = this->width; - s->p = this->p; - s->level = this->level; - s->shape = (uint8_t)this->shape; + s->modifierSelectionIndex = this->modifierSelectionIndex; + s->divideMode = this->divideMode; + s->modifier = this->modifier; + s->width = this->width; + s->p = this->p; + s->level = this->level; + s->shape = (uint8_t)this->shape; } void Gate::unpack(const OutputConfig &cfg) { - if (cfg.type != TYPE_GATE) return; + if (cfg.type != TYPE_GATE) + return; - GateSettings s; - memcpy(&s, cfg.data, sizeof(GateSettings)); + GateSettings s; + memcpy(&s, cfg.data, sizeof(GateSettings)); - this->modifierSelectionIndex = s.modifierSelectionIndex; - this->divideMode = s.divideMode; - this->modifier = s.modifier; - this->width = s.width; - this->p = s.p; - this->level = s.level; - this->shape = (WaveShape)s.shape; + this->modifierSelectionIndex = s.modifierSelectionIndex; + this->divideMode = s.divideMode; + this->modifier = s.modifier; + this->width = s.width; + this->p = s.p; + this->level = s.level; + this->shape = (WaveShape)s.shape; - setDiv(this->modifierSelectionIndex); - setWidth(this->width); + setDiv(this->modifierSelectionIndex); + setWidth(this->width); } void Gate::setupPatches() { - - matrix.patch(this->slotIdx1, this->modDest1, this->idx, DEST_LEVEL, 100, false); - - matrix.patch(this->slotIdx2, this->modDest2, this->idx, DEST_LEVEL, 100, false); + matrix.patch(this->slotIdx1, this->modDest1, this->idx, DEST_LEVEL, 100, + false); + matrix.patch(this->slotIdx2, this->modDest2, this->idx, DEST_LEVEL, 100, + false); } void Gate::setLen(uint32_t currentPeriod) { @@ -192,8 +198,6 @@ void Gate::setDiv(uint8_t modifier_selecton_index) { this->lastTriggerTick = 0xFFFFFFFF; setWidth(this->width); - // this is called in width, check if needed still? - calculatePulseWidth(); }; void Gate::setWidth(uint16_t newWidth) { @@ -231,8 +235,6 @@ void Gate::calculatePulseWidth() { pulseWidthTicks = 0; return; } - this->pulseWidthTicks = - (uint32_t)((float)this->tickInterval * (this->width / 100.0f)); if (this->width > 0 && this->pulseWidthTicks == 0) { this->pulseWidthTicks = 1; @@ -240,77 +242,117 @@ void Gate::calculatePulseWidth() { } void Gate::turnOn() { - if (!isEnabled || tickInterval == 0) + if (!isEnabled || tickInterval == 0) { return; + } - if (MASTER_TICK % tickInterval == 0) { - if (MASTER_TICK != lastTriggerTick) { - lastTriggerTick = MASTER_TICK; + if (MASTER_TICK % tickInterval == 0 && MASTER_TICK != lastTriggerTick) { + 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 (effectiveP < 0.0f) effectiveP = 0.0f; - - if ((rand() % 100) + 1 > (uint8_t)effectiveP) { - scheduledTick = 0xFFFFFFFF; - 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; - } + if (triggerCount % 2 == 0) { + scheduledTick = MASTER_TICK + (uint32_t)max(0, swingOffset); + } else { + scheduledTick = MASTER_TICK; } } if (MASTER_TICK >= scheduledTick && !state) { state = 1; startTick = MASTER_TICK; - startTimeUs = time_us_64(); - scheduledTick = 0xFFFFFFFF; + + calculatePulseWidth(); + stopTick = startTick + pulseWidthTicks; + + scheduledTick = 0xFFFFFFFF; currentRandomVal = (float)rand() / (float)RAND_MAX; } } void Gate::update() { if (!state && !sticky) { - lastOutVal = 0.0f; - return; - } - - 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); + lastOutVal = 0.0f; + writeAnalog(0); return; } - float phase = (float)elapsedUs / (float)pulseDurationUs; - float outVal = 0; + // width + float effectiveWidth = (float)width + (widthMod * 100.0f); + if (effectiveWidth > 100.0f) + effectiveWidth = 100.0f; + if (effectiveWidth < 0.0f) + effectiveWidth = 0.0f; + uint32_t modulatedTicks = + (uint32_t)((float)this->tickInterval * (effectiveWidth / 100.0f)); + this->stopTick = startTick + modulatedTicks; + + // Only kill the gate if width is strictly less than 100% + if (effectiveWidth < 100.0f) { + if (MASTER_TICK >= stopTick) { + state = 0; + if (!sticky) { + lastOutVal = 0.0f; + writeAnalog(0); + } + return; + } + } + + uint64_t now = time_us_64(); + uint64_t usSinceLastTick = (now > last_clk_us) ? (now - last_clk_us) : 0; + + double current_BPM_for_math = (double)filteredBPM; + if (current_BPM_for_math < 1.0) + current_BPM_for_math = 1.0; + double us_per_tick = 60000000.0 / (current_BPM_for_math * (double)PPQN); + + float subTick = (float)usSinceLastTick / (float)us_per_tick; + if (subTick > 0.99f) + subTick = 0.99f; + + // If width is 100%, calculate phase based on the WHOLE interval. + // If width < 100%, calculate phase based on the PULSE duration. + float elapsedTicks = (float)(MASTER_TICK - startTick) + subTick; + float totalDurationTicks = (effectiveWidth >= 100.0f) + ? (float)tickInterval + : (float)(stopTick - startTick); + + if (totalDurationTicks < 1.0f) + totalDurationTicks = 1.0f; + + float phase = elapsedTicks / totalDurationTicks; + + // Keep phase looping if at 100% width + if (effectiveWidth >= 100.0f) { + while (phase >= 1.0f) + phase -= 1.0f; + } else { + if (phase > 1.0f) + phase = 1.0f; + } + + if (phase < 0.0f) + phase = 0.0f; + + float outVal = 0; switch (shape) { case SINE: - outVal = (sinf(phase * 2.0f * 3.14159f) * 0.5f) + 0.5f; + outVal = (sinf(phase * 2.0f * 3.14159265f) * 0.5f) + 0.5f; break; - case HALFSINE: // AKA HUMP - outVal = sinf(phase * 3.14159f); + case HALFSINE: + outVal = sinf(phase * 3.14159265f); break; case TRIANGLE: outVal = (phase < 0.5f) ? (phase * 2.0f) : (2.0f - (phase * 2.0f)); @@ -334,13 +376,13 @@ void Gate::update() { outVal = 1.0f; break; case BOUNCE: - outVal = fabsf(sinf(phase * 3.14159f * 2.0f)); + 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.14159f * 4.0f); + outVal = expf(-3.0f * phase) * cosf(phase * 3.14159265f * 4.0f); if (outVal < 0) outVal = 0; break; @@ -353,35 +395,20 @@ void Gate::update() { case SH: outVal = currentRandomVal; break; + default: + outVal = 1.0f; + break; } - this->lastOutVal = outVal; + this->lastOutVal = outVal; - // handle width mod - float effectiveWidth = (float)width + (widthMod * 100.0f); + float finalLevel = ((float)this->level / 100.0f) + (this->levelMod); + if (finalLevel > 1.0f) + finalLevel = 1.0f; + if (finalLevel < 0.0f) + finalLevel = 0.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)); - if (modulatedTicks < 1) modulatedTicks = 1; - - this->pulseDurationUs = (uint32_t)(us_per_tick * (double)modulatedTicks); - - float baseLevel = (float)this->level / 100.0f; - - float normalizedMod = this->levelMod; - if (normalizedMod > 1.0f || normalizedMod < -1.0f) { - normalizedMod /= 100.0f; - } - - float finalLevel = baseLevel + normalizedMod; - - if (finalLevel > 1.0f) finalLevel = 1.0f; - if (finalLevel < 0.0f) finalLevel = 0.0f; - - writeAnalog((uint16_t)(outVal * 1023.0f * finalLevel)); + writeAnalog((uint16_t)(outVal * 1023.0f * finalLevel)); } void Gate::writeAnalog(uint16_t val) { pwm_set_gpio_level(pin, val); } diff --git a/src/main.cpp b/src/main.cpp index e24429c..990ff66 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,7 +23,7 @@ // Time based operations struct repeating_timer bpm_timer = {0}; -volatile uint8_t BPM = 60; +volatile float BPM = 60; volatile bool PLAY = true; volatile uint32_t period_us = 0; volatile uint32_t MASTER_TICK; @@ -33,7 +33,15 @@ volatile uint8_t EXTPPQNIdx = 0; volatile uint64_t last_clk_us = 0; volatile uint64_t last_valid_clk_us; volatile bool EXTERNAL_CLOCK = false; +volatile float filteredBPM = 60.0f; +volatile uint64_t last_external_pulse_us = 0; +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; +uint8_t BPM_UI_REFRESH = 0; +bool external_pulse_received = false; ModMatrix matrix; // Initialize Outputs @@ -58,6 +66,7 @@ static EncoderHandler encoder_handler(&display_handler); bool timer_callback(struct repeating_timer *t) { if (PLAY == 1) { + last_clk_us = to_us_since_boot(get_absolute_time()); MASTER_TICK += 1; } return true; @@ -69,7 +78,7 @@ void init_timer(uint32_t period_us) { } 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); } @@ -82,10 +91,6 @@ void update_BPM(bool up) { update_period(); - for (auto g : outputs) { - g->setWidth(g->width); - } - if (!EXTERNAL_CLOCK) { init_timer(period_us); } else { @@ -146,34 +151,66 @@ void handle_outs() { } void gpio_callback(uint gpio, uint32_t events) { - // CLK LOGIC if (gpio == IN_CLK_PIN && (events & GPIO_IRQ_EDGE_RISE)) { uint64_t now = to_us_since_boot(get_absolute_time()); - if (now - last_valid_clk_us < 5000) { + if (now - last_valid_clk_us < 1000) return; - } - last_valid_clk_us = now; - uint16_t incomingPPQN; - if (last_clk_us > 0) { - uint64_t diff = now - last_clk_us; - incomingPPQN = PPQNOPTS[EXTPPQNIdx]; - float calculatedBPM = 60000000.0f / (float)(diff * incomingPPQN); + if (last_external_clk_us > 0) { + uint64_t latest_diff = now - last_external_clk_us; - if (calculatedBPM >= 30 && calculatedBPM <= 255) { - if (fabsf((float)BPM - calculatedBPM) > 0.5f) { - BPM = (uint8_t)(calculatedBPM + 0.5f); + pulse_intervals[pulse_idx] = latest_diff; + pulse_idx = (pulse_idx + 1) % AVG_SAMPLES; - update_period(); - for (auto g : outputs) { - g->setWidth(g->width); + 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]; + + double calculatedBPM = 60000000.0 / (avg_diff * (double)incomingPPQN); + + if (calculatedBPM > 20.0 && calculatedBPM < 300.0) { + float diff = (float)calculatedBPM - filteredBPM; + + if (fabsf(diff) > 5.0f || filteredBPM < 1.0f) { + filteredBPM = (float)calculatedBPM; + } else { + filteredBPM += (0.3f * diff); } } } } - MASTER_TICK += (PPQN / incomingPPQN); + + 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; + } + } + + last_external_clk_us = now; last_clk_us = now; + last_valid_clk_us = now; + last_external_pulse_us = now; + external_pulse_received = true; + EXTERNAL_CLOCK = true; + + BPM_UI_REFRESH += 1; + if (BPM_UI_REFRESH % 4 == 0) { + display_handler.updateScreen = 1; + } } if (gpio == IN_RUN_PIN) { @@ -218,10 +255,8 @@ void setup_ins() { adc_gpio_init(27); } -// Helper to scale your current range to 0.0 - 1.0 float fmap(float x, float in_min, float in_max) { float result = (x - in_min) / (in_max - in_min); - // Constraints to keep it between 0.0 and 1.0 if (result < 0.0f) return 0.0f; if (result > 1.0f) @@ -233,33 +268,24 @@ void update_cv() { static uint64_t last_adc_read = 0; uint64_t now = to_us_since_boot(get_absolute_time()); if (now - last_adc_read < 2000) - return; // 2ms is plenty fast + return; last_adc_read = now; - // Calibration (Adjust these based on your earlier -0.19 to 0.15 range) const float raw_min = -0.19f; const float raw_max = 0.15f; - const float offset_zero = 0.404f; // Your calibrated offset + const float offset_zero = 0.404f; for (int i = 0; i < 2; i++) { adc_select_input(i); - // CROSSTALK FIX: Dummy read to clear the ADC capacitor adc_read(); - busy_wait_us(10); // Tiny pause to settle + busy_wait_us(10); - // Actual read float raw_val = (float)adc_read() * (1.0f / 4095.0f); float centered = offset_zero - raw_val; - // SCALING & FLIPPING: - // By using (max - centered), we flip the inversion. float scaled = (centered - raw_min) / (raw_max - raw_min); - // Optional: If it's STILL upside down, use this instead: - // float scaled = 1.0f - ((centered - raw_min) / (raw_max - raw_min)); - - // Constrain 0.0 to 1.0 if (scaled < 0.01f) scaled = 0.0f; if (scaled > 1.0f) @@ -308,8 +334,39 @@ int main() { if (RUN) { PLAY = false; } - while (true) { + uint64_t now = to_us_since_boot(get_absolute_time()); + + 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"); + } + + 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; + } + + BPM = (uint8_t)(filteredBPM + 0.5f); + + if (PLAY) { + for (Gate *g : outputs) { + g->update(); + } + } + } + update_cv(); encoder_handler.update(); @@ -320,7 +377,5 @@ int main() { g->turnOff(); } } - - lastPlayState = PLAY; } }