firewheel_nodes/
svf.rs

1use core::ops::Range;
2
3use firewheel_core::{
4    channel_config::{ChannelConfig, ChannelCount},
5    diff::{Diff, Patch},
6    dsp::{
7        coeff_update::{CoeffUpdateFactor, CoeffUpdateMask},
8        declick::{DeclickFadeCurve, Declicker},
9        filter::{
10            butterworth::Q_BUTTERWORTH_ORD2,
11            smoothing_filter::DEFAULT_SMOOTH_SECONDS,
12            svf::{SvfCoeff, SvfCoeffSimd, SvfStateSimd},
13        },
14        volume::{db_to_amp, Volume},
15    },
16    event::ProcEvents,
17    node::{
18        AudioNode, AudioNodeInfo, AudioNodeProcessor, ConstructProcessorContext, ProcBuffers,
19        ProcExtra, ProcInfo, ProcStreamCtx, ProcessStatus,
20    },
21    param::smoother::{SmoothedParam, SmootherConfig},
22    StreamInfo,
23};
24
25pub const DEFAULT_Q: f32 = Q_BUTTERWORTH_ORD2;
26
27pub const DEFAULT_MIN_HZ: f32 = 20.0;
28pub const DEFAULT_MAX_HZ: f32 = 20_480.0;
29pub const DEFAULT_MIN_Q: f32 = 0.02;
30pub const DEFAULT_MAX_Q: f32 = 40.0;
31pub const DEFAULT_MIN_GAIN_DB: f32 = -24.0;
32pub const DEFAULT_MAX_GAIN_DB: f32 = 24.0;
33
34pub type SvfMonoNode = SvfNode<1>;
35pub type SvfStereoNode = SvfNode<2>;
36
37/// The configuration for an [`SvfNode`]
38#[derive(Debug, Clone, PartialEq)]
39#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
40#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
41#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
42pub struct SvfNodeConfig {
43    /// The minimum and maximum values for cutoff frequency in hertz.
44    ///
45    /// By default this is set to `20.0..20480.0`.
46    ///
47    /// It is generally not recommended to increase this range
48    /// unless you know what you are doing.
49    pub freq_range: Range<f32>,
50
51    /// The minimum and maximum values for q values.
52    ///
53    /// By default this is set to `0.02..40.0`.
54    ///
55    /// It is generally not recommended to increase this range
56    /// unless you know what you are doing.
57    pub q_range: Range<f32>,
58
59    /// The minimum and maximum values for filter gain (in decibels).
60    ///
61    /// By default this is set to `-24.0..24.0`.
62    ///
63    /// It is generally not recommended to increase this range
64    /// unless you know what you are doing.
65    pub gain_db_range: Range<f32>,
66}
67
68impl Default for SvfNodeConfig {
69    fn default() -> Self {
70        Self {
71            freq_range: DEFAULT_MIN_HZ..DEFAULT_MAX_HZ,
72            q_range: DEFAULT_MIN_Q..DEFAULT_MAX_Q,
73            gain_db_range: DEFAULT_MIN_GAIN_DB..DEFAULT_MAX_GAIN_DB,
74        }
75    }
76}
77
78/// The filter type to use for an [`SvfNode`]
79#[derive(Default, Diff, Patch, Debug, Clone, Copy, PartialEq, Eq)]
80#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
81#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
82pub enum SvfType {
83    // Lowpass (-12 dB per octave)
84    #[default]
85    Lowpass,
86    // Lowpass (-24 dB per octave)
87    LowpassX2,
88    // Lowpass (-12 dB per octave)
89    Highpass,
90    // Lowpass (-24 dB per octave)
91    HighpassX2,
92    // Bandpass (-12 dB per octave)
93    Bandpass,
94    LowShelf,
95    HighShelf,
96    Bell,
97    Notch,
98    Allpass,
99}
100
101/// An SVF (state variable filter) node
102///
103/// This is based on the filter model developed by Andrew Simper:
104/// <https://cytomic.com/files/dsp/SvfLinearTrapOptimised2.pdf>
105#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
106#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
107#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
108#[derive(Diff, Patch, Debug, Clone, Copy, PartialEq)]
109pub struct SvfNode<const CHANNELS: usize> {
110    /// The type of filter
111    pub filter_type: SvfType,
112
113    /// The cutoff frequency in hertz in the range `[20.0, 20480.0]`.
114    pub cutoff_hz: f32,
115    /// The quality (q) factor
116    ///
117    /// This is also sometimes referred to as "bandwidth", but note the
118    /// formula to convert bandwidth in hertz to q is:
119    ///
120    /// `Q = cutoff_hz / BW`
121    ///
122    /// and the formula to convert bandwidth in octaves to q is:
123    ///
124    /// `Q = sqrt(2^BW) / (2^BW - 1)`
125    pub q_factor: f32,
126    /// The filter gain
127    ///
128    /// This only has effect if the filter type is one of the following:
129    /// * [`SvfType::LowShelf`]
130    /// * [`SvfType::HighShelf`]
131    /// * [`SvfType::Bell`]
132    pub gain: Volume,
133    /// Whether or not this node is enabled.
134    pub enabled: bool,
135
136    /// The time in seconds of the internal smoothing filter.
137    ///
138    /// By default this is set to `0.015` (15ms).
139    pub smooth_seconds: f32,
140
141    /// An exponent representing the rate at which DSP coefficients are
142    /// updated when parameters are being smoothed.
143    ///
144    /// Smaller values will produce less "stair-stepping" artifacts,
145    /// but will also consume more CPU.
146    ///
147    /// The resulting number of frames (samples in a single channel of audio)
148    /// that will elapse between each update is calculated as
149    /// `2^coeff_update_factor`.
150    ///
151    /// By default this is set to `5`.
152    pub coeff_update_factor: CoeffUpdateFactor,
153}
154
155impl<const CHANNELS: usize> Default for SvfNode<CHANNELS> {
156    fn default() -> Self {
157        Self {
158            filter_type: SvfType::Lowpass,
159            cutoff_hz: 1_000.0,
160            q_factor: DEFAULT_Q,
161            gain: Volume::Decibels(0.0),
162            enabled: true,
163            smooth_seconds: DEFAULT_SMOOTH_SECONDS,
164            coeff_update_factor: CoeffUpdateFactor(5),
165        }
166    }
167}
168
169impl<const CHANNELS: usize> SvfNode<CHANNELS> {
170    /// Construct a new SVF node with the lowpass filter type of order 2.
171    ///
172    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
173    /// * `q_factor` - The quality (q) factor
174    /// * `enabled` - Whether or not this node is enabled
175    pub const fn from_lowpass(cutoff_hz: f32, q_factor: f32, enabled: bool) -> Self {
176        Self {
177            filter_type: SvfType::Lowpass,
178            cutoff_hz,
179            q_factor,
180            gain: Volume::UNITY_GAIN,
181            enabled,
182            smooth_seconds: DEFAULT_SMOOTH_SECONDS,
183            coeff_update_factor: CoeffUpdateFactor(5),
184        }
185    }
186
187    /// Construct a new SVF node with the lowpass filter type of order 4.
188    ///
189    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
190    /// * `q_factor` - The quality (q) factor
191    /// * `enabled` - Whether or not this node is enabled
192    pub const fn from_lowpass_x2(cutoff_hz: f32, q_factor: f32, enabled: bool) -> Self {
193        Self {
194            filter_type: SvfType::LowpassX2,
195            cutoff_hz,
196            q_factor,
197            gain: Volume::UNITY_GAIN,
198            enabled,
199            smooth_seconds: DEFAULT_SMOOTH_SECONDS,
200            coeff_update_factor: CoeffUpdateFactor(5),
201        }
202    }
203
204    /// Construct a new SVF node with the highpass filter type of order 2.
205    ///
206    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
207    /// * `q_factor` - The quality (q) factor
208    /// * `enabled` - Whether or not this node is enabled
209    pub const fn from_highpass(cutoff_hz: f32, q_factor: f32, enabled: bool) -> Self {
210        Self {
211            filter_type: SvfType::Highpass,
212            cutoff_hz,
213            q_factor,
214            gain: Volume::UNITY_GAIN,
215            enabled,
216            smooth_seconds: DEFAULT_SMOOTH_SECONDS,
217            coeff_update_factor: CoeffUpdateFactor(5),
218        }
219    }
220
221    /// Construct a new SVF node with the highpass filter type of order 4.
222    ///
223    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
224    /// * `q_factor` - The quality (q) factor
225    /// * `enabled` - Whether or not this node is enabled
226    pub const fn from_highpass_x2(cutoff_hz: f32, q_factor: f32, enabled: bool) -> Self {
227        Self {
228            filter_type: SvfType::HighpassX2,
229            cutoff_hz,
230            q_factor,
231            gain: Volume::UNITY_GAIN,
232            enabled,
233            smooth_seconds: DEFAULT_SMOOTH_SECONDS,
234            coeff_update_factor: CoeffUpdateFactor(5),
235        }
236    }
237
238    /// Construct a new SVF node with the bandpass filter type.
239    ///
240    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
241    /// * `q_factor` - The quality (q) factor
242    /// * `enabled` - Whether or not this node is enabled
243    pub const fn from_bandpass(cutoff_hz: f32, q_factor: f32, enabled: bool) -> Self {
244        Self {
245            filter_type: SvfType::Bandpass,
246            cutoff_hz,
247            q_factor,
248            gain: Volume::UNITY_GAIN,
249            enabled,
250            smooth_seconds: DEFAULT_SMOOTH_SECONDS,
251            coeff_update_factor: CoeffUpdateFactor(5),
252        }
253    }
254
255    /// Construct a new SVF node with the lowshelf filter type.
256    ///
257    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
258    /// * `gain` - The filter gain
259    /// * `q_factor` - The quality (q) factor
260    /// * `enabled` - Whether or not this node is enabled
261    pub const fn from_lowshelf(cutoff_hz: f32, gain: Volume, q_factor: f32, enabled: bool) -> Self {
262        Self {
263            filter_type: SvfType::LowShelf,
264            cutoff_hz,
265            q_factor,
266            gain,
267            enabled,
268            smooth_seconds: DEFAULT_SMOOTH_SECONDS,
269            coeff_update_factor: CoeffUpdateFactor(5),
270        }
271    }
272
273    /// Construct a new SVF node with the highshelf filter type.
274    ///
275    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
276    /// * `gain` - The filter gain
277    /// * `q_factor` - The quality (q) factor
278    /// * `enabled` - Whether or not this node is enabled
279    pub const fn from_highshelf(
280        cutoff_hz: f32,
281        gain: Volume,
282        q_factor: f32,
283        enabled: bool,
284    ) -> Self {
285        Self {
286            filter_type: SvfType::HighShelf,
287            cutoff_hz,
288            q_factor,
289            gain,
290            enabled,
291            smooth_seconds: DEFAULT_SMOOTH_SECONDS,
292            coeff_update_factor: CoeffUpdateFactor(5),
293        }
294    }
295
296    /// Construct a new SVF node with the bell filter type.
297    ///
298    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
299    /// * `gain` - The filter gain
300    /// * `q_factor` - The quality (q) factor
301    /// * `enabled` - Whether or not this node is enabled
302    pub const fn from_bell(cutoff_hz: f32, gain: Volume, q_factor: f32, enabled: bool) -> Self {
303        Self {
304            filter_type: SvfType::Bell,
305            cutoff_hz,
306            q_factor,
307            gain,
308            enabled,
309            smooth_seconds: DEFAULT_SMOOTH_SECONDS,
310            coeff_update_factor: CoeffUpdateFactor(5),
311        }
312    }
313
314    /// Construct a new SVF node with the notch filter type.
315    ///
316    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
317    /// * `q_factor` - The quality (q) factor
318    /// * `enabled` - Whether or not this node is enabled
319    pub const fn from_notch(cutoff_hz: f32, q_factor: f32, enabled: bool) -> Self {
320        Self {
321            filter_type: SvfType::Notch,
322            cutoff_hz,
323            q_factor,
324            gain: Volume::UNITY_GAIN,
325            enabled,
326            smooth_seconds: DEFAULT_SMOOTH_SECONDS,
327            coeff_update_factor: CoeffUpdateFactor(5),
328        }
329    }
330
331    /// Construct a new SVF node with the allpass filter type.
332    ///
333    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
334    /// * `q_factor` - The quality (q) factor
335    /// * `enabled` - Whether or not this node is enabled
336    pub const fn from_allpass(cutoff_hz: f32, q_factor: f32, enabled: bool) -> Self {
337        Self {
338            filter_type: SvfType::Allpass,
339            cutoff_hz,
340            q_factor,
341            gain: Volume::UNITY_GAIN,
342            enabled,
343            smooth_seconds: DEFAULT_SMOOTH_SECONDS,
344            coeff_update_factor: CoeffUpdateFactor(5),
345        }
346    }
347
348    /// Set the parameters to use a lowpass filter type of order 2.
349    ///
350    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
351    /// * `q_factor` - The quality (q) factor
352    pub const fn set_lowpass(&mut self, cutoff_hz: f32, q_factor: f32) {
353        self.filter_type = SvfType::Lowpass;
354        self.cutoff_hz = cutoff_hz;
355        self.q_factor = q_factor;
356    }
357
358    /// Set the parameters to use a lowpass filter type of order 4.
359    ///
360    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
361    /// * `q_factor` - The quality (q) factor
362    pub const fn set_lowpass_x2(&mut self, cutoff_hz: f32, q_factor: f32) {
363        self.filter_type = SvfType::LowpassX2;
364        self.cutoff_hz = cutoff_hz;
365        self.q_factor = q_factor;
366    }
367
368    /// Set the parameters to use a highpass filter type of order 2.
369    ///
370    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
371    /// * `q_factor` - The quality (q) factor
372    pub const fn set_highpass(&mut self, cutoff_hz: f32, q_factor: f32) {
373        self.filter_type = SvfType::Highpass;
374        self.cutoff_hz = cutoff_hz;
375        self.q_factor = q_factor;
376    }
377
378    /// Set the parameters to use a highpass filter type of order 4.
379    ///
380    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
381    /// * `q_factor` - The quality (q) factor
382    pub const fn set_highpass_x2(&mut self, cutoff_hz: f32, q_factor: f32) {
383        self.filter_type = SvfType::HighpassX2;
384        self.cutoff_hz = cutoff_hz;
385        self.q_factor = q_factor;
386    }
387
388    /// Set the parameters to use a bandpass filter type.
389    ///
390    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
391    /// * `q_factor` - The quality (q) factor
392    pub const fn set_bandpass(&mut self, cutoff_hz: f32, q_factor: f32) {
393        self.filter_type = SvfType::Bandpass;
394        self.cutoff_hz = cutoff_hz;
395        self.q_factor = q_factor;
396    }
397
398    /// Set the parameters to use a lowshelf filter type.
399    ///
400    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
401    /// * `gain` - The filter gain
402    /// * `q_factor` - The quality (q) factor
403    pub const fn set_lowshelf(&mut self, cutoff_hz: f32, gain: Volume, q_factor: f32) {
404        self.filter_type = SvfType::LowShelf;
405        self.cutoff_hz = cutoff_hz;
406        self.gain = gain;
407        self.q_factor = q_factor;
408    }
409
410    /// Set the parameters to use a highshelf filter type.
411    ///
412    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
413    /// * `gain` - The filter gain
414    /// * `q_factor` - The quality (q) factor
415    pub const fn set_highshelf(&mut self, cutoff_hz: f32, gain: Volume, q_factor: f32) {
416        self.filter_type = SvfType::HighShelf;
417        self.cutoff_hz = cutoff_hz;
418        self.gain = gain;
419        self.q_factor = q_factor;
420    }
421
422    /// Set the parameters to use a bell filter type.
423    ///
424    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
425    /// * `gain` - The filter gain
426    /// * `q_factor` - The quality (q) factor
427    pub const fn set_bell(&mut self, cutoff_hz: f32, gain: Volume, q_factor: f32) {
428        self.filter_type = SvfType::Bell;
429        self.cutoff_hz = cutoff_hz;
430        self.gain = gain;
431        self.q_factor = q_factor;
432    }
433
434    /// Set the parameters to use a notch filter type.
435    ///
436    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
437    /// * `q_factor` - The quality (q) factor
438    pub const fn set_notch(&mut self, cutoff_hz: f32, q_factor: f32) {
439        self.filter_type = SvfType::Notch;
440        self.cutoff_hz = cutoff_hz;
441        self.q_factor = q_factor;
442    }
443
444    /// Set the parameters to use an allpass filter type.
445    ///
446    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
447    /// * `q_factor` - The quality (q) factor
448    pub const fn set_allpass(&mut self, cutoff_hz: f32, q_factor: f32) {
449        self.filter_type = SvfType::Allpass;
450        self.cutoff_hz = cutoff_hz;
451        self.q_factor = q_factor;
452    }
453
454    /// Set the given filter gain in a linear scale, where `0.0` is silence and
455    /// `1.0` is unity gain.
456    ///
457    /// These units are suitable for volume sliders (simply convert percent
458    /// volume to linear volume by diving the percent volume by 100).
459    ///
460    /// This only has effect if the filter type is one of the following:
461    /// * [`SvfType::LowShelf`]
462    /// * [`SvfType::HighShelf`]
463    /// * [`SvfType::Bell`]
464    pub const fn set_gain_linear(&mut self, linear: f32) {
465        self.gain = Volume::Linear(linear);
466    }
467
468    /// Set the given filter gain in decibels, where `0.0` is unity gain and
469    /// `f32::NEG_INFINITY` is silence.
470    ///
471    /// This only has effect if the filter type is one of the following:
472    /// * [`SvfType::LowShelf`]
473    /// * [`SvfType::HighShelf`]
474    /// * [`SvfType::Bell`]
475    pub const fn set_gain_decibels(&mut self, decibels: f32) {
476        self.gain = Volume::Decibels(decibels);
477    }
478}
479
480impl<const CHANNELS: usize> AudioNode for SvfNode<CHANNELS> {
481    type Configuration = SvfNodeConfig;
482
483    fn info(&self, _config: &Self::Configuration) -> AudioNodeInfo {
484        AudioNodeInfo::new()
485            .debug_name("svf")
486            .channel_config(ChannelConfig {
487                num_inputs: ChannelCount::new(CHANNELS as u32).unwrap(),
488                num_outputs: ChannelCount::new(CHANNELS as u32).unwrap(),
489            })
490    }
491
492    fn construct_processor(
493        &self,
494        config: &Self::Configuration,
495        cx: ConstructProcessorContext,
496    ) -> impl AudioNodeProcessor {
497        let cutoff_hz = self
498            .cutoff_hz
499            .clamp(config.freq_range.start, config.freq_range.end);
500        let q_factor = self
501            .q_factor
502            .clamp(config.q_range.start, config.q_range.end);
503
504        let min_gain = db_to_amp(config.gain_db_range.start);
505        let max_gain = db_to_amp(config.gain_db_range.end);
506        let mut gain = self.gain.amp().clamp(min_gain, max_gain);
507        if gain > 0.99999 && gain < 1.00001 {
508            gain = 1.0;
509        }
510
511        let mut new_self = Processor {
512            filter_0: SvfStateSimd::<CHANNELS>::default(),
513            filter_1: SvfStateSimd::<CHANNELS>::default(),
514            num_filters: 0,
515            filter_0_coeff: SvfCoeffSimd::<CHANNELS>::default(),
516            filter_1_coeff: SvfCoeffSimd::<CHANNELS>::default(),
517            filter_type: self.filter_type,
518            cutoff_hz: SmoothedParam::new(
519                cutoff_hz,
520                SmootherConfig {
521                    smooth_seconds: self.smooth_seconds,
522                    ..Default::default()
523                },
524                cx.stream_info.sample_rate,
525            ),
526            q_factor: SmoothedParam::new(
527                q_factor,
528                SmootherConfig {
529                    smooth_seconds: self.smooth_seconds,
530                    ..Default::default()
531                },
532                cx.stream_info.sample_rate,
533            ),
534            gain: SmoothedParam::new(
535                gain,
536                SmootherConfig {
537                    smooth_seconds: self.smooth_seconds,
538                    ..Default::default()
539                },
540                cx.stream_info.sample_rate,
541            ),
542            enable_declicker: Declicker::from_enabled(self.enabled),
543            freq_range: config.freq_range.clone(),
544            q_range: config.q_range.clone(),
545            gain_range: min_gain..max_gain,
546            coeff_update_mask: self.coeff_update_factor.mask(),
547        };
548
549        new_self.calc_coefficients(cx.stream_info.sample_rate_recip as f32);
550
551        new_self
552    }
553}
554
555struct Processor<const CHANNELS: usize> {
556    filter_0: SvfStateSimd<CHANNELS>,
557    filter_1: SvfStateSimd<CHANNELS>,
558    num_filters: usize,
559
560    filter_0_coeff: SvfCoeffSimd<CHANNELS>,
561    filter_1_coeff: SvfCoeffSimd<CHANNELS>,
562
563    filter_type: SvfType,
564    cutoff_hz: SmoothedParam,
565    q_factor: SmoothedParam,
566    gain: SmoothedParam,
567
568    enable_declicker: Declicker,
569
570    freq_range: Range<f32>,
571    q_range: Range<f32>,
572    gain_range: Range<f32>,
573    coeff_update_mask: CoeffUpdateMask,
574}
575
576impl<const CHANNELS: usize> Processor<CHANNELS> {
577    pub fn calc_coefficients(&mut self, sample_rate_recip: f32) {
578        let cutoff_hz = self.cutoff_hz.target_value();
579        let q = self.q_factor.target_value();
580        let gain = self.gain.target_value();
581
582        match self.filter_type {
583            SvfType::Lowpass => {
584                self.num_filters = 1;
585
586                self.filter_0_coeff =
587                    SvfCoeffSimd::splat(SvfCoeff::lowpass_ord2(cutoff_hz, q, sample_rate_recip));
588            }
589            SvfType::LowpassX2 => {
590                self.num_filters = 2;
591
592                let [coeff_0, coeff_1] = SvfCoeff::lowpass_ord4(cutoff_hz, q, sample_rate_recip);
593                self.filter_0_coeff = SvfCoeffSimd::splat(coeff_0);
594                self.filter_1_coeff = SvfCoeffSimd::splat(coeff_1);
595            }
596            SvfType::Highpass => {
597                self.num_filters = 1;
598
599                self.filter_0_coeff =
600                    SvfCoeffSimd::splat(SvfCoeff::highpass_ord2(cutoff_hz, q, sample_rate_recip));
601            }
602            SvfType::HighpassX2 => {
603                self.num_filters = 2;
604
605                let [coeff_0, coeff_1] = SvfCoeff::highpass_ord4(cutoff_hz, q, sample_rate_recip);
606                self.filter_0_coeff = SvfCoeffSimd::splat(coeff_0);
607                self.filter_1_coeff = SvfCoeffSimd::splat(coeff_1);
608            }
609            SvfType::Bandpass => {
610                self.num_filters = 2;
611
612                self.filter_0_coeff =
613                    SvfCoeffSimd::splat(SvfCoeff::lowpass_ord2(cutoff_hz, q, sample_rate_recip));
614                self.filter_1_coeff =
615                    SvfCoeffSimd::splat(SvfCoeff::highpass_ord2(cutoff_hz, q, sample_rate_recip));
616            }
617            SvfType::LowShelf => {
618                self.num_filters = 1;
619
620                self.filter_0_coeff =
621                    SvfCoeffSimd::splat(SvfCoeff::low_shelf(cutoff_hz, q, gain, sample_rate_recip));
622            }
623            SvfType::HighShelf => {
624                self.num_filters = 1;
625
626                self.filter_0_coeff = SvfCoeffSimd::splat(SvfCoeff::high_shelf(
627                    cutoff_hz,
628                    q,
629                    gain,
630                    sample_rate_recip,
631                ));
632            }
633            SvfType::Bell => {
634                self.num_filters = 1;
635
636                self.filter_0_coeff =
637                    SvfCoeffSimd::splat(SvfCoeff::bell(cutoff_hz, q, gain, sample_rate_recip));
638            }
639            SvfType::Notch => {
640                self.num_filters = 1;
641
642                self.filter_0_coeff =
643                    SvfCoeffSimd::splat(SvfCoeff::notch(cutoff_hz, q, sample_rate_recip));
644            }
645            SvfType::Allpass => {
646                self.num_filters = 1;
647
648                self.filter_0_coeff =
649                    SvfCoeffSimd::splat(SvfCoeff::allpass(cutoff_hz, q, sample_rate_recip));
650            }
651        }
652
653        if self.num_filters == 1 {
654            self.filter_1.reset();
655        }
656    }
657
658    fn loop_lowpass_ord2(
659        &mut self,
660        info: &ProcInfo,
661        inputs: &[&[f32]],
662        outputs: &mut [&mut [f32]],
663    ) {
664        assert!(inputs.len() == CHANNELS);
665        assert!(outputs.len() == CHANNELS);
666        for ch in inputs.iter() {
667            assert!(ch.len() >= info.frames);
668        }
669        for ch in outputs.iter() {
670            assert!(ch.len() >= info.frames);
671        }
672
673        for i in 0..info.frames {
674            let cutoff_hz = self.cutoff_hz.next_smoothed();
675            let q = self.q_factor.next_smoothed();
676
677            // Because recalculating filter coefficients is expensive, a trick like
678            // this can be used to only recalculate them every few frames.
679            //
680            // TODO: use core::hint::cold_path() once that stabilizes
681            //
682            // TODO: Alternatively, this could be optimized using a lookup table
683            if self.coeff_update_mask.do_update(i) {
684                self.filter_0_coeff = SvfCoeffSimd::splat(SvfCoeff::lowpass_ord2(
685                    cutoff_hz,
686                    q,
687                    info.sample_rate_recip as f32,
688                ));
689            }
690
691            let s: [f32; CHANNELS] = core::array::from_fn(|ch_i| {
692                // Safety: These bounds have been checked above.
693                unsafe { *inputs.get_unchecked(ch_i).get_unchecked(i) }
694            });
695
696            let out = self.filter_0.process(s, &self.filter_0_coeff);
697
698            for ch_i in 0..CHANNELS {
699                // Safety: These bounds have been checked above.
700                unsafe {
701                    *outputs.get_unchecked_mut(ch_i).get_unchecked_mut(i) = out[ch_i];
702                }
703            }
704        }
705    }
706
707    fn loop_lowpass_ord4(
708        &mut self,
709        info: &ProcInfo,
710        inputs: &[&[f32]],
711        outputs: &mut [&mut [f32]],
712    ) {
713        assert!(inputs.len() == CHANNELS);
714        assert!(outputs.len() == CHANNELS);
715        for ch in inputs.iter() {
716            assert!(ch.len() >= info.frames);
717        }
718        for ch in outputs.iter() {
719            assert!(ch.len() >= info.frames);
720        }
721
722        for i in 0..info.frames {
723            let cutoff_hz = self.cutoff_hz.next_smoothed();
724            let q = self.q_factor.next_smoothed();
725
726            // Because recalculating filter coefficients is expensive, a trick like
727            // this can be used to only recalculate them every few frames.
728            //
729            // TODO: use core::hint::cold_path() once that stabilizes
730            //
731            // TODO: Alternatively, this could be optimized using a lookup table
732            if self.coeff_update_mask.do_update(i) {
733                let [coeff_0, coeff_1] =
734                    SvfCoeff::lowpass_ord4(cutoff_hz, q, info.sample_rate_recip as f32);
735                self.filter_0_coeff = SvfCoeffSimd::splat(coeff_0);
736                self.filter_1_coeff = SvfCoeffSimd::splat(coeff_1);
737            }
738
739            let s: [f32; CHANNELS] = core::array::from_fn(|ch_i| {
740                // Safety: These bounds have been checked above.
741                unsafe { *inputs.get_unchecked(ch_i).get_unchecked(i) }
742            });
743
744            let s = self.filter_0.process(s, &self.filter_0_coeff);
745            let out = self.filter_1.process(s, &self.filter_1_coeff);
746
747            for ch_i in 0..CHANNELS {
748                // Safety: These bounds have been checked above.
749                unsafe {
750                    *outputs.get_unchecked_mut(ch_i).get_unchecked_mut(i) = out[ch_i];
751                }
752            }
753        }
754    }
755
756    fn loop_highpass_ord2(
757        &mut self,
758        info: &ProcInfo,
759        inputs: &[&[f32]],
760        outputs: &mut [&mut [f32]],
761    ) {
762        assert!(inputs.len() == CHANNELS);
763        assert!(outputs.len() == CHANNELS);
764        for ch in inputs.iter() {
765            assert!(ch.len() >= info.frames);
766        }
767        for ch in outputs.iter() {
768            assert!(ch.len() >= info.frames);
769        }
770
771        for i in 0..info.frames {
772            let cutoff_hz = self.cutoff_hz.next_smoothed();
773            let q = self.q_factor.next_smoothed();
774
775            // Because recalculating filter coefficients is expensive, a trick like
776            // this can be used to only recalculate them every few frames.
777            //
778            // TODO: use core::hint::cold_path() once that stabilizes
779            //
780            // TODO: Alternatively, this could be optimized using a lookup table
781            if self.coeff_update_mask.do_update(i) {
782                self.filter_0_coeff = SvfCoeffSimd::splat(SvfCoeff::highpass_ord2(
783                    cutoff_hz,
784                    q,
785                    info.sample_rate_recip as f32,
786                ));
787            }
788
789            let s: [f32; CHANNELS] = core::array::from_fn(|ch_i| {
790                // Safety: These bounds have been checked above.
791                unsafe { *inputs.get_unchecked(ch_i).get_unchecked(i) }
792            });
793
794            let out = self.filter_0.process(s, &self.filter_0_coeff);
795
796            for ch_i in 0..CHANNELS {
797                // Safety: These bounds have been checked above.
798                unsafe {
799                    *outputs.get_unchecked_mut(ch_i).get_unchecked_mut(i) = out[ch_i];
800                }
801            }
802        }
803    }
804
805    fn loop_highpass_ord4(
806        &mut self,
807        info: &ProcInfo,
808        inputs: &[&[f32]],
809        outputs: &mut [&mut [f32]],
810    ) {
811        assert!(inputs.len() == CHANNELS);
812        assert!(outputs.len() == CHANNELS);
813        for ch in inputs.iter() {
814            assert!(ch.len() >= info.frames);
815        }
816        for ch in outputs.iter() {
817            assert!(ch.len() >= info.frames);
818        }
819
820        for i in 0..info.frames {
821            let cutoff_hz = self.cutoff_hz.next_smoothed();
822            let q = self.q_factor.next_smoothed();
823
824            // Because recalculating filter coefficients is expensive, a trick like
825            // this can be used to only recalculate them every few frames.
826            //
827            // TODO: use core::hint::cold_path() once that stabilizes
828            //
829            // TODO: Alternatively, this could be optimized using a lookup table
830            if self.coeff_update_mask.do_update(i) {
831                let [coeff_0, coeff_1] =
832                    SvfCoeff::highpass_ord4(cutoff_hz, q, info.sample_rate_recip as f32);
833                self.filter_0_coeff = SvfCoeffSimd::splat(coeff_0);
834                self.filter_1_coeff = SvfCoeffSimd::splat(coeff_1);
835            }
836
837            let s: [f32; CHANNELS] = core::array::from_fn(|ch_i| {
838                // Safety: These bounds have been checked above.
839                unsafe { *inputs.get_unchecked(ch_i).get_unchecked(i) }
840            });
841
842            let s = self.filter_0.process(s, &self.filter_0_coeff);
843            let out = self.filter_1.process(s, &self.filter_1_coeff);
844
845            for ch_i in 0..CHANNELS {
846                // Safety: These bounds have been checked above.
847                unsafe {
848                    *outputs.get_unchecked_mut(ch_i).get_unchecked_mut(i) = out[ch_i];
849                }
850            }
851        }
852    }
853
854    fn loop_bandpass(&mut self, info: &ProcInfo, inputs: &[&[f32]], outputs: &mut [&mut [f32]]) {
855        assert!(inputs.len() == CHANNELS);
856        assert!(outputs.len() == CHANNELS);
857        for ch in inputs.iter() {
858            assert!(ch.len() >= info.frames);
859        }
860        for ch in outputs.iter() {
861            assert!(ch.len() >= info.frames);
862        }
863
864        for i in 0..info.frames {
865            let cutoff_hz = self.cutoff_hz.next_smoothed();
866            let q = self.q_factor.next_smoothed();
867
868            // Because recalculating filter coefficients is expensive, a trick like
869            // this can be used to only recalculate them every few frames.
870            //
871            // TODO: use core::hint::cold_path() once that stabilizes
872            //
873            // TODO: Alternatively, this could be optimized using a lookup table
874            if self.coeff_update_mask.do_update(i) {
875                self.filter_0_coeff = SvfCoeffSimd::splat(SvfCoeff::lowpass_ord2(
876                    cutoff_hz,
877                    q,
878                    info.sample_rate_recip as f32,
879                ));
880                self.filter_1_coeff = SvfCoeffSimd::splat(SvfCoeff::highpass_ord2(
881                    cutoff_hz,
882                    q,
883                    info.sample_rate_recip as f32,
884                ));
885            }
886
887            let s: [f32; CHANNELS] = core::array::from_fn(|ch_i| {
888                // Safety: These bounds have been checked above.
889                unsafe { *inputs.get_unchecked(ch_i).get_unchecked(i) }
890            });
891
892            let s = self.filter_0.process(s, &self.filter_0_coeff);
893            let out = self.filter_1.process(s, &self.filter_1_coeff);
894
895            for ch_i in 0..CHANNELS {
896                // Safety: These bounds have been checked above.
897                unsafe {
898                    *outputs.get_unchecked_mut(ch_i).get_unchecked_mut(i) = out[ch_i];
899                }
900            }
901        }
902    }
903
904    fn loop_low_shelf(&mut self, info: &ProcInfo, inputs: &[&[f32]], outputs: &mut [&mut [f32]]) {
905        assert!(inputs.len() == CHANNELS);
906        assert!(outputs.len() == CHANNELS);
907        for ch in inputs.iter() {
908            assert!(ch.len() >= info.frames);
909        }
910        for ch in outputs.iter() {
911            assert!(ch.len() >= info.frames);
912        }
913
914        for i in 0..info.frames {
915            let cutoff_hz = self.cutoff_hz.next_smoothed();
916            let q = self.q_factor.next_smoothed();
917            let gain = self.gain.next_smoothed();
918
919            // Because recalculating filter coefficients is expensive, a trick like
920            // this can be used to only recalculate them every few frames.
921            //
922            // TODO: use core::hint::cold_path() once that stabilizes
923            //
924            // TODO: Alternatively, this could be optimized using a lookup table
925            if self.coeff_update_mask.do_update(i) {
926                self.filter_0_coeff = SvfCoeffSimd::splat(SvfCoeff::low_shelf(
927                    cutoff_hz,
928                    q,
929                    gain,
930                    info.sample_rate_recip as f32,
931                ));
932            }
933
934            let s: [f32; CHANNELS] = core::array::from_fn(|ch_i| {
935                // Safety: These bounds have been checked above.
936                unsafe { *inputs.get_unchecked(ch_i).get_unchecked(i) }
937            });
938
939            let out = self.filter_0.process(s, &self.filter_0_coeff);
940
941            for ch_i in 0..CHANNELS {
942                // Safety: These bounds have been checked above.
943                unsafe {
944                    *outputs.get_unchecked_mut(ch_i).get_unchecked_mut(i) = out[ch_i];
945                }
946            }
947        }
948    }
949
950    fn loop_high_shelf(&mut self, info: &ProcInfo, inputs: &[&[f32]], outputs: &mut [&mut [f32]]) {
951        assert!(inputs.len() == CHANNELS);
952        assert!(outputs.len() == CHANNELS);
953        for ch in inputs.iter() {
954            assert!(ch.len() >= info.frames);
955        }
956        for ch in outputs.iter() {
957            assert!(ch.len() >= info.frames);
958        }
959
960        for i in 0..info.frames {
961            let cutoff_hz = self.cutoff_hz.next_smoothed();
962            let q = self.q_factor.next_smoothed();
963            let gain = self.gain.next_smoothed();
964
965            // Because recalculating filter coefficients is expensive, a trick like
966            // this can be used to only recalculate them every few frames.
967            //
968            // TODO: use core::hint::cold_path() once that stabilizes
969            //
970            // TODO: Alternatively, this could be optimized using a lookup table
971            if self.coeff_update_mask.do_update(i) {
972                self.filter_0_coeff = SvfCoeffSimd::splat(SvfCoeff::high_shelf(
973                    cutoff_hz,
974                    q,
975                    gain,
976                    info.sample_rate_recip as f32,
977                ));
978            }
979
980            let s: [f32; CHANNELS] = core::array::from_fn(|ch_i| {
981                // Safety: These bounds have been checked above.
982                unsafe { *inputs.get_unchecked(ch_i).get_unchecked(i) }
983            });
984
985            let out = self.filter_0.process(s, &self.filter_0_coeff);
986
987            for ch_i in 0..CHANNELS {
988                // Safety: These bounds have been checked above.
989                unsafe {
990                    *outputs.get_unchecked_mut(ch_i).get_unchecked_mut(i) = out[ch_i];
991                }
992            }
993        }
994    }
995
996    fn loop_bell(&mut self, info: &ProcInfo, inputs: &[&[f32]], outputs: &mut [&mut [f32]]) {
997        assert!(inputs.len() == CHANNELS);
998        assert!(outputs.len() == CHANNELS);
999        for ch in inputs.iter() {
1000            assert!(ch.len() >= info.frames);
1001        }
1002        for ch in outputs.iter() {
1003            assert!(ch.len() >= info.frames);
1004        }
1005
1006        for i in 0..info.frames {
1007            let cutoff_hz = self.cutoff_hz.next_smoothed();
1008            let q = self.q_factor.next_smoothed();
1009            let gain = self.gain.next_smoothed();
1010
1011            // Because recalculating filter coefficients is expensive, a trick like
1012            // this can be used to only recalculate them every few frames.
1013            //
1014            // TODO: use core::hint::cold_path() once that stabilizes
1015            //
1016            // TODO: Alternatively, this could be optimized using a lookup table
1017            if self.coeff_update_mask.do_update(i) {
1018                self.filter_0_coeff = SvfCoeffSimd::splat(SvfCoeff::bell(
1019                    cutoff_hz,
1020                    q,
1021                    gain,
1022                    info.sample_rate_recip as f32,
1023                ));
1024            }
1025
1026            let s: [f32; CHANNELS] = core::array::from_fn(|ch_i| {
1027                // Safety: These bounds have been checked above.
1028                unsafe { *inputs.get_unchecked(ch_i).get_unchecked(i) }
1029            });
1030
1031            let out = self.filter_0.process(s, &self.filter_0_coeff);
1032
1033            for ch_i in 0..CHANNELS {
1034                // Safety: These bounds have been checked above.
1035                unsafe {
1036                    *outputs.get_unchecked_mut(ch_i).get_unchecked_mut(i) = out[ch_i];
1037                }
1038            }
1039        }
1040    }
1041
1042    fn loop_notch(&mut self, info: &ProcInfo, inputs: &[&[f32]], outputs: &mut [&mut [f32]]) {
1043        assert!(inputs.len() == CHANNELS);
1044        assert!(outputs.len() == CHANNELS);
1045        for ch in inputs.iter() {
1046            assert!(ch.len() >= info.frames);
1047        }
1048        for ch in outputs.iter() {
1049            assert!(ch.len() >= info.frames);
1050        }
1051
1052        for i in 0..info.frames {
1053            let cutoff_hz = self.cutoff_hz.next_smoothed();
1054            let q = self.q_factor.next_smoothed();
1055
1056            // Because recalculating filter coefficients is expensive, a trick like
1057            // this can be used to only recalculate them every few frames.
1058            //
1059            // TODO: use core::hint::cold_path() once that stabilizes
1060            //
1061            // TODO: Alternatively, this could be optimized using a lookup table
1062            if self.coeff_update_mask.do_update(i) {
1063                self.filter_0_coeff = SvfCoeffSimd::splat(SvfCoeff::notch(
1064                    cutoff_hz,
1065                    q,
1066                    info.sample_rate_recip as f32,
1067                ));
1068            }
1069
1070            let s: [f32; CHANNELS] = core::array::from_fn(|ch_i| {
1071                // Safety: These bounds have been checked above.
1072                unsafe { *inputs.get_unchecked(ch_i).get_unchecked(i) }
1073            });
1074
1075            let out = self.filter_0.process(s, &self.filter_0_coeff);
1076
1077            for ch_i in 0..CHANNELS {
1078                // Safety: These bounds have been checked above.
1079                unsafe {
1080                    *outputs.get_unchecked_mut(ch_i).get_unchecked_mut(i) = out[ch_i];
1081                }
1082            }
1083        }
1084    }
1085
1086    fn loop_allpass(&mut self, info: &ProcInfo, inputs: &[&[f32]], outputs: &mut [&mut [f32]]) {
1087        assert!(inputs.len() == CHANNELS);
1088        assert!(outputs.len() == CHANNELS);
1089        for ch in inputs.iter() {
1090            assert!(ch.len() >= info.frames);
1091        }
1092        for ch in outputs.iter() {
1093            assert!(ch.len() >= info.frames);
1094        }
1095
1096        for i in 0..info.frames {
1097            let cutoff_hz = self.cutoff_hz.next_smoothed();
1098            let q = self.q_factor.next_smoothed();
1099
1100            // Because recalculating filter coefficients is expensive, a trick like
1101            // this can be used to only recalculate them every few frames.
1102            //
1103            // TODO: use core::hint::cold_path() once that stabilizes
1104            //
1105            // TODO: Alternatively, this could be optimized using a lookup table
1106            if self.coeff_update_mask.do_update(i) {
1107                self.filter_0_coeff = SvfCoeffSimd::splat(SvfCoeff::allpass(
1108                    cutoff_hz,
1109                    q,
1110                    info.sample_rate_recip as f32,
1111                ));
1112            }
1113
1114            let s: [f32; CHANNELS] = core::array::from_fn(|ch_i| {
1115                // Safety: These bounds have been checked above.
1116                unsafe { *inputs.get_unchecked(ch_i).get_unchecked(i) }
1117            });
1118
1119            let out = self.filter_0.process(s, &self.filter_0_coeff);
1120
1121            for ch_i in 0..CHANNELS {
1122                // Safety: These bounds have been checked above.
1123                unsafe {
1124                    *outputs.get_unchecked_mut(ch_i).get_unchecked_mut(i) = out[ch_i];
1125                }
1126            }
1127        }
1128    }
1129}
1130
1131impl<const CHANNELS: usize> AudioNodeProcessor for Processor<CHANNELS> {
1132    fn process(
1133        &mut self,
1134        info: &ProcInfo,
1135        buffers: ProcBuffers,
1136        events: &mut ProcEvents,
1137        extra: &mut ProcExtra,
1138    ) -> ProcessStatus {
1139        let mut params_changed = false;
1140
1141        for patch in events.drain_patches::<SvfNode<CHANNELS>>() {
1142            match patch {
1143                SvfNodePatch::FilterType(filter_type) => {
1144                    params_changed = true;
1145                    self.filter_type = filter_type;
1146                }
1147                SvfNodePatch::CutoffHz(cutoff) => {
1148                    params_changed = true;
1149                    self.cutoff_hz
1150                        .set_value(cutoff.clamp(self.freq_range.start, self.freq_range.end));
1151                }
1152                SvfNodePatch::QFactor(q_factor) => {
1153                    params_changed = true;
1154                    self.q_factor
1155                        .set_value(q_factor.clamp(self.q_range.start, self.q_range.end));
1156                }
1157                SvfNodePatch::Gain(gain) => {
1158                    params_changed = true;
1159                    let mut gain = gain.amp().clamp(self.gain_range.start, self.gain_range.end);
1160                    if gain > 0.99999 && gain < 1.00001 {
1161                        gain = 1.0;
1162                    }
1163                    self.gain.set_value(gain);
1164                }
1165                SvfNodePatch::Enabled(enabled) => {
1166                    // Tell the declicker to crossfade.
1167                    self.enable_declicker
1168                        .fade_to_enabled(enabled, &extra.declick_values);
1169                }
1170                SvfNodePatch::SmoothSeconds(seconds) => {
1171                    self.cutoff_hz.set_smooth_seconds(seconds, info.sample_rate);
1172                }
1173                SvfNodePatch::CoeffUpdateFactor(f) => {
1174                    self.coeff_update_mask = f.mask();
1175                }
1176            }
1177        }
1178
1179        if self.enable_declicker.disabled() {
1180            // Disabled. Bypass this node.
1181            return ProcessStatus::Bypass;
1182        }
1183
1184        if info.in_silence_mask.all_channels_silent(CHANNELS) && self.enable_declicker.has_settled()
1185        {
1186            // Outputs will be silent, so no need to process.
1187
1188            // Reset the smoothers and filters since they don't need to smooth any
1189            // output.
1190            self.cutoff_hz.reset_to_target();
1191            self.filter_0.reset();
1192            self.filter_1.reset();
1193            self.enable_declicker.reset_to_target();
1194
1195            return ProcessStatus::ClearAllOutputs;
1196        }
1197
1198        if self.cutoff_hz.is_smoothing() || self.q_factor.is_smoothing() || self.gain.is_smoothing()
1199        {
1200            match self.filter_type {
1201                SvfType::Lowpass => self.loop_lowpass_ord2(info, buffers.inputs, buffers.outputs),
1202                SvfType::LowpassX2 => self.loop_lowpass_ord4(info, buffers.inputs, buffers.outputs),
1203                SvfType::Highpass => self.loop_highpass_ord2(info, buffers.inputs, buffers.outputs),
1204                SvfType::HighpassX2 => {
1205                    self.loop_highpass_ord4(info, buffers.inputs, buffers.outputs)
1206                }
1207                SvfType::Bandpass => self.loop_bandpass(info, buffers.inputs, buffers.outputs),
1208                SvfType::LowShelf => self.loop_low_shelf(info, buffers.inputs, buffers.outputs),
1209                SvfType::HighShelf => self.loop_high_shelf(info, buffers.inputs, buffers.outputs),
1210                SvfType::Bell => self.loop_bell(info, buffers.inputs, buffers.outputs),
1211                SvfType::Notch => self.loop_notch(info, buffers.inputs, buffers.outputs),
1212                SvfType::Allpass => self.loop_allpass(info, buffers.inputs, buffers.outputs),
1213            }
1214
1215            if self.cutoff_hz.settle() && self.q_factor.settle() && self.gain.settle() {
1216                self.calc_coefficients(info.sample_rate_recip as f32);
1217            }
1218        } else {
1219            // The cutoff parameter is not currently smoothing, so we can optimize by
1220            // only updating the filter coefficients once.
1221            if params_changed {
1222                self.calc_coefficients(info.sample_rate_recip as f32);
1223            }
1224
1225            assert!(buffers.inputs.len() == CHANNELS);
1226            assert!(buffers.outputs.len() == CHANNELS);
1227            for ch in buffers.inputs.iter() {
1228                assert!(ch.len() >= info.frames);
1229            }
1230            for ch in buffers.outputs.iter() {
1231                assert!(ch.len() >= info.frames);
1232            }
1233
1234            if self.num_filters == 1 {
1235                for i in 0..info.frames {
1236                    let s: [f32; CHANNELS] = core::array::from_fn(|ch_i| {
1237                        // Safety: These bounds have been checked above.
1238                        unsafe { *buffers.inputs.get_unchecked(ch_i).get_unchecked(i) }
1239                    });
1240
1241                    let out = self.filter_0.process(s, &self.filter_0_coeff);
1242
1243                    for ch_i in 0..CHANNELS {
1244                        // Safety: These bounds have been checked above.
1245                        unsafe {
1246                            *buffers.outputs.get_unchecked_mut(ch_i).get_unchecked_mut(i) =
1247                                out[ch_i];
1248                        }
1249                    }
1250                }
1251            } else {
1252                for i in 0..info.frames {
1253                    let s: [f32; CHANNELS] = core::array::from_fn(|ch_i| {
1254                        // Safety: These bounds have been checked above.
1255                        unsafe { *buffers.inputs.get_unchecked(ch_i).get_unchecked(i) }
1256                    });
1257
1258                    let s = self.filter_0.process(s, &self.filter_0_coeff);
1259                    let out = self.filter_1.process(s, &self.filter_1_coeff);
1260
1261                    for ch_i in 0..CHANNELS {
1262                        // Safety: These bounds have been checked above.
1263                        unsafe {
1264                            *buffers.outputs.get_unchecked_mut(ch_i).get_unchecked_mut(i) =
1265                                out[ch_i];
1266                        }
1267                    }
1268                }
1269            }
1270        }
1271
1272        // Crossfade between the wet and dry signals to declick enabling/disabling.
1273        self.enable_declicker.process_crossfade(
1274            buffers.inputs,
1275            buffers.outputs,
1276            info.frames,
1277            &extra.declick_values,
1278            DeclickFadeCurve::Linear,
1279        );
1280
1281        ProcessStatus::OutputsModified
1282    }
1283
1284    fn new_stream(&mut self, stream_info: &StreamInfo, _context: &mut ProcStreamCtx) {
1285        self.cutoff_hz.update_sample_rate(stream_info.sample_rate);
1286        self.q_factor.update_sample_rate(stream_info.sample_rate);
1287        self.gain.update_sample_rate(stream_info.sample_rate);
1288
1289        self.calc_coefficients(stream_info.sample_rate_recip as f32);
1290    }
1291}