|
|
@@ -43,7 +43,7 @@ struct EvenVCO : Module { |
|
|
|
configInput(PITCH1_INPUT, "Pitch 1"); |
|
|
|
configInput(PITCH2_INPUT, "Pitch 2"); |
|
|
|
configInput(FM_INPUT, "FM"); |
|
|
|
configInput(SYNC_INPUT, "Sync"); |
|
|
|
configInput(SYNC_INPUT, "Hard Sync"); |
|
|
|
configInput(PWM_INPUT, "Pulse Width Modulation"); |
|
|
|
|
|
|
|
configOutput(TRI_OUTPUT, "Triangle"); |
|
|
@@ -52,8 +52,6 @@ struct EvenVCO : Module { |
|
|
|
configOutput(SAW_OUTPUT, "Sawtooth"); |
|
|
|
configOutput(SQUARE_OUTPUT, "Square"); |
|
|
|
|
|
|
|
// calculate up/downsampling rates |
|
|
|
onSampleRateChange(); |
|
|
|
} |
|
|
|
|
|
|
|
void onSampleRateChange() override { |
|
|
@@ -65,6 +63,13 @@ struct EvenVCO : Module { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
for (int c = 0; c < 4; c++) { |
|
|
|
for (int i = 0; i < NUM_UPSAMPLED_INPUTS; i++) { |
|
|
|
oversamplerInputs[i][c].setOversamplingIndex(oversamplingIndex); |
|
|
|
oversamplerInputs[i][c].reset(sampleRate); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
const float lowFreqRegime = oversampler[0][0].getOversamplingRatio() * 1e-3 * sampleRate; |
|
|
|
DEBUG("Low freq regime: %g", lowFreqRegime); |
|
|
|
} |
|
|
@@ -111,6 +116,12 @@ struct EvenVCO : Module { |
|
|
|
return (sawOffsetBuff[0] - 2.0 * sawOffsetBuff[1] + sawOffsetBuff[2]); |
|
|
|
} |
|
|
|
|
|
|
|
enum UpsampledInputs { |
|
|
|
FM_INPUT_UP, |
|
|
|
SYNC_INPUT_UP, |
|
|
|
NUM_UPSAMPLED_INPUTS |
|
|
|
}; |
|
|
|
chowdsp::VariableOversampling<6, float_4> oversamplerInputs[NUM_UPSAMPLED_INPUTS][4]; // uses a 2*6=12th order Butterworth filter |
|
|
|
chowdsp::VariableOversampling<6, float_4> oversampler[NUM_OUTPUTS][4]; // uses a 2*6=12th order Butterworth filter |
|
|
|
int oversamplingIndex = 2; // default is 2^oversamplingIndex == x4 oversampling |
|
|
|
|
|
|
@@ -131,24 +142,30 @@ struct EvenVCO : Module { |
|
|
|
pw = simd::rescale(pw, -1.f, +1.f, 0.f, 1.f); |
|
|
|
} |
|
|
|
|
|
|
|
const float_4 fmVoltage = inputs[FM_INPUT].getPolyVoltageSimd<float_4>(c) * 0.25f; |
|
|
|
const float_4 pitch = inputs[PITCH1_INPUT].getPolyVoltageSimd<float_4>(c) + inputs[PITCH2_INPUT].getPolyVoltageSimd<float_4>(c); |
|
|
|
const float_4 freq = dsp::FREQ_C4 * simd::pow(2.f, pitchKnobs + pitch + fmVoltage); |
|
|
|
const float_4 deltaBasePhase = simd::clamp(freq * args.sampleTime / oversamplingRatio, 1e-6, 0.5f); |
|
|
|
// floating point arithmetic doesn't work well at low frequencies, specifically because the finite difference denominator |
|
|
|
// becomes tiny - we check for that scenario and use naive / 1st order waveforms in that frequency regime (as aliasing isn't |
|
|
|
// a problem there). With no oversampling, at 44100Hz, the threshold frequency is 44.1Hz. |
|
|
|
const float_4 lowFreqRegime = simd::abs(deltaBasePhase) < 1e-3; |
|
|
|
// 1 / denominator for the second-order FD |
|
|
|
const float_4 denominatorInv = 0.25 / (deltaBasePhase * deltaBasePhase); |
|
|
|
|
|
|
|
// pulsewave waveform doesn't have DC even for non 50% duty cycles, but Befaco team would like the option |
|
|
|
// for it to be added back in for hardware compatibility reasons |
|
|
|
const float_4 pulseDCOffset = (!removePulseDC) * 2.f * (0.5f - pw); |
|
|
|
|
|
|
|
// hard sync |
|
|
|
const float_4 syncMask = syncTrigger[c / 4].process(inputs[SYNC_INPUT].getPolyVoltageSimd<float_4>(c)); |
|
|
|
phase[c / 4] = simd::ifelse(syncMask, 0.5f, phase[c / 4]); |
|
|
|
// input oversampling buffers |
|
|
|
float_4* osBufferSync = oversamplerInputs[SYNC_INPUT_UP][c / 4].getOSBuffer(); |
|
|
|
float_4* osBufferFM = oversamplerInputs[FM_INPUT_UP][c / 4].getOSBuffer(); |
|
|
|
|
|
|
|
// upsample hard sync input (if connected) |
|
|
|
if (inputs[SYNC_INPUT].isConnected()) { |
|
|
|
oversamplerInputs[SYNC_INPUT_UP][c].upsample(inputs[SYNC_INPUT].getPolyVoltageSimd<float_4>(c)); |
|
|
|
} |
|
|
|
else { |
|
|
|
std::fill(osBufferSync, &osBufferSync[oversamplingRatio], float_4::zero()); |
|
|
|
} |
|
|
|
// upsample FM input (if connected) |
|
|
|
if (inputs[FM_INPUT].isConnected()) { |
|
|
|
oversamplerInputs[FM_INPUT_UP][c].upsample(inputs[FM_INPUT].getPolyVoltageSimd<float_4>(c)); |
|
|
|
} |
|
|
|
else { |
|
|
|
std::fill(osBufferFM, &osBufferFM[oversamplingRatio], float_4::zero()); |
|
|
|
} |
|
|
|
|
|
|
|
float_4* osBufferTri = oversampler[TRI_OUTPUT][c / 4].getOSBuffer(); |
|
|
|
float_4* osBufferSaw = oversampler[SAW_OUTPUT][c / 4].getOSBuffer(); |
|
|
@@ -156,11 +173,24 @@ struct EvenVCO : Module { |
|
|
|
float_4* osBufferSquare = oversampler[SQUARE_OUTPUT][c / 4].getOSBuffer(); |
|
|
|
float_4* osBufferEven = oversampler[EVEN_OUTPUT][c / 4].getOSBuffer(); |
|
|
|
for (int i = 0; i < oversamplingRatio; ++i) { |
|
|
|
// use upsampled FM input |
|
|
|
const float_4 fmVoltage = osBufferFM[i] * 0.25f; |
|
|
|
const float_4 freq = dsp::FREQ_C4 * simd::pow(2.f, pitchKnobs + pitch + fmVoltage); |
|
|
|
const float_4 deltaBasePhase = simd::clamp(freq * args.sampleTime / oversamplingRatio, 1e-6, 0.5f); |
|
|
|
// floating point arithmetic doesn't work well at low frequencies, specifically because the finite difference denominator |
|
|
|
// becomes tiny - we check for that scenario and use naive / 1st order waveforms in that frequency regime (as aliasing isn't |
|
|
|
// a problem there). With no oversampling, at 44100Hz, the threshold frequency is 44.1Hz. |
|
|
|
const float_4 lowFreqRegime = simd::abs(deltaBasePhase) < 1e-3; |
|
|
|
// 1 / denominator for the second-order FD |
|
|
|
const float_4 denominatorInv = 0.25 / (deltaBasePhase * deltaBasePhase); |
|
|
|
|
|
|
|
phase[c / 4] += deltaBasePhase; |
|
|
|
// ensure within [0, 1] |
|
|
|
phase[c / 4] -= simd::floor(phase[c / 4]); |
|
|
|
|
|
|
|
const float_4 syncMask = syncTrigger[c / 4].process(osBufferSync[i]); |
|
|
|
phase[c / 4] = simd::ifelse(syncMask, 0.5f, phase[c / 4]); |
|
|
|
|
|
|
|
float_4 phases[3]; // phase as extrapolated to the current and two previous samples |
|
|
|
|
|
|
|
phases[0] = phase[c / 4] - 2 * deltaBasePhase + simd::ifelse(phase[c / 4] < 2 * deltaBasePhase, 1.f, 0.f); |
|
|
@@ -188,12 +218,12 @@ struct EvenVCO : Module { |
|
|
|
|
|
|
|
if (outputs[SQUARE_OUTPUT].isConnected()) { |
|
|
|
|
|
|
|
float_4 dpwOrder1 = simd::ifelse(phase[c / 4] < pw, -1.0, +1.0); |
|
|
|
dpwOrder1 -= removePulseDC ? 2.f * (0.5f - pw) : 0.f; |
|
|
|
float_4 dpwOrder1 = simd::ifelse(phase[c / 4] < pw, +1.0, -1.0); |
|
|
|
dpwOrder1 += removePulseDC ? 2.f * (0.5f - pw) : 0.f; |
|
|
|
|
|
|
|
float_4 saw = aliasSuppressedSaw(phases); |
|
|
|
float_4 sawOffset = aliasSuppressedOffsetSaw(phases, pw); |
|
|
|
float_4 dpwOrder3 = (saw - sawOffset) * denominatorInv + pulseDCOffset; |
|
|
|
float_4 dpwOrder3 = (saw - sawOffset) * denominatorInv - pulseDCOffset; |
|
|
|
|
|
|
|
osBufferSquare[i] = simd::ifelse(lowFreqRegime, dpwOrder1, dpwOrder3); |
|
|
|
} |
|
|
|