//! Trapezoidal state-variable filter (Simper/Cytomic). Stable and click-free under fast //! retuning, so it can follow a gliding note. Coefficients are separate from state, so one //! set can drive both channels. use std::f32::consts::PI; #[derive(Clone, Copy, Debug, PartialEq)] pub struct SvfCoef { a1: f32, a2: f32, a3: f32, m0: f32, m1: f32, m2: f32, } impl Default for SvfCoef { /// Identity: passes the input unchanged. fn default() -> Self { Self::IDENTITY } } impl SvfCoef { pub const IDENTITY: Self = Self { a1: 1., a2: 0., a3: 0., m0: 1., m1: 0., m2: 0., }; fn base(rate: f32, hz: f32, k: f32, warp: f32) -> (f32, f32, f32) { let g = (PI * (hz / rate).clamp(1e-6, 0.49)).tan() * warp; let a1 = 1. / (1. + g * (g + k)); let a2 = g * a1; (a1, a2, g * a2) } /// Peaking filter. At 0 dB it is an exact identity. pub fn bell(rate: f32, hz: f32, q: f32, gain_db: f32) -> Self { let a = 10f32.powf(gain_db / 40.); let k = 1. / (q.max(1e-3) * a); let (a1, a2, a3) = Self::base(rate, hz, k, 1.); Self { a1, a2, a3, m0: 1., m1: k * (a * a - 1.), m2: 0., } } pub fn lowpass(rate: f32, hz: f32, q: f32) -> Self { let (a1, a2, a3) = Self::base(rate, hz, 1. / q.max(1e-3), 1.); Self { a1, a2, a3, m0: 0., m1: 0., m2: 1., } } pub fn highpass(rate: f32, hz: f32, q: f32) -> Self { let k = 1. / q.max(1e-3); let (a1, a2, a3) = Self::base(rate, hz, k, 1.); Self { a1, a2, a3, m0: 1., m1: -k, m2: -1., } } /// Second-order allpass. With `q` = 1/√2 it has the phase of a Linkwitz–Riley crossover /// built from two lowpass and two highpass stages at the same frequency, so it keeps /// the bands of a crossover tree in phase. pub fn allpass(rate: f32, hz: f32, q: f32) -> Self { let k = 1. / q.max(1e-3); let (a1, a2, a3) = Self::base(rate, hz, k, 1.); Self { a1, a2, a3, m0: 1., m1: -2. * k, m2: 0., } } pub fn high_shelf(rate: f32, hz: f32, q: f32, gain_db: f32) -> Self { let a = 10f32.powf(gain_db / 40.); let k = 1. / q.max(1e-3); let (a1, a2, a3) = Self::base(rate, hz, k, a.sqrt()); Self { a1, a2, a3, m0: a * a, m1: k * (1. - a) * a, m2: 1. - a * a, } } } #[derive(Clone, Copy, Debug, Default)] pub struct Svf { ic1: f32, ic2: f32, } impl Svf { #[inline] pub fn process(&mut self, c: &SvfCoef, v0: f32) -> f32 { let v3 = v0 - self.ic2; let v1 = c.a1 * self.ic1 + c.a2 * v3; let v2 = self.ic2 + c.a2 * self.ic1 + c.a3 * v3; self.ic1 = 2. * v1 - self.ic1; self.ic2 = 2. * v2 - self.ic2; c.m0 * v0 + c.m1 * v1 + c.m2 * v2 } pub fn reset(&mut self) { *self = Self::default(); } }