diff --git a/src/VCF.cpp b/src/VCF.cpp index dbbb0ef..754aa0e 100644 --- a/src/VCF.cpp +++ b/src/VCF.cpp @@ -18,157 +18,152 @@ // The clipping function of a transistor pair is approximately tanh(x) // This one is a Pade-approx for tanh(sqrt(x))/sqrt(x) inline float clip(float x) { - float a = x*x; - return ((a + 105)*a + 945) / ((15*a + 420)*a + 945); + float a = x*x; + return ((a + 105)*a + 945) / ((15*a + 420)*a + 945); } struct LadderFilter { - float g = 0.1f; - float resonance = 0.5f; - float state[4] = {}; - float zi = 0.f; - float output[3] = {}; - - void process(float input) { - // input with half delay, for non-linearities - const float ih = 0.5f * (input + zi); - - // evaluate the non-linear gains - const float t0 = g * clip(ih - resonance * state[3]); - const float t1 = g * clip(state[0]); - const float t2 = g * clip(state[1]); - const float t3 = g * clip(state[2]); - const float t4 = g * clip(state[3]); - - // update last LP1 output - const float t2t3 = t2*t3; - - float y3 = (s[3]*(1+t3) + s[2]*t3)*(1+t2); - y3 = (y3 + t2t3*s[1])*(1+t1); - y3 = (y3 + t1*t2t3*(s[0]+t0*input)); - y3 = y3 / ((1+t1)*(1+t2)*(1+t3)*(1+t4) + resonance*t0*t1*t2t3); - - // update other LP1 outputs - const float xx = t0 * (input - resonance * y3); - const float y0 = t1 * (s[0] + xx) / (1+t1); - const float y1 = t2 * (s[1] + y0) / (1+t2); - const float y2 = t3 * (s[2] + y1) / (1+t3); - - // update states - s[0] += 2 * (xx - y0); - s[1] += 2 * (y0 - y1); - s[2] += 2 * (y1 - y2); - s[3] += 2 * (y2 - t4*y3); - - // returns LP, HP and BP outputs - float y1t2 = y1/t2; - float y2t3 = y2/t3; - - output[0] = y3; - output[1] = xx/t0 - 4*y0/t1 + 6*y1t2 - 4*y2t3 + y3; - output[2] = y1t2 - 2*y2t3 + y3; - - // update delay input state - zi = input; - } - - void reset() { - for (int i = 0; i < 4; i++) { - state[i] = 0.0f; - } - zi = 0.f; - } + float g = 0.1f; + float resonance = 0.5f; + float state[4] = {}; + float zi = 0.f; + float output[3] = {}; + + void process(float input) { + // input with half delay, for non-linearities + const float ih = 0.5f * (input + zi); + + // evaluate the non-linear gains + const float t0 = g * clip(ih - resonance * state[3]); + const float t1 = g * clip(state[0]); + const float t2 = g * clip(state[1]); + const float t3 = g * clip(state[2]); + const float t4 = g * clip(state[3]); + + // update last LP1 output + float y3 = (s[3]*(1+t3) + s[2]*t3)*(1+t2); + y3 = (y3 + t2*t3*s[1])*(1+t1); + y3 = (y3 + t1*t2*t3*(s[0]+t0*input)); + y3 = y3 / ((1+t1)*(1+t2)*(1+t3)*(1+t4) + resonance*t0*t1*t2*t3); + + // update other LP1 outputs + const float xx = t0 * (input - resonance * y3); + const float y0 = t1 * (s[0] + xx) / (1+t1); + const float y1 = t2 * (s[1] + y0) / (1+t2); + const float y2 = t3 * (s[2] + y1) / (1+t3); + + // update states + s[0] += 2 * (xx - y0); + s[1] += 2 * (y0 - y1); + s[2] += 2 * (y1 - y2); + s[3] += 2 * (y2 - t4*y3); + + // returns LP, HP and BP outputs + output[0] = y3; + output[1] = xx/t0 - 4*y0/t1 + 6*y1/t2 - 4*y2/t3 + y3; + output[2] = y1/t2 - 2*y2/t3 + y3; + + // update delay input state + zi = input; + } + + void reset() { + for (int i = 0; i < 4; i++) { + state[i] = 0.0f; + } + zi = 0.f; + } }; struct VCF : Module { - enum ParamIds { - FREQ_PARAM, - FINE_PARAM, - RES_PARAM, - FREQ_CV_PARAM, - DRIVE_PARAM, - NUM_PARAMS - }; - enum InputIds { - FREQ_INPUT, - RES_INPUT, - DRIVE_INPUT, - IN_INPUT, - NUM_INPUTS - }; - enum OutputIds { - LPF_OUTPUT, - HPF_OUTPUT, - NUM_OUTPUTS - }; - - LadderFilter filter; - - VCF() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {} - void step() override; - void onReset() override { - filter.reset(); - } + enum ParamIds { + FREQ_PARAM, + FINE_PARAM, + RES_PARAM, + FREQ_CV_PARAM, + DRIVE_PARAM, + NUM_PARAMS + }; + enum InputIds { + FREQ_INPUT, + RES_INPUT, + DRIVE_INPUT, + IN_INPUT, + NUM_INPUTS + }; + enum OutputIds { + LPF_OUTPUT, + HPF_OUTPUT, + NUM_OUTPUTS + }; + + LadderFilter filter; + + VCF() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {} + void step() override; + void onReset() override { + filter.reset(); + } }; void VCF::step() { - float input = inputs[IN_INPUT].value / 5.0f; - float drive = params[DRIVE_PARAM].value + inputs[DRIVE_INPUT].value / 10.0f; - float gain = powf(100.0f, drive); - input *= gain; - // Add -60dB noise to bootstrap self-oscillation - input += 1e-6f * (2.0f*randomUniform() - 1.0f); - - // Set resonance - float res = params[RES_PARAM].value + inputs[RES_INPUT].value / 5.0f; - res = clamp(res, 0.0f, 1.0f); // resonance must be between 0 and 1 - filter.resonance = res; - - // Set cutoff frequency - float cutoffExp = params[FREQ_PARAM].value + params[FREQ_CV_PARAM].value * inputs[FREQ_INPUT].value / 5.0f; - cutoffExp = clamp(cutoffExp, 0.0f, 1.0f); - const float minCutoff = 15.0f; - const float maxCutoff = 20000.0f; - float cutoff = minCutoff * powf(maxCutoff / minCutoff, cutoffExp); - filter.g = tanf(float_Pi * cutoff / engineGetSampleRate()); - - // Push a sample to the state filter - filter.process(input); - - // Set outputs - outputs[LPF_OUTPUT].value = 5.0f * filter.output[0]; - outputs[HPF_OUTPUT].value = 5.0f * filter.output[1]; - //outputs[BPF_OUTPUT].value = 5.0f * filter.output[2]; + float input = inputs[IN_INPUT].value / 5.0f; + float drive = params[DRIVE_PARAM].value + inputs[DRIVE_INPUT].value / 10.0f; + float gain = powf(100.0f, drive); + input *= gain; + // Add -60dB noise to bootstrap self-oscillation + input += 1e-6f * (2.0f*randomUniform() - 1.0f); + + // Set resonance + float res = params[RES_PARAM].value + inputs[RES_INPUT].value / 5.0f; + res = clamp(res, 0.0f, 1.0f); // resonance must be between 0 and 1 + filter.resonance = res; + + // Set cutoff frequency + float cutoffExp = params[FREQ_PARAM].value + params[FREQ_CV_PARAM].value * inputs[FREQ_INPUT].value / 5.0f; + cutoffExp = clamp(cutoffExp, 0.0f, 1.0f); + const float minCutoff = 15.0f; + const float maxCutoff = 20000.0f; + float cutoff = minCutoff * powf(maxCutoff / minCutoff, cutoffExp); + filter.g = tanf(float_Pi * cutoff / engineGetSampleRate()); + + // Push a sample to the state filter + filter.process(input); + + // Set outputs + outputs[LPF_OUTPUT].value = 5.0f * filter.output[0]; + outputs[HPF_OUTPUT].value = 5.0f * filter.output[1]; + //outputs[BPF_OUTPUT].value = 5.0f * filter.output[2]; } struct VCFWidget : ModuleWidget { - VCFWidget(VCF *module); + VCFWidget(VCF *module); }; VCFWidget::VCFWidget(VCF *module) : ModuleWidget(module) { - setPanel(SVG::load(assetPlugin(plugin, "res/VCF.svg"))); - - addChild(Widget::create(Vec(15, 0))); - addChild(Widget::create(Vec(box.size.x-30, 0))); - addChild(Widget::create(Vec(15, 365))); - addChild(Widget::create(Vec(box.size.x-30, 365))); - - addParam(ParamWidget::create(Vec(33, 61), module, VCF::FREQ_PARAM, 0.0f, 1.0f, 0.5f)); - addParam(ParamWidget::create(Vec(12, 143), module, VCF::FINE_PARAM, 0.0f, 1.0f, 0.5f)); - addParam(ParamWidget::create(Vec(71, 143), module, VCF::RES_PARAM, 0.0f, 1.0f, 0.0f)); - addParam(ParamWidget::create(Vec(12, 208), module, VCF::FREQ_CV_PARAM, -1.0f, 1.0f, 0.0f)); - addParam(ParamWidget::create(Vec(71, 208), module, VCF::DRIVE_PARAM, 0.0f, 1.0f, 0.0f)); - - addInput(Port::create(Vec(10, 276), Port::INPUT, module, VCF::FREQ_INPUT)); - addInput(Port::create(Vec(48, 276), Port::INPUT, module, VCF::RES_INPUT)); - addInput(Port::create(Vec(85, 276), Port::INPUT, module, VCF::DRIVE_INPUT)); - addInput(Port::create(Vec(10, 320), Port::INPUT, module, VCF::IN_INPUT)); - - addOutput(Port::create(Vec(48, 320), Port::OUTPUT, module, VCF::LPF_OUTPUT)); - addOutput(Port::create(Vec(85, 320), Port::OUTPUT, module, VCF::HPF_OUTPUT)); + setPanel(SVG::load(assetPlugin(plugin, "res/VCF.svg"))); + + addChild(Widget::create(Vec(15, 0))); + addChild(Widget::create(Vec(box.size.x-30, 0))); + addChild(Widget::create(Vec(15, 365))); + addChild(Widget::create(Vec(box.size.x-30, 365))); + + addParam(ParamWidget::create(Vec(33, 61), module, VCF::FREQ_PARAM, 0.0f, 1.0f, 0.5f)); + addParam(ParamWidget::create(Vec(12, 143), module, VCF::FINE_PARAM, 0.0f, 1.0f, 0.5f)); + addParam(ParamWidget::create(Vec(71, 143), module, VCF::RES_PARAM, 0.0f, 1.0f, 0.0f)); + addParam(ParamWidget::create(Vec(12, 208), module, VCF::FREQ_CV_PARAM, -1.0f, 1.0f, 0.0f)); + addParam(ParamWidget::create(Vec(71, 208), module, VCF::DRIVE_PARAM, 0.0f, 1.0f, 0.0f)); + + addInput(Port::create(Vec(10, 276), Port::INPUT, module, VCF::FREQ_INPUT)); + addInput(Port::create(Vec(48, 276), Port::INPUT, module, VCF::RES_INPUT)); + addInput(Port::create(Vec(85, 276), Port::INPUT, module, VCF::DRIVE_INPUT)); + addInput(Port::create(Vec(10, 320), Port::INPUT, module, VCF::IN_INPUT)); + + addOutput(Port::create(Vec(48, 320), Port::OUTPUT, module, VCF::LPF_OUTPUT)); + addOutput(Port::create(Vec(85, 320), Port::OUTPUT, module, VCF::HPF_OUTPUT)); }