|
|
@@ -1,126 +1,58 @@ |
|
|
|
#include "Fundamental.hpp" |
|
|
|
#include "dsp/functions.hpp" |
|
|
|
#include "dsp/resampler.hpp" |
|
|
|
#include "dsp/ode.hpp" |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
This code is derived from the algorithm described in "Modeling and Measuring a |
|
|
|
Moog Voltage-Controlled Filter, by Effrosyni Paschou, Fabián Esqueda, Vesa Välimäki |
|
|
|
and John Mourjopoulos, for APSIPA Annual Summit and Conference 2017. |
|
|
|
inline float clip(float x) { |
|
|
|
return tanhf(x); |
|
|
|
} |
|
|
|
|
|
|
|
It is a 0df algorithm using Newton-Raphson's method (4 iterations) for solving |
|
|
|
implicit equations, and midpoint integration method. |
|
|
|
|
|
|
|
Derived from the article and adapted to VCV Rack by Ivan COHEN. |
|
|
|
*/ |
|
|
|
struct LadderFilter0dfMidPoint { |
|
|
|
float cutoff = 1000.f; |
|
|
|
struct LadderFilter { |
|
|
|
float omega0; |
|
|
|
float resonance = 1.0f; |
|
|
|
float gainFactor = 0.2f; |
|
|
|
float A; |
|
|
|
/** State variables */ |
|
|
|
float lastInput = 0.f; |
|
|
|
float lastV[4] = {}; |
|
|
|
/** Outputs */ |
|
|
|
float state[4]; |
|
|
|
float input; |
|
|
|
float lowpass; |
|
|
|
float highpass; |
|
|
|
|
|
|
|
|
|
|
|
LadderFilter0dfMidPoint() { |
|
|
|
float VT = 26e-3f; |
|
|
|
A = gainFactor / (2.f * VT); |
|
|
|
} |
|
|
|
|
|
|
|
inline float clip(float x) { |
|
|
|
return tanhf(x); |
|
|
|
LadderFilter() { |
|
|
|
reset(); |
|
|
|
setCutoff(0.f); |
|
|
|
} |
|
|
|
|
|
|
|
void process(float input, float dt) { |
|
|
|
float F[4]; |
|
|
|
float V[4]; |
|
|
|
float wc = 2 * M_PI * cutoff / A; |
|
|
|
|
|
|
|
input *= gainFactor; |
|
|
|
|
|
|
|
for (int i = 0; i < 4; i++) { |
|
|
|
V[i] = lastV[i]; |
|
|
|
} |
|
|
|
|
|
|
|
void reset() { |
|
|
|
for (int i = 0; i < 4; i++) { |
|
|
|
float S0 = A * (input + lastInput + resonance * (V[3] + lastV[3])) * 0.5f; |
|
|
|
float S1 = A * (V[0] + lastV[0]) * 0.5f; |
|
|
|
float S2 = A * (V[1] + lastV[1]) * 0.5f; |
|
|
|
float S3 = A * (V[2] + lastV[2]) * 0.5f; |
|
|
|
float S4 = A * (V[3] + lastV[3]) * 0.5f; |
|
|
|
|
|
|
|
float t0 = clip(S0); |
|
|
|
float t1 = clip(S1); |
|
|
|
float t2 = clip(S2); |
|
|
|
float t3 = clip(S3); |
|
|
|
float t4 = clip(S4); |
|
|
|
|
|
|
|
// F function (the one for which we need to find the roots with NR) |
|
|
|
F[0] = V[0] - lastV[0] + wc * dt * (t0 + t1); |
|
|
|
F[1] = V[1] - lastV[1] - wc * dt * (t1 - t2); |
|
|
|
F[2] = V[2] - lastV[2] - wc * dt * (t2 - t3); |
|
|
|
F[3] = V[3] - lastV[3] - wc * dt * (t3 - t4); |
|
|
|
|
|
|
|
float g = wc * dt * A * 0.5f; |
|
|
|
|
|
|
|
// derivatives of ti |
|
|
|
float J0 = 1 - t0 * t0; |
|
|
|
float J1 = 1 - t1 * t1; |
|
|
|
float J2 = 1 - t2 * t2; |
|
|
|
float J3 = 1 - t3 * t3; |
|
|
|
float J4 = 1 - t4 * t4; |
|
|
|
|
|
|
|
// Jacobian matrix elements |
|
|
|
float a11 = 1 + g * J1; |
|
|
|
float a14 = resonance * g * J0; |
|
|
|
float a21 = -g * J1; |
|
|
|
float a22 = 1 + g * J2; |
|
|
|
float a32 = -g * J2; |
|
|
|
float a33 = 1 + g * J3; |
|
|
|
float a43 = -g * J3; |
|
|
|
float a44 = 1 + g * J4; |
|
|
|
|
|
|
|
// Newton-Raphson algorithm with Jacobian inverting |
|
|
|
float deninv = 1.f / (a11 * a22 * a33 * a44 - a14 * a21 * a32 * a43); |
|
|
|
|
|
|
|
float delta0 = ( F[0] * a22 * a33 * a44 - F[1] * a14 * a32 * a43 + F[2] * a14 * a22 * a43 - F[3] * a14 * a22 * a33) * deninv; |
|
|
|
float delta1 = (-F[0] * a21 * a33 * a44 + F[1] * a11 * a33 * a44 - F[2] * a14 * a21 * a43 + F[3] * a14 * a21 * a33) * deninv; |
|
|
|
float delta2 = ( F[0] * a21 * a32 * a44 - F[1] * a11 * a32 * a44 + F[2] * a11 * a22 * a44 - F[3] * a14 * a21 * a32) * deninv; |
|
|
|
float delta3 = (-F[0] * a21 * a32 * a43 + F[1] * a11 * a32 * a43 - F[2] * a11 * a22 * a43 + F[3] * a11 * a22 * a33) * deninv; |
|
|
|
|
|
|
|
delta0 = isfinite(delta0) ? delta0 : 0.f; |
|
|
|
delta1 = isfinite(delta1) ? delta1 : 0.f; |
|
|
|
delta2 = isfinite(delta2) ? delta2 : 0.f; |
|
|
|
delta3 = isfinite(delta3) ? delta3 : 0.f; |
|
|
|
|
|
|
|
V[0] -= delta0; |
|
|
|
V[1] -= delta1; |
|
|
|
V[2] -= delta2; |
|
|
|
V[3] -= delta3; |
|
|
|
state[i] = 0.f; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
lastInput = input; |
|
|
|
for (int i = 0; i < 4; i++) |
|
|
|
lastV[i] = V[i]; |
|
|
|
|
|
|
|
// outputs are inverted |
|
|
|
lowpass = -clip(V[3] / gainFactor); |
|
|
|
highpass = -clip(((input + resonance * V[3]) + 4 * V[0] - 6 * V[1] + 4 * V[2] - V[3]) / gainFactor); |
|
|
|
void setCutoff(float cutoff) { |
|
|
|
omega0 = 2.f*M_PI * cutoff; |
|
|
|
} |
|
|
|
|
|
|
|
void reset() { |
|
|
|
for (int i = 0; i < 4; i++) { |
|
|
|
lastV[i] = 0.f; |
|
|
|
} |
|
|
|
lastInput = 0.f; |
|
|
|
void process(float input, float dt) { |
|
|
|
stepRK4(0.f, dt, state, 4, [&](float t, const float y[], float dydt[]) { |
|
|
|
float inputc = clip(input - resonance * y[3]); |
|
|
|
float yc0 = clip(y[0]); |
|
|
|
float yc1 = clip(y[1]); |
|
|
|
float yc2 = clip(y[2]); |
|
|
|
float yc3 = clip(y[3]); |
|
|
|
|
|
|
|
dydt[0] = omega0 * (inputc - yc0); |
|
|
|
dydt[1] = omega0 * (yc0 - yc1); |
|
|
|
dydt[2] = omega0 * (yc1 - yc2); |
|
|
|
dydt[3] = omega0 * (yc2 - yc3); |
|
|
|
}); |
|
|
|
|
|
|
|
lowpass = state[3]; |
|
|
|
highpass = clip((input - resonance*state[3]) - 4 * state[0] + 6*state[1] - 4*state[2] + state[3]); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
static const int UPSAMPLE = 2; |
|
|
|
|
|
|
|
struct VCF : Module { |
|
|
|
enum ParamIds { |
|
|
|
FREQ_PARAM, |
|
|
@@ -143,7 +75,10 @@ struct VCF : Module { |
|
|
|
NUM_OUTPUTS |
|
|
|
}; |
|
|
|
|
|
|
|
LadderFilter0dfMidPoint filter; |
|
|
|
LadderFilter filter; |
|
|
|
// Upsampler<UPSAMPLE, 8> inputUpsampler; |
|
|
|
// Decimator<UPSAMPLE, 8> lowpassDecimator; |
|
|
|
// Decimator<UPSAMPLE, 8> highpassDecimator; |
|
|
|
|
|
|
|
VCF() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {} |
|
|
|
|
|
|
@@ -171,16 +106,38 @@ struct VCF : Module { |
|
|
|
filter.resonance = powf(res, 2) * 10.f; |
|
|
|
|
|
|
|
// Set cutoff frequency |
|
|
|
float pitch = inputs[FREQ_INPUT].value * quadraticBipolar(params[FREQ_CV_PARAM].value); |
|
|
|
pitch += params[FREQ_PARAM].value * 10.f - 3.f; |
|
|
|
float pitch = 0.f; |
|
|
|
if (inputs[FREQ_INPUT].active) |
|
|
|
pitch += inputs[FREQ_INPUT].value * quadraticBipolar(params[FREQ_CV_PARAM].value); |
|
|
|
pitch += params[FREQ_PARAM].value * 10.f - 5.f; |
|
|
|
pitch += quadraticBipolar(params[FINE_PARAM].value * 2.f - 1.f) * 7.f / 12.f; |
|
|
|
float cutoff = 261.626f * powf(2.f, pitch); |
|
|
|
filter.cutoff = clamp(cutoff, 1.f, 20000.f); |
|
|
|
|
|
|
|
// Step the filter |
|
|
|
filter.process(input, engineGetSampleTime()); |
|
|
|
cutoff = clamp(cutoff, 1.f, 8000.f); |
|
|
|
filter.setCutoff(cutoff); |
|
|
|
|
|
|
|
/* |
|
|
|
// Process sample |
|
|
|
float dt = engineGetSampleTime() / 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; |
|
|
|
} |
|
|
|
|
|
|
|
// Set outputs |
|
|
|
if (outputs[LPF_OUTPUT].active) { |
|
|
|
outputs[LPF_OUTPUT].value = 5.f * lowpassDecimator.process(lowpassBuf); |
|
|
|
} |
|
|
|
if (outputs[HPF_OUTPUT].active) { |
|
|
|
outputs[HPF_OUTPUT].value = 5.f * highpassDecimator.process(highpassBuf); |
|
|
|
} |
|
|
|
*/ |
|
|
|
filter.process(input, engineGetSampleTime()); |
|
|
|
outputs[LPF_OUTPUT].value = 5.f * filter.lowpass; |
|
|
|
outputs[HPF_OUTPUT].value = 5.f * filter.highpass; |
|
|
|
} |
|
|
|