diff --git a/CHANGELOG.md b/CHANGELOG.md index a2f6bf9..0869697 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## v2.8.2 + * EvenVCO + * Upsample Hard Sync and FM inputs + * Fix bug when DC option was disabled + ## v2.8.1 * Noise Plethora * Fix bug where program choice is wrongly copied between top and bottom sections diff --git a/plugin.json b/plugin.json index 2450087..d2a2274 100644 --- a/plugin.json +++ b/plugin.json @@ -1,6 +1,6 @@ { "slug": "Befaco", - "version": "2.8.1", + "version": "2.8.2", "license": "GPL-3.0-or-later", "name": "Befaco", "brand": "Befaco", @@ -349,6 +349,8 @@ "slug": "Bandit", "name": "Bandit", "description": "Bandit is a spectral processing playground.", + "manualUrl": "https://www.befaco.org/bandit/", + "modularGridUrl": "https://www.modulargrid.net/e/befaco-bandit", "tags": [ "Equalizer", "Filter", diff --git a/src/EvenVCO.cpp b/src/EvenVCO.cpp index ae2bded..7023eae 100644 --- a/src/EvenVCO.cpp +++ b/src/EvenVCO.cpp @@ -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(c) * 0.25f; const float_4 pitch = inputs[PITCH1_INPUT].getPolyVoltageSimd(c) + inputs[PITCH2_INPUT].getPolyVoltageSimd(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(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(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(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); }