diff --git a/src/VCF.cpp b/src/VCF.cpp index 6483476..75cd64b 100644 --- a/src/VCF.cpp +++ b/src/VCF.cpp @@ -57,7 +57,6 @@ struct LadderFilter { return state[3]; } T highpass() { - // TODO This is incorrect when `resonance > 0`. Is the math wrong? return clip((input - resonance * state[3]) - 4 * state[0] + 6 * state[1] - 4 * state[2] + state[3]); } }; @@ -97,20 +96,32 @@ struct VCF : Module { VCF() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS); - // Multiply and offset for backward patch compatibility - configParam(FREQ_PARAM, 0.f, 1.f, 0.5f, "Cutoff frequency", " Hz", std::pow(2, 10.f), dsp::FREQ_C4 / std::pow(2, 5.f)); - configParam(FINE_PARAM, 0.f, 1.f, 0.5f, "Fine frequency"); + // To preserve backward compatibility with <2.0, FREQ_PARAM follows + // freq = C4 * 2^(10 * param - 5) + // or + // param = (log2(freq / C4) + 5) / 10 + const float minFreq = (std::log2(dsp::FREQ_C4 / 8000.f) + 5) / 10; + const float maxFreq = (std::log2(8000.f / dsp::FREQ_C4) + 5) / 10; + const float defaultFreq = (0.f + 5) / 10; + configParam(FREQ_PARAM, minFreq, maxFreq, defaultFreq, "Cutoff frequency", " Hz", std::pow(2, 10.f), dsp::FREQ_C4 / std::pow(2, 5.f)); configParam(RES_PARAM, 0.f, 1.f, 0.f, "Resonance", "%", 0.f, 100.f); configParam(RES_CV_PARAM, -1.f, 1.f, 0.f, "Resonance CV", "%", 0.f, 100.f); configParam(FREQ_CV_PARAM, -1.f, 1.f, 0.f, "Cutoff frequency CV", "%", 0.f, 100.f); - configParam(DRIVE_PARAM, 0.f, 1.f, 0.f, "Drive", "", 0, 11); + // gain(drive) = (1 + drive)^5 + // gain(0) = 1 + // gain(-1) = 0 + // gain(1) = 5 + configParam(DRIVE_PARAM, -1.f, 1.f, 0.f, "Drive", "%", 0, 100, 100); configParam(DRIVE_CV_PARAM, -1.f, 1.f, 0.f, "Drive CV", "%", 0, 100); + configInput(FREQ_INPUT, "Frequency"); configInput(RES_INPUT, "Resonance"); configInput(DRIVE_INPUT, "Drive"); configInput(IN_INPUT, "Audio"); + configOutput(LPF_OUTPUT, "Lowpass filter"); configOutput(HPF_OUTPUT, "Highpass filter"); + configBypass(IN_INPUT, LPF_OUTPUT); configBypass(IN_INPUT, HPF_OUTPUT); } @@ -129,24 +140,21 @@ struct VCF : Module { float driveCvParam = params[DRIVE_CV_PARAM].getValue(); float resParam = params[RES_PARAM].getValue(); float resCvParam = params[RES_CV_PARAM].getValue(); - float fineParam = params[FINE_PARAM].getValue(); - fineParam = dsp::quadraticBipolar(fineParam * 2.f - 1.f) * 7.f / 12.f; - float freqCvParam = params[FREQ_CV_PARAM].getValue(); - freqCvParam = dsp::quadraticBipolar(freqCvParam); float freqParam = params[FREQ_PARAM].getValue(); + // Rescale for backward compatibility freqParam = freqParam * 10.f - 5.f; + float freqCvParam = params[FREQ_CV_PARAM].getValue(); int channels = std::max(1, inputs[IN_INPUT].getChannels()); for (int c = 0; c < channels; c += 4) { - auto* filter = &filters[c / 4]; + auto& filter = filters[c / 4]; - float_4 input = float_4::load(inputs[IN_INPUT].getVoltages(c)) / 5.f; + float_4 input = inputs[IN_INPUT].getVoltageSimd(c) / 5.f; // Drive gain - // TODO Make center of knob unity gain, 0 is off like a VCA float_4 drive = driveParam + inputs[DRIVE_INPUT].getPolyVoltageSimd(c) / 10.f * driveCvParam; - drive = clamp(drive, 0.f, 1.f); + drive = clamp(drive, -1.f, 1.f); float_4 gain = simd::pow(1.f + drive, 5); input *= gain; @@ -156,48 +164,59 @@ struct VCF : Module { // Set resonance float_4 resonance = resParam + inputs[RES_INPUT].getPolyVoltageSimd(c) / 10.f * resCvParam; resonance = clamp(resonance, 0.f, 1.f); - filter->resonance = simd::pow(resonance, 2) * 10.f; + filter.resonance = simd::pow(resonance, 2) * 10.f; // Get pitch - float_4 pitch = freqParam + fineParam + inputs[FREQ_INPUT].getPolyVoltageSimd(c) * freqCvParam; + float_4 pitch = freqParam + inputs[FREQ_INPUT].getPolyVoltageSimd(c) * freqCvParam; // Set cutoff float_4 cutoff = dsp::FREQ_C4 * simd::pow(2.f, pitch); - cutoff = clamp(cutoff, 1.f, 8000.f); - filter->setCutoff(cutoff); + // Without oversampling, we must limit to 8000 Hz or so @ 44100 Hz + cutoff = clamp(cutoff, 1.f, args.sampleRate * 0.18f); + filter.setCutoff(cutoff); + + // Upsample input + // float dt = args.sampleTime / UPSAMPLE; + // float inputBuf[UPSAMPLE]; + // float lowpassBuf[UPSAMPLE]; + // float highpassBuf[UPSAMPLE]; + // inputUpsampler.process(input, inputBuf); + // for (int i = 0; i < UPSAMPLE; i++) { + // // Step the filter + // filter.process(inputBuf[i], dt); + // if (outputs[LPF_OUTPUT].isConnected()) + // lowpassBuf[i] = filter.lowpass(); + // if (outputs[HPF_OUTPUT].isConnected()) + // highpassBuf[i] = filter.highpass(); + // } + + // // Set outputs + // if (outputs[LPF_OUTPUT].isConnected()) { + // outputs[LPF_OUTPUT].setVoltage(5.f * lowpassDecimator.process(lowpassBuf)); + // } + // if (outputs[HPF_OUTPUT].isConnected()) { + // outputs[HPF_OUTPUT].setVoltage(5.f * highpassDecimator.process(highpassBuf)); + // } // Set outputs - filter->process(input, args.sampleTime); - float_4 lowpass = 5.f * filter->lowpass(); - lowpass.store(outputs[LPF_OUTPUT].getVoltages(c)); - float_4 highpass = 5.f * filter->highpass(); - highpass.store(outputs[HPF_OUTPUT].getVoltages(c)); + filter.process(input, args.sampleTime); + if (outputs[LPF_OUTPUT].isConnected()) { + outputs[LPF_OUTPUT].setVoltageSimd(5.f * filter.lowpass(), c); + } + if (outputs[HPF_OUTPUT].isConnected()) { + outputs[HPF_OUTPUT].setVoltageSimd(5.f * filter.highpass(), c); + } } outputs[LPF_OUTPUT].setChannels(channels); outputs[HPF_OUTPUT].setChannels(channels); + } - /* - // Process sample - float dt = args.sampleTime / UPSAMPLE; - float inputBuf[UPSAMPLE]; - float lowpassBuf[UPSAMPLE]; - float highpassBuf[UPSAMPLE]; - inputUpsampler.process(input, inputBuf); - for (int i = 0; i < UPSAMPLE; i++) { - // Step the filter - filter.process(inputBuf[i], dt); - lowpassBuf[i] = filter.lowpass; - highpassBuf[i] = filter.highpass; - } + void paramsFromJson(json_t* rootJ) override { + // These attenuators didn't exist in version <2.0, so set to 1 in case they are not overwritten. + params[RES_CV_PARAM].setValue(1.f); + params[DRIVE_CV_PARAM].setValue(1.f); - // Set outputs - if (outputs[LPF_OUTPUT].isConnected()) { - outputs[LPF_OUTPUT].setVoltage(5.f * lowpassDecimator.process(lowpassBuf)); - } - if (outputs[HPF_OUTPUT].isConnected()) { - outputs[HPF_OUTPUT].setVoltage(5.f * highpassDecimator.process(highpassBuf)); - } - */ + Module::paramsFromJson(rootJ); } };