|
@@ -1,175 +1,174 @@ |
|
|
/* |
|
|
|
|
|
The filter DSP code has been derived from |
|
|
|
|
|
Miller Puckette's code hosted at |
|
|
|
|
|
https://github.com/ddiakopoulos/MoogLadders/blob/master/src/RKSimulationModel.h |
|
|
|
|
|
which is licensed for use under the following terms (MIT license): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Copyright (c) 2015, Miller Puckette. All rights reserved. |
|
|
|
|
|
|
|
|
|
|
|
Redistribution and use in source and binary forms, with or without |
|
|
|
|
|
modification, are permitted provided that the following conditions are met: |
|
|
|
|
|
|
|
|
|
|
|
* Redistributions of source code must retain the above copyright notice, this |
|
|
|
|
|
list of conditions and the following disclaimer. |
|
|
|
|
|
* Redistributions in binary form must reproduce the above copyright notice, |
|
|
|
|
|
this list of conditions and the following disclaimer in the documentation |
|
|
|
|
|
and/or other materials provided with the distribution. |
|
|
|
|
|
|
|
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
|
|
|
|
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
|
|
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|
|
|
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
|
|
|
|
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
|
|
|
|
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
|
|
|
|
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
|
|
|
|
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
|
|
|
|
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
|
|
|
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
// LICENSE TERMS: Copyright 2012 Teemu Voipio |
|
|
|
|
|
// |
|
|
|
|
|
// You can use this however you like for pretty much any purpose, |
|
|
|
|
|
// as long as you don't claim you wrote it. There is no warranty. |
|
|
|
|
|
// |
|
|
|
|
|
// Distribution of substantial portions of this code in source form |
|
|
|
|
|
// must include this copyright notice and list of conditions. |
|
|
|
|
|
// |
|
|
|
|
|
// Improved by Ivan COHEN from musical entropy |
|
|
|
|
|
// More about this algorithm here : |
|
|
|
|
|
// http://www.kvraudio.com/forum/viewtopic.php?t=349859 |
|
|
|
|
|
// http://www.kvraudio.com/forum/viewtopic.php?t=207647 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include "Fundamental.hpp" |
|
|
#include "Fundamental.hpp" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// The clipping function of a transistor pair is approximately tanh(x) |
|
|
// The clipping function of a transistor pair is approximately tanh(x) |
|
|
// TODO: Put this in a lookup table. 5th order approx doesn't seem to cut it |
|
|
|
|
|
|
|
|
// This one is a Pade-approx for tanh(sqrt(x))/sqrt(x) |
|
|
inline float clip(float x) { |
|
|
inline float clip(float x) { |
|
|
return tanhf(x); |
|
|
|
|
|
|
|
|
float a = x*x; |
|
|
|
|
|
return ((a + 105)*a + 945) / ((15*a + 420)*a + 945); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
struct LadderFilter { |
|
|
struct LadderFilter { |
|
|
float cutoff = 1000.0f; |
|
|
|
|
|
float resonance = 1.0f; |
|
|
|
|
|
float state[4] = {}; |
|
|
|
|
|
|
|
|
|
|
|
void calculateDerivatives(float input, float *dstate, const float *state) { |
|
|
|
|
|
float cutoff2Pi = 2*M_PI * cutoff; |
|
|
|
|
|
|
|
|
|
|
|
float satstate0 = clip(state[0]); |
|
|
|
|
|
float satstate1 = clip(state[1]); |
|
|
|
|
|
float satstate2 = clip(state[2]); |
|
|
|
|
|
|
|
|
|
|
|
dstate[0] = cutoff2Pi * (clip(input - resonance * state[3]) - satstate0); |
|
|
|
|
|
dstate[1] = cutoff2Pi * (satstate0 - satstate1); |
|
|
|
|
|
dstate[2] = cutoff2Pi * (satstate1 - satstate2); |
|
|
|
|
|
dstate[3] = cutoff2Pi * (satstate2 - clip(state[3])); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void process(float input, float dt) { |
|
|
|
|
|
float deriv1[4], deriv2[4], deriv3[4], deriv4[4], tempState[4]; |
|
|
|
|
|
|
|
|
|
|
|
calculateDerivatives(input, deriv1, state); |
|
|
|
|
|
for (int i = 0; i < 4; i++) |
|
|
|
|
|
tempState[i] = state[i] + 0.5f * dt * deriv1[i]; |
|
|
|
|
|
|
|
|
|
|
|
calculateDerivatives(input, deriv2, tempState); |
|
|
|
|
|
for (int i = 0; i < 4; i++) |
|
|
|
|
|
tempState[i] = state[i] + 0.5f * dt * deriv2[i]; |
|
|
|
|
|
|
|
|
|
|
|
calculateDerivatives(input, deriv3, tempState); |
|
|
|
|
|
for (int i = 0; i < 4; i++) |
|
|
|
|
|
tempState[i] = state[i] + dt * deriv3[i]; |
|
|
|
|
|
|
|
|
|
|
|
calculateDerivatives(input, deriv4, tempState); |
|
|
|
|
|
for (int i = 0; i < 4; i++) |
|
|
|
|
|
state[i] += (1.0f / 6.0f) * dt * (deriv1[i] + 2.0f * deriv2[i] + 2.0f * deriv3[i] + deriv4[i]); |
|
|
|
|
|
} |
|
|
|
|
|
void reset() { |
|
|
|
|
|
for (int i = 0; i < 4; i++) { |
|
|
|
|
|
state[i] = 0.0f; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
} |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct VCF : Module { |
|
|
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() { |
|
|
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 = 5.5f * clamp(res, 0.0f, 1.0f); |
|
|
|
|
|
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 = 8400.0f; |
|
|
|
|
|
filter.cutoff = minCutoff * powf(maxCutoff / minCutoff, cutoffExp); |
|
|
|
|
|
|
|
|
|
|
|
// Push a sample to the state filter |
|
|
|
|
|
filter.process(input, 1.0f/engineGetSampleRate()); |
|
|
|
|
|
|
|
|
|
|
|
// Set outputs |
|
|
|
|
|
outputs[LPF_OUTPUT].value = 5.0f * filter.state[3]; |
|
|
|
|
|
outputs[HPF_OUTPUT].value = 5.0f * (input - 4*filter.state[0] + 6*filter.state[1] - 4*filter.state[2] + filter.state[3]); |
|
|
|
|
|
//outputs[BPF_OUTPUT].value = 5.0f * (filter.state[1] - 2*filter.state[2] + filter.state[3]); |
|
|
|
|
|
|
|
|
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 { |
|
|
struct VCFWidget : ModuleWidget { |
|
|
VCFWidget(VCF *module); |
|
|
|
|
|
|
|
|
VCFWidget(VCF *module); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
VCFWidget::VCFWidget(VCF *module) : ModuleWidget(module) { |
|
|
VCFWidget::VCFWidget(VCF *module) : ModuleWidget(module) { |
|
|
setPanel(SVG::load(assetPlugin(plugin, "res/VCF.svg"))); |
|
|
|
|
|
|
|
|
|
|
|
addChild(Widget::create<ScrewSilver>(Vec(15, 0))); |
|
|
|
|
|
addChild(Widget::create<ScrewSilver>(Vec(box.size.x-30, 0))); |
|
|
|
|
|
addChild(Widget::create<ScrewSilver>(Vec(15, 365))); |
|
|
|
|
|
addChild(Widget::create<ScrewSilver>(Vec(box.size.x-30, 365))); |
|
|
|
|
|
|
|
|
|
|
|
addParam(ParamWidget::create<RoundHugeBlackKnob>(Vec(33, 61), module, VCF::FREQ_PARAM, 0.0f, 1.0f, 0.5f)); |
|
|
|
|
|
addParam(ParamWidget::create<RoundLargeBlackKnob>(Vec(12, 143), module, VCF::FINE_PARAM, 0.0f, 1.0f, 0.5f)); |
|
|
|
|
|
addParam(ParamWidget::create<RoundLargeBlackKnob>(Vec(71, 143), module, VCF::RES_PARAM, 0.0f, 1.0f, 0.0f)); |
|
|
|
|
|
addParam(ParamWidget::create<RoundLargeBlackKnob>(Vec(12, 208), module, VCF::FREQ_CV_PARAM, -1.0f, 1.0f, 0.0f)); |
|
|
|
|
|
addParam(ParamWidget::create<RoundLargeBlackKnob>(Vec(71, 208), module, VCF::DRIVE_PARAM, 0.0f, 1.0f, 0.0f)); |
|
|
|
|
|
|
|
|
|
|
|
addInput(Port::create<PJ301MPort>(Vec(10, 276), Port::INPUT, module, VCF::FREQ_INPUT)); |
|
|
|
|
|
addInput(Port::create<PJ301MPort>(Vec(48, 276), Port::INPUT, module, VCF::RES_INPUT)); |
|
|
|
|
|
addInput(Port::create<PJ301MPort>(Vec(85, 276), Port::INPUT, module, VCF::DRIVE_INPUT)); |
|
|
|
|
|
addInput(Port::create<PJ301MPort>(Vec(10, 320), Port::INPUT, module, VCF::IN_INPUT)); |
|
|
|
|
|
|
|
|
|
|
|
addOutput(Port::create<PJ301MPort>(Vec(48, 320), Port::OUTPUT, module, VCF::LPF_OUTPUT)); |
|
|
|
|
|
addOutput(Port::create<PJ301MPort>(Vec(85, 320), Port::OUTPUT, module, VCF::HPF_OUTPUT)); |
|
|
|
|
|
|
|
|
setPanel(SVG::load(assetPlugin(plugin, "res/VCF.svg"))); |
|
|
|
|
|
|
|
|
|
|
|
addChild(Widget::create<ScrewSilver>(Vec(15, 0))); |
|
|
|
|
|
addChild(Widget::create<ScrewSilver>(Vec(box.size.x-30, 0))); |
|
|
|
|
|
addChild(Widget::create<ScrewSilver>(Vec(15, 365))); |
|
|
|
|
|
addChild(Widget::create<ScrewSilver>(Vec(box.size.x-30, 365))); |
|
|
|
|
|
|
|
|
|
|
|
addParam(ParamWidget::create<RoundHugeBlackKnob>(Vec(33, 61), module, VCF::FREQ_PARAM, 0.0f, 1.0f, 0.5f)); |
|
|
|
|
|
addParam(ParamWidget::create<RoundLargeBlackKnob>(Vec(12, 143), module, VCF::FINE_PARAM, 0.0f, 1.0f, 0.5f)); |
|
|
|
|
|
addParam(ParamWidget::create<RoundLargeBlackKnob>(Vec(71, 143), module, VCF::RES_PARAM, 0.0f, 1.0f, 0.0f)); |
|
|
|
|
|
addParam(ParamWidget::create<RoundLargeBlackKnob>(Vec(12, 208), module, VCF::FREQ_CV_PARAM, -1.0f, 1.0f, 0.0f)); |
|
|
|
|
|
addParam(ParamWidget::create<RoundLargeBlackKnob>(Vec(71, 208), module, VCF::DRIVE_PARAM, 0.0f, 1.0f, 0.0f)); |
|
|
|
|
|
|
|
|
|
|
|
addInput(Port::create<PJ301MPort>(Vec(10, 276), Port::INPUT, module, VCF::FREQ_INPUT)); |
|
|
|
|
|
addInput(Port::create<PJ301MPort>(Vec(48, 276), Port::INPUT, module, VCF::RES_INPUT)); |
|
|
|
|
|
addInput(Port::create<PJ301MPort>(Vec(85, 276), Port::INPUT, module, VCF::DRIVE_INPUT)); |
|
|
|
|
|
addInput(Port::create<PJ301MPort>(Vec(10, 320), Port::INPUT, module, VCF::IN_INPUT)); |
|
|
|
|
|
|
|
|
|
|
|
addOutput(Port::create<PJ301MPort>(Vec(48, 320), Port::OUTPUT, module, VCF::LPF_OUTPUT)); |
|
|
|
|
|
addOutput(Port::create<PJ301MPort>(Vec(85, 320), Port::OUTPUT, module, VCF::HPF_OUTPUT)); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|