rill_core_model/wdf/
moog_ladder.rs1use rill_core::math::vector::traits::Vector as VecTrait;
2use rill_core::traits::{Algorithm, AlgorithmCategory, AlgorithmMetadata, ProcessResult};
3use rill_core::Transcendental;
4
5crate::wdf_element! {
17 name: RcPole<T>,
18 params: { alpha: T },
19 state: { state: T },
20 port_resistance: |_s| T::ONE,
21 scattering: |s, a| {
22 let b = s.state + s.alpha * (a - s.state);
23 s.state = b + s.alpha * (a - b);
24 b
25 },
26 update: |_s| {},
27 reset: |s| { s.state = T::ZERO; },
28}
29
30crate::wdf_cascade! {
32 name: MoogLadder<T>,
33 section: RcPole<T>,
34 count: 4,
35 params: { cutoff: T, resonance: T, sample_rate: T },
36 state: { feedback_prev: T },
37 feedback: |s, input, fb_prev| {
38 let k = s.resonance * T::from_f32(4.0);
39 let fb = fb_prev * k;
40 input - fb.clamp(-T::ONE, T::ONE)
41 },
42 update: |s| {
43 let g = T::PI * s.cutoff / s.sample_rate;
44 let alpha = g / (T::ONE + g);
45 for p in &mut s.poles { p.alpha = alpha; }
46 },
47}
48
49impl<T: Transcendental> MoogLadder<T> {
50 pub fn process_4_voices(
56 &mut self,
57 inputs: rill_core::math::vector::scalar::ScalarVector4<T>,
58 ) -> rill_core::math::vector::scalar::ScalarVector4<T> {
59 rill_core::math::vector::scalar::ScalarVector4::from_fn(|i| {
60 self.process_sample(inputs.extract(i))
61 })
62 }
63}
64
65impl<T: Transcendental> crate::WdfElement<T> for MoogLadder<T> {
66 fn port_resistance(&self) -> T {
67 T::ONE
68 }
69 fn process_incident(&mut self, a: T) -> T {
70 self.process_sample(a)
71 }
72 fn update_state(&mut self) {}
73 fn voltage(&self) -> T {
74 self.feedback_prev
75 }
76 fn current(&self) -> T {
77 T::ZERO
78 }
79 fn reset(&mut self) {
80 self.reset();
81 }
82}
83
84impl<T: Transcendental> Algorithm<T> for MoogLadder<T> {
85 fn init(&mut self, sample_rate: f32) {
86 self.sample_rate = T::from_f32(sample_rate);
87 self.update_coeffs();
88 }
89
90 fn reset(&mut self) {
91 self.reset();
92 }
93
94 fn process(&mut self, input: Option<&[T]>, output: &mut [T]) -> ProcessResult<()> {
95 let input = input.unwrap_or(&[]);
96 let len = input.len().min(output.len());
97 for i in 0..len {
98 output[i] = self.process_sample(input[i]);
99 }
100 Ok(())
101 }
102
103 fn metadata(&self) -> AlgorithmMetadata {
104 AlgorithmMetadata {
105 name: "WDF Moog Ladder Filter",
106 category: AlgorithmCategory::Filter,
107 description: "WDF-based 4-pole Moog transistor ladder VCF with resonance",
108 author: "Rill",
109 version: env!("CARGO_PKG_VERSION"),
110 }
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 fn make_filter(sample_rate: f64) -> MoogLadder<f64> {
119 let pole = RcPole::new(0.0);
120 let mut filter = MoogLadder::new(pole, 1000.0, 0.0, sample_rate);
121 filter.update_coeffs();
122 filter
123 }
124
125 #[test]
126 fn test_lp_section_dc() {
127 let fs = 44100.0_f64;
128 let fc = 1000.0_f64;
129 let g = core::f64::consts::PI * fc / fs;
130 let alpha = g / (1.0 + g);
131 let mut pole = RcPole::new(alpha);
132 let mut out = 0.0_f64;
133 for _ in 0..5000 {
134 out = crate::WdfElement::process_incident(&mut pole, 1.0);
135 }
136 assert!((out - 1.0).abs() < 0.01, "DC gain should approach 1.0");
137 }
138
139 #[test]
140 fn test_moog_ladder_creation() {
141 let filter = make_filter(44100.0);
142 assert!((filter.cutoff() - 1000.0).abs() < 1e-6);
143 }
144
145 #[test]
146 fn test_moog_ladder_dc() {
147 let mut filter = make_filter(44100.0);
148 filter.set_cutoff(100.0);
149 let mut out = 0.0_f64;
150 for _ in 0..5000 {
151 out = filter.process_sample(1.0_f64);
152 }
153 assert!((out - 1.0_f64).abs() < 0.01, "DC gain should be near 1.0");
154 }
155
156 #[test]
157 fn test_moog_ladder_cutoff_clamp() {
158 let mut filter = make_filter(44100.0);
159 filter.set_cutoff(10.0);
160 assert!((filter.cutoff() - 20.0).abs() < 1e-6);
161 filter.set_cutoff(50000.0);
162 assert!((filter.cutoff() - 22050.0).abs() < 1e-6);
163 }
164
165 #[test]
166 fn test_moog_ladder_algorithm_process() {
167 let mut filter = make_filter(44100.0);
168 filter.set_cutoff(100.0);
169 let input = vec![1.0f64; 64];
170 let mut output = vec![0.0f64; 64];
171 for _ in 0..500 {
172 filter.process(Some(&input), &mut output).unwrap();
173 }
174 for &o in &output {
175 assert!(o.is_finite());
176 assert!((o - 1.0).abs() < 0.05);
177 }
178 }
179
180 #[test]
181 fn test_moog_ladder_algorithm_reset() {
182 let mut filter = make_filter(44100.0);
183 let input = vec![1.0f64; 64];
184 let mut output = vec![0.0f64; 64];
185 filter.process(Some(&input), &mut output).unwrap();
186 filter.reset();
187 filter.process(Some(&input), &mut output).unwrap();
188 assert!(output[0] >= 0.0);
189 }
190}