1use aether_core::{node::DspNode, param::ParamBlock, state::StateBlob, BUFFER_SIZE, MAX_INPUTS};
9
10#[derive(Clone, Copy, Default)]
11struct SvfState {
12 ic1eq: f32,
13 ic2eq: f32,
14}
15
16pub struct StateVariableFilter {
17 ic1eq: f32,
18 ic2eq: f32,
19}
20
21impl StateVariableFilter {
22 pub fn new() -> Self {
23 Self {
24 ic1eq: 0.0,
25 ic2eq: 0.0,
26 }
27 }
28
29 #[inline(always)]
31 fn process_sample(&mut self, input: f32, cutoff: f32, q: f32, sr: f32) -> (f32, f32, f32) {
32 let g = (std::f32::consts::PI * cutoff / sr).tan();
33 let k = 1.0 / q.max(0.1);
34 let a1 = 1.0 / (1.0 + g * (g + k));
35 let a2 = g * a1;
36 let a3 = g * a2;
37
38 let v3 = input - self.ic2eq;
39 let v1 = a1 * self.ic1eq + a2 * v3;
40 let v2 = self.ic2eq + a2 * self.ic1eq + a3 * v3;
41
42 self.ic1eq = 2.0 * v1 - self.ic1eq;
43 self.ic2eq = 2.0 * v2 - self.ic2eq;
44
45 let lp = v2;
46 let bp = v1;
47 let hp = input - k * v1 - v2;
48 (lp, hp, bp)
49 }
50}
51
52impl Default for StateVariableFilter {
53 fn default() -> Self {
54 Self::new()
55 }
56}
57
58impl DspNode for StateVariableFilter {
59 fn process(
60 &mut self,
61 inputs: &[Option<&[f32; BUFFER_SIZE]>; MAX_INPUTS],
62 output: &mut [f32; BUFFER_SIZE],
63 params: &mut ParamBlock,
64 sample_rate: f32,
65 ) {
66 let silence = [0.0f32; BUFFER_SIZE];
67 let input = inputs[0].unwrap_or(&silence);
68
69 for (i, out) in output.iter_mut().enumerate() {
70 let cutoff = params.get(0).current.clamp(20.0, sample_rate * 0.49);
71 let q = params.get(1).current.clamp(0.5, 20.0);
72 let mode = params.get(2).current as u32;
73
74 let (lp, hp, bp) = self.process_sample(input[i], cutoff, q, sample_rate);
75 *out = match mode {
76 0 => lp,
77 1 => hp,
78 _ => bp,
79 };
80 params.tick_all();
81 }
82 }
83
84 fn capture_state(&self) -> StateBlob {
85 StateBlob::from_value(&SvfState {
86 ic1eq: self.ic1eq,
87 ic2eq: self.ic2eq,
88 })
89 }
90
91 fn restore_state(&mut self, state: StateBlob) {
92 let s: SvfState = state.to_value();
93 self.ic1eq = s.ic1eq;
94 self.ic2eq = s.ic2eq;
95 }
96
97 fn type_name(&self) -> &'static str {
98 "StateVariableFilter"
99 }
100}