388 lines
8.5 KiB
C++
388 lines
8.5 KiB
C++
// Gate.cpp
|
|
#include "Gate.h"
|
|
#include "Mod.h"
|
|
#include "Settings.h"
|
|
#include "globals.h"
|
|
#include "hardware/pwm.h"
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <math.h>
|
|
|
|
|
|
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;
|
|
|
|
state = 0;
|
|
|
|
editing = 0;
|
|
|
|
modifierSelectionIndex = 8;
|
|
divideMode = 0; // 1 divison | 0 multiplication
|
|
modifier = 0; // divide mode modifier (4x, /32, etc)
|
|
|
|
dur = 0; // how long pulse is on
|
|
width = 50; // pulse width
|
|
len = 0; // max len a pulse can be on, as determined by width
|
|
|
|
p = 100; // probability of a pulse
|
|
level = 100;
|
|
|
|
modDest1 = (idx + 1) % 8;
|
|
modDest2 = (idx + 2) % 8;
|
|
}
|
|
|
|
|
|
void Gate::pack(OutputConfig &cfg) {
|
|
cfg.type = TYPE_GATE;
|
|
|
|
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;
|
|
}
|
|
|
|
void Gate::unpack(const OutputConfig &cfg) {
|
|
if (cfg.type != TYPE_GATE) return;
|
|
|
|
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;
|
|
|
|
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);
|
|
}
|
|
|
|
void Gate::setLen(uint32_t currentPeriod) {
|
|
len = (uint32_t)((double)currentPeriod * (width / 100.0) / 1000.0);
|
|
}
|
|
|
|
void Gate::setDiv(uint8_t modifier_selecton_index) {
|
|
switch (modifier_selecton_index) {
|
|
case 0: // x32
|
|
tickInterval = 3;
|
|
isEnabled = true;
|
|
divideMode = 0;
|
|
modifier = 32;
|
|
break;
|
|
case 1: // x16
|
|
tickInterval = 6;
|
|
isEnabled = true;
|
|
divideMode = 0;
|
|
modifier = 16;
|
|
break;
|
|
case 2: // x12
|
|
tickInterval = 8;
|
|
isEnabled = true;
|
|
divideMode = 0;
|
|
modifier = 12;
|
|
break;
|
|
case 3: // x8
|
|
tickInterval = 12;
|
|
isEnabled = true;
|
|
divideMode = 0;
|
|
modifier = 8;
|
|
break;
|
|
case 4: // x6
|
|
tickInterval = 16;
|
|
isEnabled = true;
|
|
divideMode = 0;
|
|
modifier = 6;
|
|
break;
|
|
case 5: // x4
|
|
tickInterval = 24;
|
|
isEnabled = true;
|
|
divideMode = 0;
|
|
modifier = 4;
|
|
break;
|
|
case 6: // x3
|
|
tickInterval = 32;
|
|
isEnabled = true;
|
|
divideMode = 0;
|
|
modifier = 3;
|
|
break;
|
|
case 7: // x2
|
|
tickInterval = 48;
|
|
isEnabled = true;
|
|
divideMode = 0;
|
|
modifier = 2;
|
|
break;
|
|
case 8: // 0 (OFF)
|
|
tickInterval = 0;
|
|
isEnabled = false;
|
|
divideMode = 0;
|
|
modifier = 0;
|
|
break;
|
|
case 9: // /1
|
|
tickInterval = 96;
|
|
isEnabled = true;
|
|
divideMode = 1;
|
|
modifier = 1;
|
|
break;
|
|
case 10: // /2
|
|
tickInterval = 192;
|
|
isEnabled = true;
|
|
divideMode = 1;
|
|
modifier = 2;
|
|
break;
|
|
case 11: // /3
|
|
tickInterval = 288;
|
|
isEnabled = true;
|
|
divideMode = 1;
|
|
modifier = 3;
|
|
break;
|
|
case 12: // /4
|
|
tickInterval = 384;
|
|
isEnabled = true;
|
|
divideMode = 1;
|
|
modifier = 4;
|
|
break;
|
|
case 13: // /6
|
|
tickInterval = 576;
|
|
isEnabled = true;
|
|
divideMode = 1;
|
|
modifier = 4;
|
|
break;
|
|
case 14: // /8
|
|
tickInterval = 768;
|
|
isEnabled = true;
|
|
divideMode = 1;
|
|
modifier = 8;
|
|
break;
|
|
case 15: // /16
|
|
tickInterval = 1536;
|
|
isEnabled = true;
|
|
divideMode = 1;
|
|
modifier = 16;
|
|
break;
|
|
case 16: // /32
|
|
tickInterval = 3072;
|
|
isEnabled = true;
|
|
divideMode = 1;
|
|
modifier = 16;
|
|
break;
|
|
default:
|
|
tickInterval = 96;
|
|
isEnabled = true;
|
|
divideMode = 1;
|
|
}
|
|
|
|
this->triggerCount = 0;
|
|
this->lastTriggerTick = 0xFFFFFFFF;
|
|
|
|
setWidth(this->width);
|
|
// this is called in width, check if needed still?
|
|
calculatePulseWidth();
|
|
};
|
|
|
|
void Gate::setWidth(uint16_t newWidth) {
|
|
this->width = newWidth;
|
|
|
|
double ms_per_tick = (60000.0 / (double)BPM) / 96.0;
|
|
|
|
double ms_between_triggers = ms_per_tick * (double)this->tickInterval;
|
|
|
|
this->len = (uint32_t)(ms_between_triggers * (this->width / 100.0));
|
|
|
|
uint32_t max_allowed_len =
|
|
(ms_between_triggers > 5) ? (uint32_t)ms_between_triggers - 2 : 1;
|
|
|
|
if (this->len > max_allowed_len) {
|
|
this->len = max_allowed_len;
|
|
}
|
|
|
|
if (this->len < 1)
|
|
this->len = 1;
|
|
|
|
double us_per_tick = 625000.0 / (double)BPM;
|
|
|
|
calculatePulseWidth();
|
|
|
|
this->pulseDurationUs =
|
|
(uint32_t)(us_per_tick * (double)this->pulseWidthTicks);
|
|
|
|
if (this->pulseDurationUs < 100)
|
|
this->pulseDurationUs = 100;
|
|
}
|
|
|
|
void Gate::calculatePulseWidth() {
|
|
if (tickInterval == 0 || !isEnabled) {
|
|
pulseWidthTicks = 0;
|
|
return;
|
|
}
|
|
this->pulseWidthTicks =
|
|
(uint32_t)((float)this->tickInterval * (this->width / 100.0f));
|
|
|
|
if (this->width > 0 && this->pulseWidthTicks == 0) {
|
|
this->pulseWidthTicks = 1;
|
|
}
|
|
}
|
|
|
|
void Gate::turnOn() {
|
|
if (!isEnabled || tickInterval == 0)
|
|
return;
|
|
|
|
if (MASTER_TICK % tickInterval == 0) {
|
|
if (MASTER_TICK != lastTriggerTick) {
|
|
lastTriggerTick = MASTER_TICK;
|
|
|
|
float baseP = (float)this->p;
|
|
|
|
float effectiveP = baseP + (this->pMod * 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 (MASTER_TICK >= scheduledTick && !state) {
|
|
state = 1;
|
|
startTick = MASTER_TICK;
|
|
startTimeUs = time_us_64();
|
|
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);
|
|
return;
|
|
}
|
|
|
|
float phase = (float)elapsedUs / (float)pulseDurationUs;
|
|
float outVal = 0;
|
|
|
|
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));
|
|
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));}
|
|
|
|
void Gate::writeAnalog(uint16_t val) { pwm_set_gpio_level(pin, val); }
|
|
|
|
void Gate::turnOff() { writeAnalog(0); }
|