cleanup
This commit is contained in:
parent
ed79c8e3b9
commit
67922bdd56
3 changed files with 171 additions and 145 deletions
|
|
@ -397,7 +397,7 @@ void DisplayHandler::render() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DisplayHandler::renderMainPage() {
|
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 (cursorPosition == 0) {
|
||||||
if (cursorClick == 1) {
|
if (cursorClick == 1) {
|
||||||
|
|
|
||||||
227
src/Gate.cpp
227
src/Gate.cpp
|
|
@ -9,18 +9,17 @@
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <pico/types.h>
|
#include <pico/types.h>
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#ifndef max
|
#ifndef max
|
||||||
#define max(a,b) (((a) > (b)) ? (a) : (b))
|
#define max(a, b) (((a) > (b)) ? (a) : (b))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
Gate::Gate(uint8_t pin, uint8_t idx, uint8_t slotIdx1, uint8_t slotIdx2)
|
||||||
Gate::Gate(uint8_t pin, uint8_t idx, uint8_t slotIdx1, uint8_t slotIdx2) : Output(pin, idx, slotIdx1, slotIdx2) {
|
: Output(pin, idx, slotIdx1, slotIdx2) {
|
||||||
this->pin = pin;
|
this->pin = pin;
|
||||||
this->idx = idx;
|
this->idx = idx;
|
||||||
this->slotIdx1 = slotIdx1;
|
this->slotIdx1 = slotIdx1;
|
||||||
this->slotIdx2 = slotIdx2;
|
this->slotIdx2 = slotIdx2;
|
||||||
|
|
||||||
state = 0;
|
state = 0;
|
||||||
|
|
||||||
|
|
@ -37,48 +36,48 @@ Gate::Gate(uint8_t pin, uint8_t idx, uint8_t slotIdx1, uint8_t slotIdx2) : Outpu
|
||||||
p = 100; // probability of a pulse
|
p = 100; // probability of a pulse
|
||||||
level = 100;
|
level = 100;
|
||||||
|
|
||||||
modDest1 = (idx + 1) % 8;
|
modDest1 = (idx + 1) % 8;
|
||||||
modDest2 = (idx + 2) % 8;
|
modDest2 = (idx + 2) % 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Gate::pack(OutputConfig &cfg) {
|
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->modifierSelectionIndex = this->modifierSelectionIndex;
|
||||||
s->divideMode = this->divideMode;
|
s->divideMode = this->divideMode;
|
||||||
s->modifier = this->modifier;
|
s->modifier = this->modifier;
|
||||||
s->width = this->width;
|
s->width = this->width;
|
||||||
s->p = this->p;
|
s->p = this->p;
|
||||||
s->level = this->level;
|
s->level = this->level;
|
||||||
s->shape = (uint8_t)this->shape;
|
s->shape = (uint8_t)this->shape;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gate::unpack(const OutputConfig &cfg) {
|
void Gate::unpack(const OutputConfig &cfg) {
|
||||||
if (cfg.type != TYPE_GATE) return;
|
if (cfg.type != TYPE_GATE)
|
||||||
|
return;
|
||||||
|
|
||||||
GateSettings s;
|
GateSettings s;
|
||||||
memcpy(&s, cfg.data, sizeof(GateSettings));
|
memcpy(&s, cfg.data, sizeof(GateSettings));
|
||||||
|
|
||||||
this->modifierSelectionIndex = s.modifierSelectionIndex;
|
this->modifierSelectionIndex = s.modifierSelectionIndex;
|
||||||
this->divideMode = s.divideMode;
|
this->divideMode = s.divideMode;
|
||||||
this->modifier = s.modifier;
|
this->modifier = s.modifier;
|
||||||
this->width = s.width;
|
this->width = s.width;
|
||||||
this->p = s.p;
|
this->p = s.p;
|
||||||
this->level = s.level;
|
this->level = s.level;
|
||||||
this->shape = (WaveShape)s.shape;
|
this->shape = (WaveShape)s.shape;
|
||||||
|
|
||||||
setDiv(this->modifierSelectionIndex);
|
setDiv(this->modifierSelectionIndex);
|
||||||
setWidth(this->width);
|
setWidth(this->width);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gate::setupPatches() {
|
void Gate::setupPatches() {
|
||||||
|
matrix.patch(this->slotIdx1, this->modDest1, this->idx, DEST_LEVEL, 100,
|
||||||
matrix.patch(this->slotIdx1, this->modDest1, this->idx, DEST_LEVEL, 100, false);
|
false);
|
||||||
|
matrix.patch(this->slotIdx2, this->modDest2, this->idx, DEST_LEVEL, 100,
|
||||||
matrix.patch(this->slotIdx2, this->modDest2, this->idx, DEST_LEVEL, 100, false);
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Gate::setLen(uint32_t currentPeriod) {
|
void Gate::setLen(uint32_t currentPeriod) {
|
||||||
|
|
@ -199,8 +198,6 @@ void Gate::setDiv(uint8_t modifier_selecton_index) {
|
||||||
this->lastTriggerTick = 0xFFFFFFFF;
|
this->lastTriggerTick = 0xFFFFFFFF;
|
||||||
|
|
||||||
setWidth(this->width);
|
setWidth(this->width);
|
||||||
// this is called in width, check if needed still?
|
|
||||||
calculatePulseWidth();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void Gate::setWidth(uint16_t newWidth) {
|
void Gate::setWidth(uint16_t newWidth) {
|
||||||
|
|
@ -239,22 +236,16 @@ void Gate::calculatePulseWidth() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) return;
|
if (!isEnabled || tickInterval == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Trigger on the interval, ensuring we don't double-trigger on the same tick
|
|
||||||
if (MASTER_TICK % tickInterval == 0 && MASTER_TICK != lastTriggerTick) {
|
if (MASTER_TICK % tickInterval == 0 && MASTER_TICK != lastTriggerTick) {
|
||||||
lastTriggerTick = MASTER_TICK;
|
lastTriggerTick = MASTER_TICK;
|
||||||
|
|
||||||
|
|
@ -267,46 +258,46 @@ void Gate::turnOn() {
|
||||||
|
|
||||||
// Swing
|
// Swing
|
||||||
triggerCount++;
|
triggerCount++;
|
||||||
int32_t swingOffset = (int32_t)((float)tickInterval * ((float)swing - 50.0f) / 100.0f);
|
int32_t swingOffset =
|
||||||
|
(int32_t)((float)tickInterval * ((float)swing - 50.0f) / 100.0f);
|
||||||
|
|
||||||
if (triggerCount % 2 == 0) {
|
if (triggerCount % 2 == 0) {
|
||||||
// Use max(0, offset) to prevent early triggers from breaking the state machine
|
|
||||||
scheduledTick = MASTER_TICK + (uint32_t)max(0, swingOffset);
|
scheduledTick = MASTER_TICK + (uint32_t)max(0, swingOffset);
|
||||||
} else {
|
} else {
|
||||||
scheduledTick = MASTER_TICK;
|
scheduledTick = MASTER_TICK;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execution
|
|
||||||
if (MASTER_TICK >= scheduledTick && !state) {
|
if (MASTER_TICK >= scheduledTick && !state) {
|
||||||
state = 1;
|
state = 1;
|
||||||
startTick = MASTER_TICK;
|
startTick = MASTER_TICK;
|
||||||
|
|
||||||
calculatePulseWidth();
|
calculatePulseWidth();
|
||||||
stopTick = startTick + pulseWidthTicks;
|
stopTick = startTick + pulseWidthTicks;
|
||||||
|
|
||||||
scheduledTick = 0xFFFFFFFF;
|
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;
|
||||||
writeAnalog(0);
|
writeAnalog(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. LIVE WIDTH MODULATION
|
// width
|
||||||
float effectiveWidth = (float)width + (widthMod * 100.0f);
|
float effectiveWidth = (float)width + (widthMod * 100.0f);
|
||||||
if (effectiveWidth > 100.0f) effectiveWidth = 100.0f;
|
if (effectiveWidth > 100.0f)
|
||||||
if (effectiveWidth < 0.0f) effectiveWidth = 0.0f;
|
effectiveWidth = 100.0f;
|
||||||
|
if (effectiveWidth < 0.0f)
|
||||||
|
effectiveWidth = 0.0f;
|
||||||
|
|
||||||
uint32_t modulatedTicks = (uint32_t)((float)this->tickInterval * (effectiveWidth / 100.0f));
|
uint32_t modulatedTicks =
|
||||||
|
(uint32_t)((float)this->tickInterval * (effectiveWidth / 100.0f));
|
||||||
this->stopTick = startTick + modulatedTicks;
|
this->stopTick = startTick + modulatedTicks;
|
||||||
|
|
||||||
// 3. THE HARD SYNC
|
|
||||||
// Only kill the gate if width is strictly less than 100%
|
// Only kill the gate if width is strictly less than 100%
|
||||||
if (effectiveWidth < 100.0f) {
|
if (effectiveWidth < 100.0f) {
|
||||||
if (MASTER_TICK >= stopTick) {
|
if (MASTER_TICK >= stopTick) {
|
||||||
|
|
@ -319,63 +310,103 @@ void Gate::update() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. HYBRID SMOOTHNESS MATH
|
|
||||||
uint64_t now = time_us_64();
|
uint64_t now = time_us_64();
|
||||||
uint64_t usSinceLastTick = (now > last_clk_us) ? (now - last_clk_us) : 0;
|
uint64_t usSinceLastTick = (now > last_clk_us) ? (now - last_clk_us) : 0;
|
||||||
|
|
||||||
double current_BPM_for_math = (double)filteredBPM;
|
double current_BPM_for_math = (double)filteredBPM;
|
||||||
if (current_BPM_for_math < 1.0) current_BPM_for_math = 1.0;
|
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);
|
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;
|
|
||||||
|
|
||||||
// --- THE SINE WAVE FIX ---
|
float subTick = (float)usSinceLastTick / (float)us_per_tick;
|
||||||
// If width is 100%, we calculate phase based on the WHOLE interval.
|
if (subTick > 0.99f)
|
||||||
// If width < 100%, we calculate phase based on the PULSE duration.
|
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 elapsedTicks = (float)(MASTER_TICK - startTick) + subTick;
|
||||||
float totalDurationTicks = (effectiveWidth >= 100.0f) ? (float)tickInterval : (float)(stopTick - startTick);
|
float totalDurationTicks = (effectiveWidth >= 100.0f)
|
||||||
|
? (float)tickInterval
|
||||||
if (totalDurationTicks < 1.0f) totalDurationTicks = 1.0f;
|
: (float)(stopTick - startTick);
|
||||||
|
|
||||||
|
if (totalDurationTicks < 1.0f)
|
||||||
|
totalDurationTicks = 1.0f;
|
||||||
|
|
||||||
float phase = elapsedTicks / totalDurationTicks;
|
float phase = elapsedTicks / totalDurationTicks;
|
||||||
|
|
||||||
// Keep phase looping if we are at 100% width (so LFOs/Sines keep moving)
|
// Keep phase looping if at 100% width
|
||||||
if (effectiveWidth >= 100.0f) {
|
if (effectiveWidth >= 100.0f) {
|
||||||
while (phase >= 1.0f) phase -= 1.0f;
|
while (phase >= 1.0f)
|
||||||
|
phase -= 1.0f;
|
||||||
} else {
|
} else {
|
||||||
if (phase > 1.0f) phase = 1.0f;
|
if (phase > 1.0f)
|
||||||
|
phase = 1.0f;
|
||||||
}
|
}
|
||||||
if (phase < 0.0f) phase = 0.0f;
|
|
||||||
|
|
||||||
// 5. WAVEFORM GENERATION
|
if (phase < 0.0f)
|
||||||
|
phase = 0.0f;
|
||||||
|
|
||||||
float outVal = 0;
|
float outVal = 0;
|
||||||
switch (shape) {
|
switch (shape) {
|
||||||
case SINE: outVal = (sinf(phase * 2.0f * 3.14159265f) * 0.5f) + 0.5f; break;
|
case SINE:
|
||||||
case HALFSINE: outVal = sinf(phase * 3.14159265f); break;
|
outVal = (sinf(phase * 2.0f * 3.14159265f) * 0.5f) + 0.5f;
|
||||||
case TRIANGLE: outVal = (phase < 0.5f) ? (phase * 2.0f) : (2.0f - (phase * 2.0f)); break;
|
break;
|
||||||
case SAW: outVal = 1.0f - phase; break;
|
case HALFSINE:
|
||||||
case RAMP: outVal = phase; break;
|
outVal = sinf(phase * 3.14159265f);
|
||||||
case EXP: outVal = expf(-5.0f * phase); break;
|
break;
|
||||||
case REXP: outVal = expf(5.0f * (phase - 1.0f)); break;
|
case TRIANGLE:
|
||||||
case LOG: outVal = 1.0f - expf(-5.0f * phase); break;
|
outVal = (phase < 0.5f) ? (phase * 2.0f) : (2.0f - (phase * 2.0f));
|
||||||
case SQUARE: outVal = 1.0f; break;
|
break;
|
||||||
case BOUNCE: outVal = fabsf(sinf(phase * 3.14159265f * 2.0f)); break;
|
case SAW:
|
||||||
case SIGMO: outVal = phase * phase * (3.0f - 2.0f * phase); break;
|
outVal = 1.0f - phase;
|
||||||
case WOBBLE: outVal = expf(-3.0f * phase) * cosf(phase * 3.14159265f * 4.0f);
|
break;
|
||||||
if (outVal < 0) outVal = 0; break;
|
case RAMP:
|
||||||
case STEPDW: outVal = 1.0f - (floorf(phase * 4.0f) / 3.0f); break;
|
outVal = phase;
|
||||||
case STEPUP: outVal = floorf(phase * 4.0f) / 3.0f; break;
|
break;
|
||||||
case SH: outVal = currentRandomVal; break;
|
case EXP:
|
||||||
default: outVal = 1.0f; break;
|
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.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;
|
this->lastOutVal = outVal;
|
||||||
|
|
||||||
// 6. FINAL LEVEL & MODULATION
|
float finalLevel = ((float)this->level / 100.0f) + (this->levelMod);
|
||||||
float finalLevel = ((float)this->level / 100.0f) + (this->levelMod);
|
if (finalLevel > 1.0f)
|
||||||
if (finalLevel > 1.0f) finalLevel = 1.0f;
|
finalLevel = 1.0f;
|
||||||
if (finalLevel < 0.0f) finalLevel = 0.0f;
|
if (finalLevel < 0.0f)
|
||||||
|
finalLevel = 0.0f;
|
||||||
|
|
||||||
writeAnalog((uint16_t)(outVal * 1023.0f * finalLevel));
|
writeAnalog((uint16_t)(outVal * 1023.0f * finalLevel));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
87
src/main.cpp
87
src/main.cpp
|
|
@ -33,13 +33,13 @@ 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 float filteredBPM = 60.0f;
|
||||||
volatile uint64_t last_external_pulse_us =
|
volatile uint64_t last_external_pulse_us = 0;
|
||||||
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};
|
uint64_t pulse_intervals[AVG_SAMPLES] = {0};
|
||||||
uint8_t pulse_idx = 0;
|
uint8_t pulse_idx = 0;
|
||||||
uint64_t last_external_clk_us = 0;
|
uint64_t last_external_clk_us = 0;
|
||||||
|
uint8_t BPM_UI_REFRESH = 0;
|
||||||
|
|
||||||
bool external_pulse_received = false;
|
bool external_pulse_received = false;
|
||||||
ModMatrix matrix;
|
ModMatrix matrix;
|
||||||
|
|
@ -91,10 +91,6 @@ void update_BPM(bool up) {
|
||||||
|
|
||||||
update_period();
|
update_period();
|
||||||
|
|
||||||
// for (auto g : outputs) {
|
|
||||||
// g->setWidth(g->width);
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (!EXTERNAL_CLOCK) {
|
if (!EXTERNAL_CLOCK) {
|
||||||
init_timer(period_us);
|
init_timer(period_us);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -155,13 +151,12 @@ 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;
|
|
||||||
|
|
||||||
// 2. BPM Math - Use 'last_external_clk_us' specifically
|
if (now - last_valid_clk_us < 1000)
|
||||||
|
return;
|
||||||
|
|
||||||
if (last_external_clk_us > 0) {
|
if (last_external_clk_us > 0) {
|
||||||
uint64_t latest_diff = now - last_external_clk_us;
|
uint64_t latest_diff = now - last_external_clk_us;
|
||||||
|
|
||||||
|
|
@ -177,23 +172,27 @@ if (gpio == IN_CLK_PIN && (events & GPIO_IRQ_EDGE_RISE)) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
double avg_diff = (double)sum / (double)count;
|
double avg_diff = (double)sum / (double)count;
|
||||||
uint16_t incomingPPQN = PPQNOPTS[EXTPPQNIdx];
|
uint16_t incomingPPQN = PPQNOPTS[EXTPPQNIdx];
|
||||||
|
|
||||||
// Use 60,000,000.0 (double) to ensure high precision
|
|
||||||
double calculatedBPM = 60000000.0 / (avg_diff * (double)incomingPPQN);
|
double calculatedBPM = 60000000.0 / (avg_diff * (double)incomingPPQN);
|
||||||
|
|
||||||
if (calculatedBPM > 20.0 && calculatedBPM < 300.0) {
|
if (calculatedBPM > 20.0 && calculatedBPM < 300.0) {
|
||||||
// Slow down the LPF even more (0.05 instead of 0.1)
|
float diff = (float)calculatedBPM - filteredBPM;
|
||||||
// This makes the screen feel "heavy" and professional like a real synth
|
|
||||||
filteredBPM = filteredBPM + (0.05f * ((float)calculatedBPM - filteredBPM));
|
if (fabsf(diff) > 5.0f || filteredBPM < 1.0f) {
|
||||||
}
|
filteredBPM = (float)calculatedBPM;
|
||||||
}}
|
} else {
|
||||||
|
filteredBPM += (0.3f * diff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 3. Sync Logic
|
|
||||||
uint32_t ticks_per_pulse = 96 / PPQNOPTS[EXTPPQNIdx];
|
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;
|
||||||
|
|
||||||
for (int i = 0; i < 8; i++) {
|
for (int i = 0; i < 8; i++) {
|
||||||
if (outputs[i]->lastTriggerTick > MASTER_TICK) {
|
if (outputs[i]->lastTriggerTick > MASTER_TICK) {
|
||||||
|
|
@ -201,17 +200,29 @@ if (gpio == IN_CLK_PIN && (events & GPIO_IRQ_EDGE_RISE)) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Update Timestamps
|
last_external_clk_us = now;
|
||||||
last_external_clk_us = now; // Store for next external pulse
|
last_clk_us = now;
|
||||||
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_pulse_received = true;
|
external_pulse_received = true;
|
||||||
EXTERNAL_CLOCK = true;
|
EXTERNAL_CLOCK = true;
|
||||||
|
|
||||||
|
BPM_UI_REFRESH += 1;
|
||||||
|
if (BPM_UI_REFRESH % 4 == 0) {
|
||||||
|
display_handler.updateScreen = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
@ -244,10 +255,8 @@ void setup_ins() {
|
||||||
adc_gpio_init(27);
|
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 fmap(float x, float in_min, float in_max) {
|
||||||
float result = (x - in_min) / (in_max - in_min);
|
float result = (x - in_min) / (in_max - in_min);
|
||||||
// Constraints to keep it between 0.0 and 1.0
|
|
||||||
if (result < 0.0f)
|
if (result < 0.0f)
|
||||||
return 0.0f;
|
return 0.0f;
|
||||||
if (result > 1.0f)
|
if (result > 1.0f)
|
||||||
|
|
@ -259,33 +268,24 @@ void update_cv() {
|
||||||
static uint64_t last_adc_read = 0;
|
static uint64_t last_adc_read = 0;
|
||||||
uint64_t now = to_us_since_boot(get_absolute_time());
|
uint64_t now = to_us_since_boot(get_absolute_time());
|
||||||
if (now - last_adc_read < 2000)
|
if (now - last_adc_read < 2000)
|
||||||
return; // 2ms is plenty fast
|
return;
|
||||||
last_adc_read = now;
|
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_min = -0.19f;
|
||||||
const float raw_max = 0.15f;
|
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++) {
|
for (int i = 0; i < 2; i++) {
|
||||||
adc_select_input(i);
|
adc_select_input(i);
|
||||||
|
|
||||||
// CROSSTALK FIX: Dummy read to clear the ADC capacitor
|
|
||||||
adc_read();
|
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 raw_val = (float)adc_read() * (1.0f / 4095.0f);
|
||||||
float centered = offset_zero - raw_val;
|
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);
|
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)
|
if (scaled < 0.01f)
|
||||||
scaled = 0.0f;
|
scaled = 0.0f;
|
||||||
if (scaled > 1.0f)
|
if (scaled > 1.0f)
|
||||||
|
|
@ -337,7 +337,6 @@ int main() {
|
||||||
while (true) {
|
while (true) {
|
||||||
uint64_t now = to_us_since_boot(get_absolute_time());
|
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)) {
|
if (EXTERNAL_CLOCK && (now - last_external_pulse_us > CLOCK_TIMEOUT_US)) {
|
||||||
EXTERNAL_CLOCK = false;
|
EXTERNAL_CLOCK = false;
|
||||||
BPM = globalSettings.bpm;
|
BPM = globalSettings.bpm;
|
||||||
|
|
@ -346,7 +345,6 @@ int main() {
|
||||||
printf("Clock Lost. Internal BPM Resumed.\n");
|
printf("Clock Lost. Internal BPM Resumed.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. EXTERNAL PULSE UI UPDATES
|
|
||||||
if (external_pulse_received) {
|
if (external_pulse_received) {
|
||||||
external_pulse_received = false;
|
external_pulse_received = false;
|
||||||
|
|
||||||
|
|
@ -360,8 +358,6 @@ int main() {
|
||||||
last_ppqn_idx = EXTPPQNIdx;
|
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);
|
BPM = (uint8_t)(filteredBPM + 0.5f);
|
||||||
|
|
||||||
if (PLAY) {
|
if (PLAY) {
|
||||||
|
|
@ -371,12 +367,11 @@ int main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. REGULAR TASKS
|
|
||||||
update_cv();
|
update_cv();
|
||||||
encoder_handler.update();
|
encoder_handler.update();
|
||||||
|
|
||||||
if (PLAY) {
|
if (PLAY) {
|
||||||
handle_outs(); // This runs at the frequency set by our PLL
|
handle_outs();
|
||||||
} else {
|
} else {
|
||||||
for (Gate *g : outputs) {
|
for (Gate *g : outputs) {
|
||||||
g->turnOff();
|
g->turnOff();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue