Browse Source

Cascaded biquads to get proper EQ slope responses

Fix labels #54
pull/55/head
hemmer 6 months ago
parent
commit
25f834cf03
2 changed files with 41 additions and 29 deletions
  1. +35
    -23
      src/Bandit.cpp
  2. +6
    -6
      src/Octaves.cpp

+ 35
- 23
src/Bandit.cpp View File

@@ -41,14 +41,16 @@ struct Bandit : Module {
LIGHTS_LEN
};

dsp::TBiquadFilter<float_4> filterLow[4], filterLowMid[4], filterHighMid[4], filterHigh[4];
// float_4 * [4] give 16 polyphony channels, [2] is for cascading biquads
dsp::TBiquadFilter<float_4> 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<float_4>::Type::LOWPASS, lowFc, Q, V);
filterLowMid[i].setParameters(dsp::TBiquadFilter<float_4>::Type::BANDPASS, lowMidFc, Q, V);
filterHighMid[i].setParameters(dsp::TBiquadFilter<float_4>::Type::BANDPASS, highMidFc, Q, V);
filterHigh[i].setParameters(dsp::TBiquadFilter<float_4>::Type::HIGHPASS, highFc, Q, V);
for (int stage = 0; stage < 2; ++stage) {
filterLow[i][stage].setParameters(dsp::TBiquadFilter<float_4>::Type::LOWPASS, lowFc, Q[stage], V);
filterLowMid[i][stage].setParameters(dsp::TBiquadFilter<float_4>::Type::BANDPASS, lowMidFc, Q[stage], V);
filterHighMid[i][stage].setParameters(dsp::TBiquadFilter<float_4>::Type::BANDPASS, highMidFc, Q[stage], V);
filterHigh[i][stage].setParameters(dsp::TBiquadFilter<float_4>::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<float_4>(c);
@@ -127,30 +133,36 @@ struct Bandit : Module {
const float_4 inAll = inputs[ALL_INPUT].getPolyVoltageSimd<float_4>(c);

const float_4 lowGain = params[LOW_GAIN_PARAM].getValue() * inputs[LOW_CV_INPUT].getNormalPolyVoltageSimd<float_4>(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<float_4>(outLow, c);

const float_4 lowMidGain = params[LOW_MID_GAIN_PARAM].getValue() * inputs[LOW_MID_CV_INPUT].getNormalPolyVoltageSimd<float_4>(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<float_4>(outLowMid, c);

const float_4 highMidGain = params[HIGH_MID_GAIN_PARAM].getValue() * inputs[HIGH_MID_CV_INPUT].getNormalPolyVoltageSimd<float_4>(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<float_4>(outHighMid, c);

const float_4 highGain = params[HIGH_GAIN_PARAM].getValue() * inputs[HIGH_CV_INPUT].getNormalPolyVoltageSimd<float_4>(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<float_4>(outHigh, c);

const float_4 fxReturnSum = inputs[LOW_RETURN_INPUT].getPolyVoltageSimd<float_4>(c) +
inputs[LOW_MID_RETURN_INPUT].getPolyVoltageSimd<float_4>(c) +
inputs[HIGH_MID_RETURN_INPUT].getPolyVoltageSimd<float_4>(c) +
inputs[HIGH_RETURN_INPUT].getPolyVoltageSimd<float_4>(c);

outputs[MIX_OUTPUT].setVoltageSimd<float_4>(fxReturnSum, c);
mixOutput = outputs[LOW_OUTPUT].isConnected() ? inputs[LOW_RETURN_INPUT].getPolyVoltageSimd<float_4>(c) : outLow;
mixOutput += outputs[LOW_MID_OUTPUT].isConnected() ? inputs[LOW_MID_RETURN_INPUT].getPolyVoltageSimd<float_4>(c) : outLowMid;
mixOutput += outputs[HIGH_MID_OUTPUT].isConnected() ? inputs[HIGH_MID_RETURN_INPUT].getPolyVoltageSimd<float_4>(c) : outHighMid;
mixOutput += outputs[HIGH_OUTPUT].isConnected() ? inputs[HIGH_RETURN_INPUT].getPolyVoltageSimd<float_4>(c) : outHigh;
mixOutput = mixOutput * clamp(inputs[ALL_CV_INPUT].getNormalPolyVoltageSimd<float_4>(10.f, c) / 10.f, 0.f, 1.f);

outputs[MIX_OUTPUT].setVoltageSimd<float_4>(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);


+ 6
- 6
src/Octaves.cpp View File

@@ -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");


Loading…
Cancel
Save