1use aether_core::{node::DspNode, param::ParamBlock, BUFFER_SIZE, MAX_INPUTS};
13
14#[derive(Debug, Clone, Copy)]
16struct BiquadCoeffs {
17 b0: f32,
18 b1: f32,
19 b2: f32,
20 a1: f32,
21 a2: f32,
22}
23
24#[derive(Debug, Clone, Copy)]
26struct BiquadState {
27 x1: f32,
28 x2: f32,
29 y1: f32,
30 y2: f32,
31}
32
33impl BiquadState {
34 fn new() -> Self {
35 Self {
36 x1: 0.0,
37 x2: 0.0,
38 y1: 0.0,
39 y2: 0.0,
40 }
41 }
42
43 #[inline(always)]
44 fn process(&mut self, x: f32, coeffs: &BiquadCoeffs) -> f32 {
45 let y = coeffs.b0 * x + coeffs.b1 * self.x1 + coeffs.b2 * self.x2
46 - coeffs.a1 * self.y1
47 - coeffs.a2 * self.y2;
48
49 self.x2 = self.x1;
50 self.x1 = x;
51 self.y2 = self.y1;
52 self.y1 = y;
53
54 y
55 }
56}
57
58impl BiquadCoeffs {
59 fn peaking(freq: f32, gain_db: f32, q: f32, sample_rate: f32) -> Self {
61 let a = 10.0f32.powf(gain_db / 40.0);
62 let omega = 2.0 * std::f32::consts::PI * freq / sample_rate;
63 let sin_omega = omega.sin();
64 let cos_omega = omega.cos();
65 let alpha = sin_omega / (2.0 * q);
66
67 let b0 = 1.0 + alpha * a;
68 let b1 = -2.0 * cos_omega;
69 let b2 = 1.0 - alpha * a;
70 let a0 = 1.0 + alpha / a;
71 let a1 = -2.0 * cos_omega;
72 let a2 = 1.0 - alpha / a;
73
74 Self {
75 b0: b0 / a0,
76 b1: b1 / a0,
77 b2: b2 / a0,
78 a1: a1 / a0,
79 a2: a2 / a0,
80 }
81 }
82
83 fn low_shelf(freq: f32, gain_db: f32, sample_rate: f32) -> Self {
85 let a = 10.0f32.powf(gain_db / 40.0);
86 let omega = 2.0 * std::f32::consts::PI * freq / sample_rate;
87 let sin_omega = omega.sin();
88 let cos_omega = omega.cos();
89 let alpha = sin_omega / 2.0 * ((a + 1.0 / a) * (1.0 / 0.75 - 1.0) + 2.0).sqrt();
90
91 let a_plus_1 = a + 1.0;
92 let a_minus_1 = a - 1.0;
93 let sqrt_a = a.sqrt();
94
95 let b0 = a * (a_plus_1 - a_minus_1 * cos_omega + 2.0 * sqrt_a * alpha);
96 let b1 = 2.0 * a * (a_minus_1 - a_plus_1 * cos_omega);
97 let b2 = a * (a_plus_1 - a_minus_1 * cos_omega - 2.0 * sqrt_a * alpha);
98 let a0 = a_plus_1 + a_minus_1 * cos_omega + 2.0 * sqrt_a * alpha;
99 let a1 = -2.0 * (a_minus_1 + a_plus_1 * cos_omega);
100 let a2 = a_plus_1 + a_minus_1 * cos_omega - 2.0 * sqrt_a * alpha;
101
102 Self {
103 b0: b0 / a0,
104 b1: b1 / a0,
105 b2: b2 / a0,
106 a1: a1 / a0,
107 a2: a2 / a0,
108 }
109 }
110
111 fn high_shelf(freq: f32, gain_db: f32, sample_rate: f32) -> Self {
113 let a = 10.0f32.powf(gain_db / 40.0);
114 let omega = 2.0 * std::f32::consts::PI * freq / sample_rate;
115 let sin_omega = omega.sin();
116 let cos_omega = omega.cos();
117 let alpha = sin_omega / 2.0 * ((a + 1.0 / a) * (1.0 / 0.75 - 1.0) + 2.0).sqrt();
118
119 let a_plus_1 = a + 1.0;
120 let a_minus_1 = a - 1.0;
121 let sqrt_a = a.sqrt();
122
123 let b0 = a * (a_plus_1 + a_minus_1 * cos_omega + 2.0 * sqrt_a * alpha);
124 let b1 = -2.0 * a * (a_minus_1 + a_plus_1 * cos_omega);
125 let b2 = a * (a_plus_1 + a_minus_1 * cos_omega - 2.0 * sqrt_a * alpha);
126 let a0 = a_plus_1 - a_minus_1 * cos_omega + 2.0 * sqrt_a * alpha;
127 let a1 = 2.0 * (a_minus_1 - a_plus_1 * cos_omega);
128 let a2 = a_plus_1 - a_minus_1 * cos_omega - 2.0 * sqrt_a * alpha;
129
130 Self {
131 b0: b0 / a0,
132 b1: b1 / a0,
133 b2: b2 / a0,
134 a1: a1 / a0,
135 a2: a2 / a0,
136 }
137 }
138}
139
140pub struct ParametricEq {
141 low_state: BiquadState,
142 mid_state: BiquadState,
143 high_state: BiquadState,
144 low_coeffs: BiquadCoeffs,
145 mid_coeffs: BiquadCoeffs,
146 high_coeffs: BiquadCoeffs,
147 last_sample_rate: f32,
148}
149
150impl ParametricEq {
151 pub fn new() -> Self {
152 Self {
153 low_state: BiquadState::new(),
154 mid_state: BiquadState::new(),
155 high_state: BiquadState::new(),
156 low_coeffs: BiquadCoeffs::low_shelf(100.0, 0.0, 48000.0),
157 mid_coeffs: BiquadCoeffs::peaking(1000.0, 0.0, 1.0, 48000.0),
158 high_coeffs: BiquadCoeffs::high_shelf(5000.0, 0.0, 48000.0),
159 last_sample_rate: 48000.0,
160 }
161 }
162}
163
164impl Default for ParametricEq {
165 fn default() -> Self {
166 Self::new()
167 }
168}
169
170impl DspNode for ParametricEq {
171 fn process(
172 &mut self,
173 inputs: &[Option<&[f32; BUFFER_SIZE]>; MAX_INPUTS],
174 output: &mut [f32; BUFFER_SIZE],
175 params: &mut ParamBlock,
176 sample_rate: f32,
177 ) {
178 let silence = [0.0f32; BUFFER_SIZE];
179 let input = inputs[0].unwrap_or(&silence);
180
181 let low_freq = params.get(0).current.clamp(20.0, 500.0);
183 let low_gain = params.get(1).current.clamp(-24.0, 24.0);
184 let mid_freq = params.get(2).current.clamp(200.0, 5000.0);
185 let mid_gain = params.get(3).current.clamp(-24.0, 24.0);
186 let mid_q = params.get(4).current.clamp(0.1, 10.0);
187 let high_freq = params.get(5).current.clamp(2000.0, 20000.0);
188 let high_gain = params.get(6).current.clamp(-24.0, 24.0);
189
190 if (sample_rate - self.last_sample_rate).abs() > 0.1 {
192 self.last_sample_rate = sample_rate;
193 self.low_coeffs = BiquadCoeffs::low_shelf(low_freq, low_gain, sample_rate);
194 self.mid_coeffs = BiquadCoeffs::peaking(mid_freq, mid_gain, mid_q, sample_rate);
195 self.high_coeffs = BiquadCoeffs::high_shelf(high_freq, high_gain, sample_rate);
196 }
197
198 for i in 0..BUFFER_SIZE {
199 let x = input[i];
200
201 let y1 = self.low_state.process(x, &self.low_coeffs);
203 let y2 = self.mid_state.process(y1, &self.mid_coeffs);
204 let y3 = self.high_state.process(y2, &self.high_coeffs);
205
206 output[i] = y3;
207 params.tick_all();
208 }
209 }
210
211 fn type_name(&self) -> &'static str {
212 "ParametricEq"
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn test_eq_silence_passthrough() {
222 let mut eq = ParametricEq::new();
223 let mut params = ParamBlock::new();
224 for &v in &[100.0f32, 0.0, 1000.0, 0.0, 1.0, 5000.0, 0.0] {
226 params.add(v);
227 }
228 let input = [0.0f32; BUFFER_SIZE];
229 let inputs = [Some(&input); MAX_INPUTS];
230 let mut output = [0.0f32; BUFFER_SIZE];
231 eq.process(&inputs, &mut output, &mut params, 48000.0);
232 for s in &output {
233 assert!(s.abs() < 1e-6, "silence should pass through");
234 }
235 }
236
237 #[test]
238 fn test_eq_processes_signal() {
239 let mut eq = ParametricEq::new();
240 let mut params = ParamBlock::new();
241 for &v in &[100.0f32, 0.0, 1000.0, 12.0, 1.0, 5000.0, 0.0] {
243 params.add(v);
244 }
245 let input = [0.1f32; BUFFER_SIZE];
246 let inputs = [Some(&input); MAX_INPUTS];
247 let mut output = [0.0f32; BUFFER_SIZE];
248 eq.process(&inputs, &mut output, &mut params, 48000.0);
249 assert!(
251 output[BUFFER_SIZE - 1].abs() > 0.0,
252 "EQ should process signal"
253 );
254 }
255}