1use std::f32::consts::PI;
2
3use sim_lib_audio_graph_core::{PrepareConfig, ProcessBlock, Processor};
4
5use crate::common::{
6 clamp_cutoff, db_to_gain, input_sample, prepare_channels, prepared_output_channels,
7};
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub enum OnePoleMode {
12 LowPass,
14 HighPass,
16}
17
18#[derive(Clone, Copy, Debug, Default, PartialEq)]
19struct OnePoleState {
20 z1: f32,
21}
22
23#[derive(Clone, Debug, PartialEq)]
25pub struct OnePoleFilter {
26 mode: OnePoleMode,
27 cutoff_hz: f32,
28 sample_rate_hz: f32,
29 states: Vec<OnePoleState>,
30}
31
32impl OnePoleFilter {
33 pub fn low_pass(cutoff_hz: f32) -> Self {
35 Self::new(OnePoleMode::LowPass, cutoff_hz)
36 }
37
38 pub fn high_pass(cutoff_hz: f32) -> Self {
40 Self::new(OnePoleMode::HighPass, cutoff_hz)
41 }
42
43 pub fn new(mode: OnePoleMode, cutoff_hz: f32) -> Self {
45 Self {
46 mode,
47 cutoff_hz,
48 sample_rate_hz: 48_000.0,
49 states: Vec::new(),
50 }
51 }
52
53 fn alpha(&self) -> f32 {
54 let cutoff = clamp_cutoff(self.cutoff_hz, self.sample_rate_hz);
55 1.0 - (-2.0 * PI * cutoff / self.sample_rate_hz).exp()
56 }
57
58 #[cfg(all(test, not(debug_assertions)))]
59 pub(crate) fn realtime_state_snapshot(&self) -> Vec<usize> {
60 vec![self.states.capacity()]
61 }
62}
63
64impl Processor for OnePoleFilter {
65 fn prepare(&mut self, cfg: PrepareConfig) {
66 self.sample_rate_hz = cfg.sample_rate_hz as f32;
67 prepare_channels(
68 &mut self.states,
69 cfg.out_channels as usize,
70 OnePoleState::default(),
71 );
72 }
73
74 fn reset(&mut self) {
75 self.states.fill(OnePoleState::default());
76 }
77
78 fn process(&mut self, block: &mut ProcessBlock<'_>) {
79 let channels = prepared_output_channels(block, self.states.len(), "OnePoleFilter");
80 let alpha = self.alpha();
81 let frames = block.frames as usize;
82 for channel in 0..channels {
83 let state = &mut self.states[channel];
84 for frame in 0..frames {
85 let input = input_sample(block, channel, frame);
86 state.z1 += alpha * (input - state.z1);
87 block.out_audio[channel][frame] = match self.mode {
88 OnePoleMode::LowPass => state.z1,
89 OnePoleMode::HighPass => input - state.z1,
90 };
91 }
92 }
93 }
94}
95
96#[derive(Clone, Copy, Debug, PartialEq)]
98pub enum BiquadKind {
99 LowPass,
101 HighPass,
103 BandPass,
105 Notch,
107 Peaking {
109 gain_db: f32,
111 },
112}
113
114#[derive(Clone, Copy, Debug, PartialEq)]
115struct Coefficients {
116 b0: f32,
117 b1: f32,
118 b2: f32,
119 a1: f32,
120 a2: f32,
121}
122
123impl Default for Coefficients {
124 fn default() -> Self {
125 Self {
126 b0: 1.0,
127 b1: 0.0,
128 b2: 0.0,
129 a1: 0.0,
130 a2: 0.0,
131 }
132 }
133}
134
135#[derive(Clone, Copy, Debug, Default, PartialEq)]
136struct BiquadState {
137 z1: f32,
138 z2: f32,
139}
140
141#[derive(Clone, Debug, PartialEq)]
143pub struct BiquadFilter {
144 kind: BiquadKind,
145 frequency_hz: f32,
146 q: f32,
147 sample_rate_hz: f32,
148 coefficients: Coefficients,
149 states: Vec<BiquadState>,
150}
151
152impl BiquadFilter {
153 pub fn new(kind: BiquadKind, frequency_hz: f32, q: f32) -> Self {
156 let mut filter = Self {
157 kind,
158 frequency_hz,
159 q: q.max(0.05),
160 sample_rate_hz: 48_000.0,
161 coefficients: Coefficients::default(),
162 states: Vec::new(),
163 };
164 filter.update_coefficients();
165 filter
166 }
167
168 pub fn low_pass(frequency_hz: f32, q: f32) -> Self {
170 Self::new(BiquadKind::LowPass, frequency_hz, q)
171 }
172
173 pub fn high_pass(frequency_hz: f32, q: f32) -> Self {
175 Self::new(BiquadKind::HighPass, frequency_hz, q)
176 }
177
178 pub fn band_pass(frequency_hz: f32, q: f32) -> Self {
180 Self::new(BiquadKind::BandPass, frequency_hz, q)
181 }
182
183 pub fn notch(frequency_hz: f32, q: f32) -> Self {
185 Self::new(BiquadKind::Notch, frequency_hz, q)
186 }
187
188 fn update_coefficients(&mut self) {
189 let frequency = clamp_cutoff(self.frequency_hz, self.sample_rate_hz);
190 let omega = 2.0 * PI * frequency / self.sample_rate_hz;
191 let sin = omega.sin();
192 let cos = omega.cos();
193 let alpha = sin / (2.0 * self.q.max(0.05));
194 let (b0, b1, b2, a0, a1, a2) = match self.kind {
195 BiquadKind::LowPass => (
196 (1.0 - cos) * 0.5,
197 1.0 - cos,
198 (1.0 - cos) * 0.5,
199 1.0 + alpha,
200 -2.0 * cos,
201 1.0 - alpha,
202 ),
203 BiquadKind::HighPass => (
204 (1.0 + cos) * 0.5,
205 -(1.0 + cos),
206 (1.0 + cos) * 0.5,
207 1.0 + alpha,
208 -2.0 * cos,
209 1.0 - alpha,
210 ),
211 BiquadKind::BandPass => (alpha, 0.0, -alpha, 1.0 + alpha, -2.0 * cos, 1.0 - alpha),
212 BiquadKind::Notch => (1.0, -2.0 * cos, 1.0, 1.0 + alpha, -2.0 * cos, 1.0 - alpha),
213 BiquadKind::Peaking { gain_db } => {
214 let amp = db_to_gain(gain_db).sqrt();
215 (
216 1.0 + alpha * amp,
217 -2.0 * cos,
218 1.0 - alpha * amp,
219 1.0 + alpha / amp,
220 -2.0 * cos,
221 1.0 - alpha / amp,
222 )
223 }
224 };
225 self.coefficients = Coefficients {
226 b0: b0 / a0,
227 b1: b1 / a0,
228 b2: b2 / a0,
229 a1: a1 / a0,
230 a2: a2 / a0,
231 };
232 }
233
234 #[cfg(all(test, not(debug_assertions)))]
235 pub(crate) fn realtime_state_snapshot(&self) -> Vec<usize> {
236 vec![self.states.capacity()]
237 }
238}
239
240impl Processor for BiquadFilter {
241 fn prepare(&mut self, cfg: PrepareConfig) {
242 self.sample_rate_hz = cfg.sample_rate_hz as f32;
243 self.update_coefficients();
244 prepare_channels(
245 &mut self.states,
246 cfg.out_channels as usize,
247 BiquadState::default(),
248 );
249 }
250
251 fn reset(&mut self) {
252 self.states.fill(BiquadState::default());
253 }
254
255 fn process(&mut self, block: &mut ProcessBlock<'_>) {
256 let channels = prepared_output_channels(block, self.states.len(), "BiquadFilter");
257 let c = self.coefficients;
258 let frames = block.frames as usize;
259 for channel in 0..channels {
260 let state = &mut self.states[channel];
261 for frame in 0..frames {
262 let input = input_sample(block, channel, frame);
263 let output = c.b0 * input + state.z1;
264 state.z1 = c.b1 * input - c.a1 * output + state.z2;
265 state.z2 = c.b2 * input - c.a2 * output;
266 block.out_audio[channel][frame] = output;
267 }
268 }
269 }
270}
271
272#[derive(Clone, Copy, Debug, PartialEq, Eq)]
274pub enum StateVariableMode {
275 LowPass,
277 HighPass,
279 BandPass,
281 Notch,
283}
284
285#[derive(Clone, Copy, Debug, Default, PartialEq)]
286struct SvfState {
287 ic1eq: f32,
288 ic2eq: f32,
289}
290
291#[derive(Clone, Debug, PartialEq)]
293pub struct StateVariableFilter {
294 mode: StateVariableMode,
295 frequency_hz: f32,
296 q: f32,
297 sample_rate_hz: f32,
298 states: Vec<SvfState>,
299}
300
301impl StateVariableFilter {
302 pub fn new(mode: StateVariableMode, frequency_hz: f32, q: f32) -> Self {
305 Self {
306 mode,
307 frequency_hz,
308 q: q.max(0.05),
309 sample_rate_hz: 48_000.0,
310 states: Vec::new(),
311 }
312 }
313
314 fn process_sample(&self, state: &mut SvfState, input: f32) -> f32 {
315 let frequency = clamp_cutoff(self.frequency_hz, self.sample_rate_hz);
316 let g = (PI * frequency / self.sample_rate_hz).tan();
317 let k = 1.0 / self.q.max(0.05);
318 let a1 = 1.0 / (1.0 + g * (g + k));
319 let a2 = g * a1;
320 let a3 = g * a2;
321 let v3 = input - state.ic2eq;
322 let v1 = a1 * state.ic1eq + a2 * v3;
323 let v2 = state.ic2eq + a2 * state.ic1eq + a3 * v3;
324 state.ic1eq = 2.0 * v1 - state.ic1eq;
325 state.ic2eq = 2.0 * v2 - state.ic2eq;
326 let low = v2;
327 let high = input - k * v1 - v2;
328 match self.mode {
329 StateVariableMode::LowPass => low,
330 StateVariableMode::HighPass => high,
331 StateVariableMode::BandPass => v1,
332 StateVariableMode::Notch => low + high,
333 }
334 }
335
336 #[cfg(all(test, not(debug_assertions)))]
337 pub(crate) fn realtime_state_snapshot(&self) -> Vec<usize> {
338 vec![self.states.capacity()]
339 }
340}
341
342impl Processor for StateVariableFilter {
343 fn prepare(&mut self, cfg: PrepareConfig) {
344 self.sample_rate_hz = cfg.sample_rate_hz as f32;
345 prepare_channels(
346 &mut self.states,
347 cfg.out_channels as usize,
348 SvfState::default(),
349 );
350 }
351
352 fn reset(&mut self) {
353 self.states.fill(SvfState::default());
354 }
355
356 fn process(&mut self, block: &mut ProcessBlock<'_>) {
357 let channels = prepared_output_channels(block, self.states.len(), "StateVariableFilter");
358 let frames = block.frames as usize;
359 for channel in 0..channels {
360 for frame in 0..frames {
361 let input = input_sample(block, channel, frame);
362 let mut state = self.states[channel];
363 let output = self.process_sample(&mut state, input);
364 self.states[channel] = state;
365 block.out_audio[channel][frame] = output;
366 }
367 }
368 }
369}