Skip to main content

firewheel_nodes/
svf.rs

1use core::ops::Range;
2
3use firewheel_core::node::NodeError;
4use firewheel_core::{
5    StreamInfo,
6    channel_config::{ChannelConfig, ChannelCount},
7    diff::{Diff, Patch},
8    dsp::{
9        coeff_update::{CoeffUpdateFactor, CoeffUpdateMask},
10        filter::{
11            butterworth::Q_BUTTERWORTH_ORD2,
12            smoothing_filter::DEFAULT_SMOOTH_SECONDS,
13            svf::{SvfCoeff, SvfCoeffSimd, SvfStateSimd},
14        },
15        volume::{Volume, db_to_amp},
16    },
17    event::ProcEvents,
18    node::{
19        AudioNode, AudioNodeInfo, AudioNodeProcessor, ConstructProcessorContext, ProcBuffers,
20        ProcExtra, ProcInfo, ProcStreamCtx, ProcessStatus,
21    },
22    param::smoother::{SmoothedParam, SmootherConfig},
23};
24
25pub const DEFAULT_Q: f32 = Q_BUTTERWORTH_ORD2;
26pub const DEFAULT_MIN_HZ: f32 = 20.0;
27pub const DEFAULT_MAX_HZ: f32 = 20_480.0;
28pub const DEFAULT_MIN_Q: f32 = 0.02;
29pub const DEFAULT_MAX_Q: f32 = 40.0;
30pub const DEFAULT_MIN_GAIN_DB: f32 = -24.0;
31pub const DEFAULT_MAX_GAIN_DB: f32 = 24.0;
32
33/// The configuration for an [`SvfNode`]
34#[derive(Debug, Clone, PartialEq)]
35#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
36#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
37#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
38pub struct SvfNodeConfig {
39    /// The minimum and maximum values for cutoff frequency in hertz.
40    ///
41    /// By default this is set to `20.0..20480.0`.
42    ///
43    /// It is generally not recommended to increase this range
44    /// unless you know what you are doing.
45    pub freq_range: Range<f32>,
46
47    /// The minimum and maximum values for q values.
48    ///
49    /// By default this is set to `0.02..40.0`.
50    ///
51    /// It is generally not recommended to increase this range
52    /// unless you know what you are doing.
53    pub q_range: Range<f32>,
54
55    /// The minimum and maximum values for filter gain (in decibels).
56    ///
57    /// By default this is set to `-24.0..24.0`.
58    ///
59    /// It is generally not recommended to increase this range
60    /// unless you know what you are doing.
61    pub gain_db_range: Range<f32>,
62}
63
64impl Default for SvfNodeConfig {
65    fn default() -> Self {
66        Self {
67            freq_range: DEFAULT_MIN_HZ..DEFAULT_MAX_HZ,
68            q_range: DEFAULT_MIN_Q..DEFAULT_MAX_Q,
69            gain_db_range: DEFAULT_MIN_GAIN_DB..DEFAULT_MAX_GAIN_DB,
70        }
71    }
72}
73
74/// The filter type to use for an [`SvfNode`]
75#[derive(Default, Diff, Patch, Debug, Clone, Copy, PartialEq, Eq)]
76#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
77#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
78pub enum SvfType {
79    // Lowpass (-12 dB per octave)
80    #[default]
81    Lowpass,
82    // Lowpass (-24 dB per octave)
83    LowpassX2,
84    // Lowpass (-12 dB per octave)
85    Highpass,
86    // Lowpass (-24 dB per octave)
87    HighpassX2,
88    // Bandpass (-12 dB per octave)
89    Bandpass,
90    LowShelf,
91    HighShelf,
92    Bell,
93    Notch,
94    Allpass,
95}
96
97pub type SvfMonoNode = SvfNode<1>;
98pub type SvfStereoNode = SvfNode<2>;
99
100/// An SVF (state variable filter) node
101///
102/// This is based on the filter model developed by Andrew Simper:
103/// <https://cytomic.com/files/dsp/SvfLinearTrapOptimised2.pdf>
104#[cfg_attr(feature = "bevy", derive(bevy_ecs::prelude::Component))]
105#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
106#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
107#[derive(Diff, Patch, Debug, Clone, Copy, PartialEq)]
108pub struct SvfNode<const CHANNELS: usize = 2> {
109    /// The type of filter
110    pub filter_type: SvfType,
111
112    /// The cutoff frequency in hertz in the range `[20.0, 20480.0]`.
113    pub cutoff_hz: f32,
114    /// The quality (q) factor
115    ///
116    /// This is also sometimes referred to as "bandwidth", but note the
117    /// formula to convert bandwidth in hertz to q is:
118    ///
119    /// `Q = cutoff_hz / BW`
120    ///
121    /// and the formula to convert bandwidth in octaves to q is:
122    ///
123    /// `Q = sqrt(2^BW) / (2^BW - 1)`
124    pub q_factor: f32,
125    /// The filter gain
126    ///
127    /// This only has effect if the filter type is one of the following:
128    /// * [`SvfType::LowShelf`]
129    /// * [`SvfType::HighShelf`]
130    /// * [`SvfType::Bell`]
131    pub gain: Volume,
132
133    /// The time in seconds of the internal smoothing filter.
134    ///
135    /// By default this is set to `0.062` (62ms). This value is chosen such that
136    /// the stair-stepping effect isn't noticeable for a typical block size of 1024
137    /// samples.
138    pub smooth_seconds: f32,
139
140    /// An exponent representing the rate at which DSP coefficients are
141    /// updated when parameters are being smoothed.
142    ///
143    /// Smaller values will produce less "stair-stepping" artifacts,
144    /// but will also consume more CPU.
145    ///
146    /// The resulting number of frames (samples in a single channel of audio)
147    /// that will elapse between each update is calculated as
148    /// `2^coeff_update_factor`.
149    ///
150    /// By default this is set to `4`.
151    pub coeff_update_factor: CoeffUpdateFactor,
152}
153
154impl<const CHANNELS: usize> Default for SvfNode<CHANNELS> {
155    fn default() -> Self {
156        Self {
157            filter_type: SvfType::Lowpass,
158            cutoff_hz: 1_000.0,
159            q_factor: DEFAULT_Q,
160            gain: Volume::Decibels(0.0),
161            smooth_seconds: DEFAULT_SMOOTH_SECONDS,
162            coeff_update_factor: CoeffUpdateFactor::default(),
163        }
164    }
165}
166
167impl<const CHANNELS: usize> SvfNode<CHANNELS> {
168    /// Construct a new SVF node with the lowpass filter type of order 2.
169    ///
170    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
171    /// * `q_factor` - The quality (q) factor
172    /// * `enabled` - Whether or not this node is enabled
173    pub const fn from_lowpass(cutoff_hz: f32, q_factor: f32) -> Self {
174        Self {
175            filter_type: SvfType::Lowpass,
176            cutoff_hz,
177            q_factor,
178            gain: Volume::UNITY_GAIN,
179            smooth_seconds: DEFAULT_SMOOTH_SECONDS,
180            coeff_update_factor: CoeffUpdateFactor(5),
181        }
182    }
183
184    /// Construct a new SVF node with the lowpass filter type of order 4.
185    ///
186    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
187    /// * `q_factor` - The quality (q) factor
188    /// * `enabled` - Whether or not this node is enabled
189    pub const fn from_lowpass_x2(cutoff_hz: f32, q_factor: f32) -> Self {
190        Self {
191            filter_type: SvfType::LowpassX2,
192            cutoff_hz,
193            q_factor,
194            gain: Volume::UNITY_GAIN,
195            smooth_seconds: DEFAULT_SMOOTH_SECONDS,
196            coeff_update_factor: CoeffUpdateFactor(5),
197        }
198    }
199
200    /// Construct a new SVF node with the highpass filter type of order 2.
201    ///
202    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
203    /// * `q_factor` - The quality (q) factor
204    /// * `enabled` - Whether or not this node is enabled
205    pub const fn from_highpass(cutoff_hz: f32, q_factor: f32) -> Self {
206        Self {
207            filter_type: SvfType::Highpass,
208            cutoff_hz,
209            q_factor,
210            gain: Volume::UNITY_GAIN,
211            smooth_seconds: DEFAULT_SMOOTH_SECONDS,
212            coeff_update_factor: CoeffUpdateFactor(5),
213        }
214    }
215
216    /// Construct a new SVF node with the highpass filter type of order 4.
217    ///
218    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
219    /// * `q_factor` - The quality (q) factor
220    /// * `enabled` - Whether or not this node is enabled
221    pub const fn from_highpass_x2(cutoff_hz: f32, q_factor: f32) -> Self {
222        Self {
223            filter_type: SvfType::HighpassX2,
224            cutoff_hz,
225            q_factor,
226            gain: Volume::UNITY_GAIN,
227            smooth_seconds: DEFAULT_SMOOTH_SECONDS,
228            coeff_update_factor: CoeffUpdateFactor(5),
229        }
230    }
231
232    /// Construct a new SVF node with the bandpass filter type.
233    ///
234    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
235    /// * `q_factor` - The quality (q) factor
236    /// * `enabled` - Whether or not this node is enabled
237    pub const fn from_bandpass(cutoff_hz: f32, q_factor: f32) -> Self {
238        Self {
239            filter_type: SvfType::Bandpass,
240            cutoff_hz,
241            q_factor,
242            gain: Volume::UNITY_GAIN,
243            smooth_seconds: DEFAULT_SMOOTH_SECONDS,
244            coeff_update_factor: CoeffUpdateFactor(5),
245        }
246    }
247
248    /// Construct a new SVF node with the lowshelf filter type.
249    ///
250    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
251    /// * `gain` - The filter gain
252    /// * `q_factor` - The quality (q) factor
253    /// * `enabled` - Whether or not this node is enabled
254    pub const fn from_lowshelf(cutoff_hz: f32, gain: Volume, q_factor: f32) -> Self {
255        Self {
256            filter_type: SvfType::LowShelf,
257            cutoff_hz,
258            q_factor,
259            gain,
260            smooth_seconds: DEFAULT_SMOOTH_SECONDS,
261            coeff_update_factor: CoeffUpdateFactor(5),
262        }
263    }
264
265    /// Construct a new SVF node with the highshelf filter type.
266    ///
267    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
268    /// * `gain` - The filter gain
269    /// * `q_factor` - The quality (q) factor
270    /// * `enabled` - Whether or not this node is enabled
271    pub const fn from_highshelf(cutoff_hz: f32, gain: Volume, q_factor: f32) -> Self {
272        Self {
273            filter_type: SvfType::HighShelf,
274            cutoff_hz,
275            q_factor,
276            gain,
277            smooth_seconds: DEFAULT_SMOOTH_SECONDS,
278            coeff_update_factor: CoeffUpdateFactor(5),
279        }
280    }
281
282    /// Construct a new SVF node with the bell filter type.
283    ///
284    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
285    /// * `gain` - The filter gain
286    /// * `q_factor` - The quality (q) factor
287    /// * `enabled` - Whether or not this node is enabled
288    pub const fn from_bell(cutoff_hz: f32, gain: Volume, q_factor: f32) -> Self {
289        Self {
290            filter_type: SvfType::Bell,
291            cutoff_hz,
292            q_factor,
293            gain,
294            smooth_seconds: DEFAULT_SMOOTH_SECONDS,
295            coeff_update_factor: CoeffUpdateFactor(5),
296        }
297    }
298
299    /// Construct a new SVF node with the notch filter type.
300    ///
301    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
302    /// * `q_factor` - The quality (q) factor
303    /// * `enabled` - Whether or not this node is enabled
304    pub const fn from_notch(cutoff_hz: f32, q_factor: f32) -> Self {
305        Self {
306            filter_type: SvfType::Notch,
307            cutoff_hz,
308            q_factor,
309            gain: Volume::UNITY_GAIN,
310            smooth_seconds: DEFAULT_SMOOTH_SECONDS,
311            coeff_update_factor: CoeffUpdateFactor(5),
312        }
313    }
314
315    /// Construct a new SVF node with the allpass filter type.
316    ///
317    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
318    /// * `q_factor` - The quality (q) factor
319    /// * `enabled` - Whether or not this node is enabled
320    pub const fn from_allpass(cutoff_hz: f32, q_factor: f32) -> Self {
321        Self {
322            filter_type: SvfType::Allpass,
323            cutoff_hz,
324            q_factor,
325            gain: Volume::UNITY_GAIN,
326            smooth_seconds: DEFAULT_SMOOTH_SECONDS,
327            coeff_update_factor: CoeffUpdateFactor(5),
328        }
329    }
330
331    /// Set the parameters to use a lowpass filter type of order 2.
332    ///
333    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
334    /// * `q_factor` - The quality (q) factor
335    pub const fn set_lowpass(&mut self, cutoff_hz: f32, q_factor: f32) {
336        self.filter_type = SvfType::Lowpass;
337        self.cutoff_hz = cutoff_hz;
338        self.q_factor = q_factor;
339    }
340
341    /// Set the parameters to use a lowpass filter type of order 4.
342    ///
343    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
344    /// * `q_factor` - The quality (q) factor
345    pub const fn set_lowpass_x2(&mut self, cutoff_hz: f32, q_factor: f32) {
346        self.filter_type = SvfType::LowpassX2;
347        self.cutoff_hz = cutoff_hz;
348        self.q_factor = q_factor;
349    }
350
351    /// Set the parameters to use a highpass filter type of order 2.
352    ///
353    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
354    /// * `q_factor` - The quality (q) factor
355    pub const fn set_highpass(&mut self, cutoff_hz: f32, q_factor: f32) {
356        self.filter_type = SvfType::Highpass;
357        self.cutoff_hz = cutoff_hz;
358        self.q_factor = q_factor;
359    }
360
361    /// Set the parameters to use a highpass filter type of order 4.
362    ///
363    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
364    /// * `q_factor` - The quality (q) factor
365    pub const fn set_highpass_x2(&mut self, cutoff_hz: f32, q_factor: f32) {
366        self.filter_type = SvfType::HighpassX2;
367        self.cutoff_hz = cutoff_hz;
368        self.q_factor = q_factor;
369    }
370
371    /// Set the parameters to use a bandpass filter type.
372    ///
373    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
374    /// * `q_factor` - The quality (q) factor
375    pub const fn set_bandpass(&mut self, cutoff_hz: f32, q_factor: f32) {
376        self.filter_type = SvfType::Bandpass;
377        self.cutoff_hz = cutoff_hz;
378        self.q_factor = q_factor;
379    }
380
381    /// Set the parameters to use a lowshelf filter type.
382    ///
383    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
384    /// * `gain` - The filter gain
385    /// * `q_factor` - The quality (q) factor
386    pub const fn set_lowshelf(&mut self, cutoff_hz: f32, gain: Volume, q_factor: f32) {
387        self.filter_type = SvfType::LowShelf;
388        self.cutoff_hz = cutoff_hz;
389        self.gain = gain;
390        self.q_factor = q_factor;
391    }
392
393    /// Set the parameters to use a highshelf filter type.
394    ///
395    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
396    /// * `gain` - The filter gain
397    /// * `q_factor` - The quality (q) factor
398    pub const fn set_highshelf(&mut self, cutoff_hz: f32, gain: Volume, q_factor: f32) {
399        self.filter_type = SvfType::HighShelf;
400        self.cutoff_hz = cutoff_hz;
401        self.gain = gain;
402        self.q_factor = q_factor;
403    }
404
405    /// Set the parameters to use a bell filter type.
406    ///
407    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
408    /// * `gain` - The filter gain
409    /// * `q_factor` - The quality (q) factor
410    pub const fn set_bell(&mut self, cutoff_hz: f32, gain: Volume, q_factor: f32) {
411        self.filter_type = SvfType::Bell;
412        self.cutoff_hz = cutoff_hz;
413        self.gain = gain;
414        self.q_factor = q_factor;
415    }
416
417    /// Set the parameters to use a notch filter type.
418    ///
419    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
420    /// * `q_factor` - The quality (q) factor
421    pub const fn set_notch(&mut self, cutoff_hz: f32, q_factor: f32) {
422        self.filter_type = SvfType::Notch;
423        self.cutoff_hz = cutoff_hz;
424        self.q_factor = q_factor;
425    }
426
427    /// Set the parameters to use an allpass filter type.
428    ///
429    /// * `cutoff_hz` - The cutoff frequency in hertz in the range `[20.0, 20480.0]`
430    /// * `q_factor` - The quality (q) factor
431    pub const fn set_allpass(&mut self, cutoff_hz: f32, q_factor: f32) {
432        self.filter_type = SvfType::Allpass;
433        self.cutoff_hz = cutoff_hz;
434        self.q_factor = q_factor;
435    }
436
437    /// Set the given filter gain in a linear scale, where `0.0` is silence and
438    /// `1.0` is unity gain.
439    ///
440    /// These units are suitable for volume sliders (simply convert percent
441    /// volume to linear volume by diving the percent volume by 100).
442    ///
443    /// This only has effect if the filter type is one of the following:
444    /// * [`SvfType::LowShelf`]
445    /// * [`SvfType::HighShelf`]
446    /// * [`SvfType::Bell`]
447    pub const fn set_gain_linear(&mut self, linear: f32) {
448        self.gain = Volume::Linear(linear);
449    }
450
451    /// Set the given filter gain in decibels, where `0.0` is unity gain and
452    /// `f32::NEG_INFINITY` is silence.
453    ///
454    /// This only has effect if the filter type is one of the following:
455    /// * [`SvfType::LowShelf`]
456    /// * [`SvfType::HighShelf`]
457    /// * [`SvfType::Bell`]
458    pub const fn set_gain_decibels(&mut self, decibels: f32) {
459        self.gain = Volume::Decibels(decibels);
460    }
461}
462
463impl<const CHANNELS: usize> AudioNode for SvfNode<CHANNELS> {
464    type Configuration = SvfNodeConfig;
465
466    fn info(&self, _config: &Self::Configuration) -> Result<AudioNodeInfo, NodeError> {
467        Ok(AudioNodeInfo::new()
468            .debug_name("svf")
469            .channel_config(ChannelConfig {
470                num_inputs: ChannelCount::new(CHANNELS as u32).unwrap(),
471                num_outputs: ChannelCount::new(CHANNELS as u32).unwrap(),
472            })
473            // Use SVF node as a test for in-place buffers, even though the
474            // schedulder does not natively support this yet, meaning it does
475            // not improve performance yet.
476            .in_place_buffers(true))
477    }
478
479    fn construct_processor(
480        &self,
481        config: &Self::Configuration,
482        cx: ConstructProcessorContext,
483    ) -> Result<impl AudioNodeProcessor, NodeError> {
484        let cutoff_hz = self
485            .cutoff_hz
486            .clamp(config.freq_range.start, config.freq_range.end);
487        let q_factor = self
488            .q_factor
489            .clamp(config.q_range.start, config.q_range.end);
490
491        let min_gain = db_to_amp(config.gain_db_range.start);
492        let max_gain = db_to_amp(config.gain_db_range.end);
493        let mut gain = self.gain.amp().clamp(min_gain, max_gain);
494        if gain > 0.99999 && gain < 1.00001 {
495            gain = 1.0;
496        }
497
498        let mut new_self = Processor {
499            filter_0: SvfStateSimd::<CHANNELS>::default(),
500            filter_1: SvfStateSimd::<CHANNELS>::default(),
501            num_filters: 0,
502            filter_0_coeff: SvfCoeffSimd::<CHANNELS>::default(),
503            filter_1_coeff: SvfCoeffSimd::<CHANNELS>::default(),
504            filter_type: self.filter_type,
505            cutoff_hz: SmoothedParam::new(
506                cutoff_hz,
507                config.freq_range.end - config.freq_range.start,
508                SmootherConfig {
509                    smooth_seconds: self.smooth_seconds,
510                    ..Default::default()
511                },
512                cx.stream_info.sample_rate,
513            ),
514            q_factor: SmoothedParam::new(
515                q_factor,
516                config.q_range.end - config.q_range.start,
517                SmootherConfig {
518                    smooth_seconds: self.smooth_seconds,
519                    ..Default::default()
520                },
521                cx.stream_info.sample_rate,
522            ),
523            gain: SmoothedParam::new(
524                gain,
525                max_gain - min_gain,
526                SmootherConfig {
527                    smooth_seconds: self.smooth_seconds,
528                    ..Default::default()
529                },
530                cx.stream_info.sample_rate,
531            ),
532            freq_range: config.freq_range.clone(),
533            q_range: config.q_range.clone(),
534            gain_range: min_gain..max_gain,
535            coeff_update_mask: self.coeff_update_factor.mask(),
536            params_changed: false,
537        };
538
539        new_self.update_coefficients(
540            new_self.cutoff_hz.target_value(),
541            new_self.q_factor.target_value(),
542            new_self.gain.target_value(),
543            cx.stream_info.sample_rate_recip as f32,
544        );
545
546        Ok(new_self)
547    }
548}
549
550struct Processor<const CHANNELS: usize> {
551    filter_0: SvfStateSimd<CHANNELS>,
552    filter_1: SvfStateSimd<CHANNELS>,
553    num_filters: usize,
554
555    filter_0_coeff: SvfCoeffSimd<CHANNELS>,
556    filter_1_coeff: SvfCoeffSimd<CHANNELS>,
557
558    filter_type: SvfType,
559    cutoff_hz: SmoothedParam,
560    q_factor: SmoothedParam,
561    gain: SmoothedParam,
562
563    freq_range: Range<f32>,
564    q_range: Range<f32>,
565    gain_range: Range<f32>,
566    coeff_update_mask: CoeffUpdateMask,
567    params_changed: bool,
568}
569
570impl<const CHANNELS: usize> Processor<CHANNELS> {
571    #[cold]
572    #[inline(never)]
573    fn update_coefficients(&mut self, cutoff_hz: f32, q: f32, gain: f32, sample_rate_recip: f32) {
574        match self.filter_type {
575            SvfType::Lowpass => {
576                self.num_filters = 1;
577
578                self.filter_0_coeff =
579                    SvfCoeffSimd::splat(SvfCoeff::lowpass_ord2(cutoff_hz, q, sample_rate_recip));
580            }
581            SvfType::LowpassX2 => {
582                self.num_filters = 2;
583
584                let [coeff_0, coeff_1] = SvfCoeff::lowpass_ord4(cutoff_hz, q, sample_rate_recip);
585                self.filter_0_coeff = SvfCoeffSimd::splat(coeff_0);
586                self.filter_1_coeff = SvfCoeffSimd::splat(coeff_1);
587            }
588            SvfType::Highpass => {
589                self.num_filters = 1;
590
591                self.filter_0_coeff =
592                    SvfCoeffSimd::splat(SvfCoeff::highpass_ord2(cutoff_hz, q, sample_rate_recip));
593            }
594            SvfType::HighpassX2 => {
595                self.num_filters = 2;
596
597                let [coeff_0, coeff_1] = SvfCoeff::highpass_ord4(cutoff_hz, q, sample_rate_recip);
598                self.filter_0_coeff = SvfCoeffSimd::splat(coeff_0);
599                self.filter_1_coeff = SvfCoeffSimd::splat(coeff_1);
600            }
601            SvfType::Bandpass => {
602                self.num_filters = 2;
603
604                self.filter_0_coeff =
605                    SvfCoeffSimd::splat(SvfCoeff::lowpass_ord2(cutoff_hz, q, sample_rate_recip));
606                self.filter_1_coeff =
607                    SvfCoeffSimd::splat(SvfCoeff::highpass_ord2(cutoff_hz, q, sample_rate_recip));
608            }
609            SvfType::LowShelf => {
610                self.num_filters = 1;
611
612                self.filter_0_coeff =
613                    SvfCoeffSimd::splat(SvfCoeff::low_shelf(cutoff_hz, q, gain, sample_rate_recip));
614            }
615            SvfType::HighShelf => {
616                self.num_filters = 1;
617
618                self.filter_0_coeff = SvfCoeffSimd::splat(SvfCoeff::high_shelf(
619                    cutoff_hz,
620                    q,
621                    gain,
622                    sample_rate_recip,
623                ));
624            }
625            SvfType::Bell => {
626                self.num_filters = 1;
627
628                self.filter_0_coeff =
629                    SvfCoeffSimd::splat(SvfCoeff::bell(cutoff_hz, q, gain, sample_rate_recip));
630            }
631            SvfType::Notch => {
632                self.num_filters = 1;
633
634                self.filter_0_coeff =
635                    SvfCoeffSimd::splat(SvfCoeff::notch(cutoff_hz, q, sample_rate_recip));
636            }
637            SvfType::Allpass => {
638                self.num_filters = 1;
639
640                self.filter_0_coeff =
641                    SvfCoeffSimd::splat(SvfCoeff::allpass(cutoff_hz, q, sample_rate_recip));
642            }
643        }
644
645        if self.num_filters == 1 {
646            self.filter_1.reset();
647        }
648    }
649
650    /// Smoothing loop for single-filter types that don't use gain
651    /// (Lowpass, Highpass, Notch, Allpass).
652    fn smoothing_loop_single(&mut self, info: &ProcInfo, outputs: &mut [&mut [f32]]) {
653        assert!(outputs.len() == CHANNELS);
654        for ch in outputs.iter() {
655            assert!(ch.len() >= info.frames);
656        }
657
658        for i in 0..info.frames {
659            let cutoff_hz = self.cutoff_hz.next_smoothed();
660            let q = self.q_factor.next_smoothed();
661
662            // Only recalculate coefficients every 2^coeff_update_factor frames
663            if self.coeff_update_mask.do_update(i) {
664                self.update_coefficients(cutoff_hz, q, 0.0, info.sample_rate_recip as f32);
665            }
666
667            let s: [f32; CHANNELS] = core::array::from_fn(|ch_i| {
668                // Safety: These bounds have been checked above.
669                unsafe { *outputs.get_unchecked(ch_i).get_unchecked(i) }
670            });
671
672            let out = self.filter_0.process(s, &self.filter_0_coeff);
673
674            for (ch_i, &o) in out.iter().enumerate().take(CHANNELS) {
675                // Safety: These bounds have been checked above.
676                unsafe {
677                    *outputs.get_unchecked_mut(ch_i).get_unchecked_mut(i) = o;
678                }
679            }
680        }
681    }
682
683    /// Smoothing loop for single-filter types that use gain
684    /// (LowShelf, HighShelf, Bell).
685    fn smoothing_loop_single_with_gain(&mut self, info: &ProcInfo, outputs: &mut [&mut [f32]]) {
686        assert!(outputs.len() == CHANNELS);
687        for ch in outputs.iter() {
688            assert!(ch.len() >= info.frames);
689        }
690
691        for i in 0..info.frames {
692            let cutoff_hz = self.cutoff_hz.next_smoothed();
693            let q = self.q_factor.next_smoothed();
694            let gain = self.gain.next_smoothed();
695
696            // Only recalculate coefficients every 2^coeff_update_factor frames
697            if self.coeff_update_mask.do_update(i) {
698                self.update_coefficients(cutoff_hz, q, gain, info.sample_rate_recip as f32);
699            }
700
701            let s: [f32; CHANNELS] = core::array::from_fn(|ch_i| {
702                // Safety: These bounds have been checked above.
703                unsafe { *outputs.get_unchecked(ch_i).get_unchecked(i) }
704            });
705
706            let out = self.filter_0.process(s, &self.filter_0_coeff);
707
708            for (ch_i, &o) in out.iter().enumerate().take(CHANNELS) {
709                // Safety: These bounds have been checked above.
710                unsafe {
711                    *outputs.get_unchecked_mut(ch_i).get_unchecked_mut(i) = o;
712                }
713            }
714        }
715    }
716
717    /// Smoothing loop for dual-filter types that don't use gain
718    /// (LowpassX2, HighpassX2, Bandpass).
719    fn smoothing_loop_dual(&mut self, info: &ProcInfo, outputs: &mut [&mut [f32]]) {
720        assert!(outputs.len() == CHANNELS);
721        for ch in outputs.iter() {
722            assert!(ch.len() >= info.frames);
723        }
724
725        for i in 0..info.frames {
726            let cutoff_hz = self.cutoff_hz.next_smoothed();
727            let q = self.q_factor.next_smoothed();
728
729            // Only recalculate coefficients every 2^coeff_update_factor frames
730            if self.coeff_update_mask.do_update(i) {
731                self.update_coefficients(cutoff_hz, q, 0.0, info.sample_rate_recip as f32);
732            }
733
734            let s: [f32; CHANNELS] = core::array::from_fn(|ch_i| {
735                // Safety: These bounds have been checked above.
736                unsafe { *outputs.get_unchecked(ch_i).get_unchecked(i) }
737            });
738
739            let s = self.filter_0.process(s, &self.filter_0_coeff);
740            let out = self.filter_1.process(s, &self.filter_1_coeff);
741
742            for (ch_i, &o) in out.iter().enumerate().take(CHANNELS) {
743                // Safety: These bounds have been checked above.
744                unsafe {
745                    *outputs.get_unchecked_mut(ch_i).get_unchecked_mut(i) = o;
746                }
747            }
748        }
749    }
750}
751
752impl<const CHANNELS: usize> Processor<CHANNELS> {
753    fn reset(&mut self) {
754        self.cutoff_hz.reset_to_target();
755        self.filter_0.reset();
756        self.filter_1.reset();
757    }
758}
759
760impl<const CHANNELS: usize> AudioNodeProcessor for Processor<CHANNELS> {
761    fn events(&mut self, info: &ProcInfo, events: &mut ProcEvents, _extra: &mut ProcExtra) {
762        for patch in events.drain_patches::<SvfNode<CHANNELS>>() {
763            match patch {
764                SvfNodePatch::FilterType(filter_type) => {
765                    self.params_changed = true;
766                    self.filter_type = filter_type;
767                }
768                SvfNodePatch::CutoffHz(cutoff) => {
769                    self.params_changed = true;
770                    self.cutoff_hz
771                        .set_value(cutoff.clamp(self.freq_range.start, self.freq_range.end));
772                }
773                SvfNodePatch::QFactor(q_factor) => {
774                    self.params_changed = true;
775                    self.q_factor
776                        .set_value(q_factor.clamp(self.q_range.start, self.q_range.end));
777                }
778                SvfNodePatch::Gain(gain) => {
779                    self.params_changed = true;
780                    let mut gain = gain.amp().clamp(self.gain_range.start, self.gain_range.end);
781                    if gain > 0.99999 && gain < 1.00001 {
782                        gain = 1.0;
783                    }
784                    self.gain.set_value(gain);
785                }
786                SvfNodePatch::SmoothSeconds(seconds) => {
787                    self.cutoff_hz.set_smooth_seconds(seconds, info.sample_rate);
788                }
789                SvfNodePatch::CoeffUpdateFactor(f) => {
790                    self.coeff_update_mask = f.mask();
791                }
792            }
793        }
794    }
795
796    fn bypassed(&mut self, _bypassed: bool) {
797        self.reset();
798    }
799
800    fn process(
801        &mut self,
802        info: &ProcInfo,
803        buffers: ProcBuffers,
804        _extra: &mut ProcExtra,
805    ) -> ProcessStatus {
806        // Make sure that in-place buffer processing is being handled correctly.
807        debug_assert_eq!(buffers.inputs.len(), 0);
808
809        if info.out_silence_mask.all_channels_silent(CHANNELS) {
810            // Outputs will be silent, so no need to process.
811
812            // Reset the smoothers and filters since they don't need to smooth any
813            // output.
814            self.reset();
815
816            return ProcessStatus::ClearAllOutputs;
817        }
818
819        if self.cutoff_hz.is_smoothing() || self.q_factor.is_smoothing() || self.gain.is_smoothing()
820        {
821            match self.filter_type {
822                SvfType::Lowpass | SvfType::Highpass | SvfType::Notch | SvfType::Allpass => {
823                    self.smoothing_loop_single(info, buffers.outputs)
824                }
825                SvfType::LowShelf | SvfType::HighShelf | SvfType::Bell => {
826                    self.smoothing_loop_single_with_gain(info, buffers.outputs)
827                }
828                SvfType::LowpassX2 | SvfType::HighpassX2 | SvfType::Bandpass => {
829                    self.smoothing_loop_dual(info, buffers.outputs)
830                }
831            }
832
833            if self.cutoff_hz.settle() && self.q_factor.settle() && self.gain.settle() {
834                self.update_coefficients(
835                    self.cutoff_hz.target_value(),
836                    self.q_factor.target_value(),
837                    self.gain.target_value(),
838                    info.sample_rate_recip as f32,
839                );
840            }
841        } else {
842            // The cutoff parameter is not currently smoothing, so we can optimize by
843            // only updating the filter coefficients once.
844            if self.params_changed {
845                self.params_changed = false;
846                self.update_coefficients(
847                    self.cutoff_hz.target_value(),
848                    self.q_factor.target_value(),
849                    self.gain.target_value(),
850                    info.sample_rate_recip as f32,
851                );
852            }
853
854            assert!(buffers.outputs.len() == CHANNELS);
855            for ch in buffers.outputs.iter() {
856                assert!(ch.len() >= info.frames);
857            }
858
859            if self.num_filters == 1 {
860                for i in 0..info.frames {
861                    let s: [f32; CHANNELS] = core::array::from_fn(|ch_i| {
862                        // Safety: These bounds have been checked above.
863                        unsafe { *buffers.outputs.get_unchecked(ch_i).get_unchecked(i) }
864                    });
865
866                    let out = self.filter_0.process(s, &self.filter_0_coeff);
867
868                    for (ch_i, &o) in out.iter().enumerate().take(CHANNELS) {
869                        // Safety: These bounds have been checked above.
870                        unsafe {
871                            *buffers.outputs.get_unchecked_mut(ch_i).get_unchecked_mut(i) = o;
872                        }
873                    }
874                }
875            } else {
876                for i in 0..info.frames {
877                    let s: [f32; CHANNELS] = core::array::from_fn(|ch_i| {
878                        // Safety: These bounds have been checked above.
879                        unsafe { *buffers.outputs.get_unchecked(ch_i).get_unchecked(i) }
880                    });
881
882                    let s = self.filter_0.process(s, &self.filter_0_coeff);
883                    let out = self.filter_1.process(s, &self.filter_1_coeff);
884
885                    for (ch_i, &o) in out.iter().enumerate().take(CHANNELS) {
886                        // Safety: These bounds have been checked above.
887                        unsafe {
888                            *buffers.outputs.get_unchecked_mut(ch_i).get_unchecked_mut(i) = o;
889                        }
890                    }
891                }
892            }
893        }
894
895        ProcessStatus::OutputsModified
896    }
897
898    fn new_stream(&mut self, stream_info: &StreamInfo, _context: &mut ProcStreamCtx) {
899        self.cutoff_hz.update_sample_rate(stream_info.sample_rate);
900        self.q_factor.update_sample_rate(stream_info.sample_rate);
901        self.gain.update_sample_rate(stream_info.sample_rate);
902
903        self.update_coefficients(
904            self.cutoff_hz.target_value(),
905            self.q_factor.target_value(),
906            self.gain.target_value(),
907            stream_info.sample_rate_recip as f32,
908        );
909    }
910}