aether_nodes/
moog_ladder.rs1use aether_core::{node::DspNode, param::ParamBlock, state::StateBlob, BUFFER_SIZE, MAX_INPUTS};
12use std::f32::consts::PI;
13
14#[derive(Clone, Copy, Default)]
15struct LadderState {
16 stage: [f32; 4],
17 #[allow(dead_code)]
18 stage_tanh: [f32; 4],
19 delay: [f32; 6],
20}
21
22pub struct MoogLadder {
23 state: LadderState,
24 #[allow(dead_code)]
27 thermal: f32,
28}
29
30impl MoogLadder {
31 pub fn new() -> Self {
32 Self {
33 state: LadderState::default(),
34 thermal: 0.000_025, }
36 }
37
38 #[inline(always)]
39 fn process_sample(&mut self, input: f32, cutoff: f32, resonance: f32, drive: f32, sr: f32) -> f32 {
40 let f = cutoff / (sr * 0.5);
41 let f = f.clamp(0.0, 1.0);
42
43 let fc = f * PI;
45 let fc2 = fc * fc;
46 let fc3 = fc2 * fc;
47
48 let fcr = 1.8730 * fc3 + 0.4955 * fc2 - 0.6490 * fc + 0.9988;
49 let acr = -3.9364 * fc2 + 1.8409 * fc + 0.9968;
50
51 let f2 = (2.0 / 1.3) * f;
52 let res4 = resonance * acr;
53
54 let inp = (input * (1.0 + drive * 3.0)).tanh();
56
57 let inp_sub = inp - res4 * self.state.delay[5];
59
60 let t1 = self.state.stage[0] * fcr;
62 let t2 = self.state.delay[0] * fcr;
63 self.state.stage[0] = inp_sub * f2 - t1;
64 self.state.delay[0] = self.state.stage[0] + t1;
65 let out1 = self.state.delay[0].tanh();
66
67 let t1 = self.state.stage[1] * fcr;
69 let t2_2 = self.state.delay[1] * fcr;
70 self.state.stage[1] = out1 * f2 - t1;
71 self.state.delay[1] = self.state.stage[1] + t1;
72 let out2 = self.state.delay[1].tanh();
73
74 let t1 = self.state.stage[2] * fcr;
76 let _t2_3 = self.state.delay[2] * fcr;
77 self.state.stage[2] = out2 * f2 - t1;
78 self.state.delay[2] = self.state.stage[2] + t1;
79 let out3 = self.state.delay[2].tanh();
80
81 let t1 = self.state.stage[3] * fcr;
83 let _t2_4 = self.state.delay[3] * fcr;
84 self.state.stage[3] = out3 * f2 - t1;
85 self.state.delay[3] = self.state.stage[3] + t1;
86 let out4 = self.state.delay[3];
87
88 self.state.delay[5] = (self.state.delay[4] + out4) * 0.5;
90 self.state.delay[4] = out4;
91
92 let _ = (t2, t2_2, fc3);
94
95 out4
96 }
97}
98
99impl Default for MoogLadder {
100 fn default() -> Self { Self::new() }
101}
102
103impl DspNode for MoogLadder {
104 fn process(
105 &mut self,
106 inputs: &[Option<&[f32; BUFFER_SIZE]>; MAX_INPUTS],
107 output: &mut [f32; BUFFER_SIZE],
108 params: &mut ParamBlock,
109 sample_rate: f32,
110 ) {
111 let silence = [0.0f32; BUFFER_SIZE];
112 let input = inputs[0].unwrap_or(&silence);
113
114 for (i, out) in output.iter_mut().enumerate() {
115 let cutoff = params.get(0).current.clamp(20.0, sample_rate * 0.45);
116 let resonance = params.get(1).current.clamp(0.0, 4.0);
117 let drive = params.get(2).current.clamp(0.0, 1.0);
118 *out = self.process_sample(input[i], cutoff, resonance, drive, sample_rate);
119 params.tick_all();
120 }
121 }
122
123 fn capture_state(&self) -> StateBlob { StateBlob::EMPTY }
124 fn type_name(&self) -> &'static str { "MoogLadder" }
125}