From 25f834cf03236e09872817cd60139563d776ba95 Mon Sep 17 00:00:00 2001 From: hemmer <915048+hemmer@users.noreply.github.com> Date: Sat, 19 Oct 2024 06:24:30 +0100 Subject: [PATCH] Cascaded biquads to get proper EQ slope responses Fix labels #54 --- src/Bandit.cpp | 58 +++++++++++++++++++++++++++++-------------------- src/Octaves.cpp | 12 +++++----- 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/src/Bandit.cpp b/src/Bandit.cpp index 4b4f5b9..64c30af 100644 --- a/src/Bandit.cpp +++ b/src/Bandit.cpp @@ -41,14 +41,16 @@ struct Bandit : Module { LIGHTS_LEN }; - dsp::TBiquadFilter filterLow[4], filterLowMid[4], filterHighMid[4], filterHigh[4]; + // float_4 * [4] give 16 polyphony channels, [2] is for cascading biquads + dsp::TBiquadFilter filterLow[4][2], filterLowMid[4][2], filterHighMid[4][2], filterHigh[4][2]; + Bandit() { config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN); - configParam(LOW_GAIN_PARAM, -1.f, 1.f, 0.f, "Low gain"); - configParam(LOW_MID_GAIN_PARAM, -1.f, 1.f, 0.f, "Low mid gain"); - configParam(HIGH_MID_GAIN_PARAM, -1.f, 1.f, 0.f, "High mid gain"); - configParam(HIGH_GAIN_PARAM, -1.f, 1.f, 0.f, "High gain"); + configParam(LOW_GAIN_PARAM, 0.f, 1.f, 0.f, "Low gain"); + configParam(LOW_MID_GAIN_PARAM, 0.f, 1.f, 0.f, "Low mid gain"); + configParam(HIGH_MID_GAIN_PARAM, 0.f, 1.f, 0.f, "High mid gain"); + configParam(HIGH_GAIN_PARAM, 0.f, 1.f, 0.f, "High gain"); configInput(LOW_INPUT, "Low"); configInput(LOW_MID_INPUT, "Low mid"); @@ -70,8 +72,6 @@ struct Bandit : Module { configOutput(HIGH_MID_OUTPUT, "High mid"); configOutput(HIGH_OUTPUT, "High"); configOutput(MIX_OUTPUT, "Mix"); - - onSampleRateChange(); } void onSampleRateChange() override { @@ -79,14 +79,19 @@ struct Bandit : Module { const float lowFc = 300.f / sr; const float lowMidFc = 750.f / sr; const float highMidFc = 1500.f / sr; - const float highFc = 5000.f / sr; - const float Q = 1.f, V = 1.f; + const float highFc = 3800.f / sr; + // Qs for cascaded biquads to get Butterworth response, see https://www.earlevel.com/main/2016/09/29/cascading-filters/ + // technically only for LOWPASS and HIGHPASS, but seems to work well for BANDPASS too + const float Q[2] = {0.54119610f, 1.3065630f}; + const float V = 1.f; for (int i = 0; i < 4; ++i) { - filterLow[i].setParameters(dsp::TBiquadFilter::Type::LOWPASS, lowFc, Q, V); - filterLowMid[i].setParameters(dsp::TBiquadFilter::Type::BANDPASS, lowMidFc, Q, V); - filterHighMid[i].setParameters(dsp::TBiquadFilter::Type::BANDPASS, highMidFc, Q, V); - filterHigh[i].setParameters(dsp::TBiquadFilter::Type::HIGHPASS, highFc, Q, V); + for (int stage = 0; stage < 2; ++stage) { + filterLow[i][stage].setParameters(dsp::TBiquadFilter::Type::LOWPASS, lowFc, Q[stage], V); + filterLowMid[i][stage].setParameters(dsp::TBiquadFilter::Type::BANDPASS, lowMidFc, Q[stage], V); + filterHighMid[i][stage].setParameters(dsp::TBiquadFilter::Type::BANDPASS, highMidFc, Q[stage], V); + filterHigh[i][stage].setParameters(dsp::TBiquadFilter::Type::HIGHPASS, highFc, Q[stage], V); + } } } @@ -117,7 +122,8 @@ struct Bandit : Module { inputs[LOW_MID_INPUT].getChannels(), inputs[HIGH_MID_INPUT].getChannels(), inputs[HIGH_INPUT].getChannels()}); - + + float_4 mixOutput; for (int c = 0; c < maxPolyphony; c += 4) { const float_4 inLow = inputs[LOW_INPUT].getPolyVoltageSimd(c); @@ -127,30 +133,36 @@ struct Bandit : Module { const float_4 inAll = inputs[ALL_INPUT].getPolyVoltageSimd(c); const float_4 lowGain = params[LOW_GAIN_PARAM].getValue() * inputs[LOW_CV_INPUT].getNormalPolyVoltageSimd(10.f, c) / 10.f; - const float_4 outLow = filterLow[c / 4].process((inLow + inAll) * lowGain); + const float_4 outLow = 0.7 * 2 * filterLow[c / 4][1].process(filterLow[c / 4][0].process((inLow + inAll) * lowGain)); outputs[LOW_OUTPUT].setVoltageSimd(outLow, c); const float_4 lowMidGain = params[LOW_MID_GAIN_PARAM].getValue() * inputs[LOW_MID_CV_INPUT].getNormalPolyVoltageSimd(10.f, c) / 10.f; - const float_4 outLowMid = filterLowMid[c / 4].process((inLowMid + inAll) * lowMidGain); + const float_4 outLowMid = 2 * filterLowMid[c / 4][1].process(filterLowMid[c / 4][0].process((inLowMid + inAll) * lowMidGain)); outputs[LOW_MID_OUTPUT].setVoltageSimd(outLowMid, c); const float_4 highMidGain = params[HIGH_MID_GAIN_PARAM].getValue() * inputs[HIGH_MID_CV_INPUT].getNormalPolyVoltageSimd(10.f, c) / 10.f; - const float_4 outHighMid = filterHighMid[c / 4].process((inHighMid + inAll) * highMidGain); + const float_4 outHighMid = 2 * filterHighMid[c / 4][1].process(filterHighMid[c / 4][0].process((inHighMid + inAll) * highMidGain)); outputs[HIGH_MID_OUTPUT].setVoltageSimd(outHighMid, c); const float_4 highGain = params[HIGH_GAIN_PARAM].getValue() * inputs[HIGH_CV_INPUT].getNormalPolyVoltageSimd(10.f, c) / 10.f; - const float_4 outHigh = filterHigh[c / 4].process((inHigh + inAll) * highGain); + const float_4 outHigh = 0.7 * 2 * filterHigh[c / 4][1].process(filterHigh[c / 4][0].process((inHigh + inAll) * highGain)); outputs[HIGH_OUTPUT].setVoltageSimd(outHigh, c); - const float_4 fxReturnSum = inputs[LOW_RETURN_INPUT].getPolyVoltageSimd(c) + - inputs[LOW_MID_RETURN_INPUT].getPolyVoltageSimd(c) + - inputs[HIGH_MID_RETURN_INPUT].getPolyVoltageSimd(c) + - inputs[HIGH_RETURN_INPUT].getPolyVoltageSimd(c); - outputs[MIX_OUTPUT].setVoltageSimd(fxReturnSum, c); + mixOutput = outputs[LOW_OUTPUT].isConnected() ? inputs[LOW_RETURN_INPUT].getPolyVoltageSimd(c) : outLow; + mixOutput += outputs[LOW_MID_OUTPUT].isConnected() ? inputs[LOW_MID_RETURN_INPUT].getPolyVoltageSimd(c) : outLowMid; + mixOutput += outputs[HIGH_MID_OUTPUT].isConnected() ? inputs[HIGH_MID_RETURN_INPUT].getPolyVoltageSimd(c) : outHighMid; + mixOutput += outputs[HIGH_OUTPUT].isConnected() ? inputs[HIGH_RETURN_INPUT].getPolyVoltageSimd(c) : outHigh; + mixOutput = mixOutput * clamp(inputs[ALL_CV_INPUT].getNormalPolyVoltageSimd(10.f, c) / 10.f, 0.f, 1.f); + + outputs[MIX_OUTPUT].setVoltageSimd(mixOutput, c); } outputs[LOW_OUTPUT].setChannels(maxPolyphony); + outputs[LOW_MID_OUTPUT].setChannels(maxPolyphony); + outputs[HIGH_MID_OUTPUT].setChannels(maxPolyphony); + outputs[HIGH_OUTPUT].setChannels(maxPolyphony); + outputs[MIX_OUTPUT].setChannels(maxPolyphony); if (maxPolyphony == 1) { lights[MIX_LIGHT + 0].setBrightness(0.f); diff --git a/src/Octaves.cpp b/src/Octaves.cpp index 0d1d129..1994271 100644 --- a/src/Octaves.cpp +++ b/src/Octaves.cpp @@ -80,12 +80,12 @@ struct Octaves : Module { configInput(VOCT2_INPUT, "V/Octave 2"); configInput(SYNC_INPUT, "Sync"); configInput(PWM_INPUT, "PWM"); - configInput(GAIN_01F_INPUT, "Gain x1F CV"); - configInput(GAIN_02F_INPUT, "Gain x1F CV"); - configInput(GAIN_04F_INPUT, "Gain x1F CV"); - configInput(GAIN_08F_INPUT, "Gain x1F CV"); - configInput(GAIN_16F_INPUT, "Gain x1F CV"); - configInput(GAIN_32F_INPUT, "Gain x1F CV"); + configInput(GAIN_01F_INPUT, "Gain Fundamental CV"); + configInput(GAIN_02F_INPUT, "Gain x2F CV"); + configInput(GAIN_04F_INPUT, "Gain x4F CV"); + configInput(GAIN_08F_INPUT, "Gain x8F CV"); + configInput(GAIN_16F_INPUT, "Gain x16F CV"); + configInput(GAIN_32F_INPUT, "Gain x32F CV"); configOutput(OUT_01F_OUTPUT, "x1F"); configOutput(OUT_02F_OUTPUT, "x2F");