diff --git a/src/VCF.cpp b/src/VCF.cpp index daafb61..2c343f5 100644 --- a/src/VCF.cpp +++ b/src/VCF.cpp @@ -9,15 +9,15 @@ inline T rcp_newton1(T x) { } -/** Approximates tan(pi*x) for x in [0, 0.5). -Optimized coefficients for max relative error: 1.18e-05. +/** Approximates tan(x) for x in [0, pi*0.5). +Optimized coefficients for max relative error: 2.78e-05. */ template -inline T tan_pi_1_2(T x) { +inline T tan_1_2(T x) { T x2 = x * x; - T num = T(1) + T(-0.9622845476351338) * x2; - T den = T(1) + x2 * (T(-4.252711329946427) + T(1.0108965067534537) * x2); - return T(M_PI) * x * num / den; + T num = T(1) + x2 * T(-0.09776575533683811); + T den = T(1) + x2 * (T(-0.43119539396382) + x2 * T(0.0105011966117302)); + return x * num / den; } @@ -62,7 +62,7 @@ inline T ln_cosh_3_4(T x) { } -/** 1st-order antiderivative anti-aliasing (ADAA) for tanh. */ +/** 1st-order ADAA for tanh. */ template struct TanhADAA1 { T xPrev = T(0); @@ -92,7 +92,19 @@ struct TanhADAA1 { }; -/** 4-pole transistor ladder filter using TPT/ZDF (Zero-Delay Feedback). +/** 4-pole transistor ladder filter using TPT (Topology-Preserving Transform). +Feedback uses previous sample (i.e. 1 sample delayed) to avoid iterative solving. + +For the equivalent state / trapezoidal integrator approach: +Zavalishin, V. "The Art of VA Filter Design". 2018 +https://www.native-instruments.com/fileadmin/ni_media/downloads/pdf/VAFilterDesign_2.1.2.pdf + +For Cytomic's formulation: +https://cytomic.com/files/dsp/SvfLinearTrapOptimised2.pdf + +For antiderivative anti-aliasing (ADAA): +Parker, J., Zavalishin, V., Le Bihan, E. "Reducing the Aliasing of Nonlinear Waveshaping Using Continuous-Time Convolution". DAFx 2016 +https://dafx.de/paper-archive/2016/dafxpapers/20-DAFx-16_paper_41-PN.pdf */ template struct LadderFilter { @@ -126,13 +138,14 @@ struct LadderFilter { void process(Frame& frame) { // Pre-warped frequency - T g = tan_pi_1_2(frame.cutoff); + T g = tan_1_2(T(M_PI) * frame.cutoff); // Integrator gain T G = g / (T(1) + g); // Feedback path // Apply resonance scaling and soft-clip with tanh T feedback = adaa[4].process(frame.resonance * state[3]); + // T feedback = tanh_2_3(frame.resonance * state[3]); T u = frame.input - feedback; // Stage 0