diff --git a/src/VCF.cpp b/src/VCF.cpp index 6dd0e19..daafb61 100644 --- a/src/VCF.cpp +++ b/src/VCF.cpp @@ -62,7 +62,7 @@ inline T ln_cosh_3_4(T x) { } -/** First-order ADAA for tanh, caching F(x) between samples. */ +/** 1st-order antiderivative anti-aliasing (ADAA) for tanh. */ template struct TanhADAA1 { T xPrev = T(0); @@ -92,8 +92,7 @@ struct TanhADAA1 { }; -/** 4-pole (24dB/oct) transistor ladder filter using TPT/ZDF (Zero-Delay Feedback). -Uses first-order ADAA on tanh saturation. +/** 4-pole transistor ladder filter using TPT/ZDF (Zero-Delay Feedback). */ template struct LadderFilter { @@ -102,14 +101,13 @@ struct LadderFilter { struct Frame { T input; - /** Normalized cutoff frequency: 0 <= fc/fs < 0.5. */ + /** Normalized cutoff frequency fc/fs in [0, 0.5) */ T cutoff; - /** Resonance: 0-1, self-oscillation begins around 0.7. */ T resonance; - /** 4-pole (24dB/oct) lowpass output. */ + /** 4-pole lowpass output. */ T lowpass4; - /** 4-pole (24dB/oct) highpass output. */ + /** 4-pole highpass output. */ T highpass4; }; @@ -127,13 +125,13 @@ struct LadderFilter { } void process(Frame& frame) { - // Pre-warp cutoff frequency using bilinear transform + // Pre-warped frequency T g = tan_pi_1_2(frame.cutoff); - // Compute the one-pole lowpass gain coefficient G = g/(1+g) + // Integrator gain T G = g / (T(1) + g); - // Global feedback path - // Apply resonance scaling and soft-clip with tanh (with ADAA) + // Feedback path + // Apply resonance scaling and soft-clip with tanh T feedback = adaa[4].process(frame.resonance * state[3]); T u = frame.input - feedback; @@ -166,8 +164,6 @@ struct LadderFilter { state[3] = y3 + v3; frame.lowpass4 = y3; - - // Binomial expansion (1-H)^4 = 1 - 4H + 6H^2 - 4H^3 + H^4 frame.highpass4 = sat0 - T(4) * y0 + T(6) * y1 - T(4) * y2 + y3; } }; @@ -276,7 +272,7 @@ struct VCF : Module { // Resonance float_4 resonance = resParam + inputs[RES_INPUT].getPolyVoltageSimd(c) / 10.f * resCvParam; resonance = simd::clamp(resonance, 0.f, 1.f); - frame.resonance = resonance * resonance * 10.f; + frame.resonance = simd::pow(resonance, 2) * 10.f; // Cutoff frequency float_4 pitch = freqParam + inputs[FREQ_INPUT].getPolyVoltageSimd(c) * freqCvParam;