servo_media_audio/
biquad_filter_node.rs

1use crate::block::Chunk;
2use crate::block::Tick;
3use crate::node::AudioNodeEngine;
4use crate::node::BlockInfo;
5use crate::node::{AudioNodeMessage, AudioNodeType, ChannelInfo};
6use crate::param::{Param, ParamType};
7use smallvec::SmallVec;
8use std::f64::consts::{PI, SQRT_2};
9
10#[derive(Copy, Clone, Debug)]
11pub struct BiquadFilterNodeOptions {
12    pub filter: FilterType,
13    pub frequency: f32,
14    pub detune: f32,
15    pub q: f32,
16    pub gain: f32,
17}
18
19#[derive(Copy, Clone, Debug)]
20pub enum FilterType {
21    LowPass,
22    HighPass,
23    BandPass,
24    LowShelf,
25    HighShelf,
26    Peaking,
27    Notch,
28    AllPass,
29}
30
31impl Default for BiquadFilterNodeOptions {
32    fn default() -> Self {
33        BiquadFilterNodeOptions {
34            filter: FilterType::LowPass,
35            frequency: 350.,
36            detune: 0.,
37            q: 1.,
38            gain: 0.,
39        }
40    }
41}
42
43#[derive(Copy, Clone, Debug)]
44pub enum BiquadFilterNodeMessage {
45    SetFilterType(FilterType),
46}
47
48/// The last two input and output values, per-channel
49// Default sets all fields to zero
50#[derive(Default, Copy, Clone, PartialEq)]
51struct BiquadState {
52    /// The input value from last frame
53    x1: f64,
54    /// The input value from two frames ago
55    x2: f64,
56    /// The output value from last frame
57    y1: f64,
58    /// The output value from two frames ago
59    y2: f64,
60}
61
62impl BiquadState {
63    /// Update with new input/output values from this frame
64    fn update(&mut self, x: f64, y: f64) {
65        self.x2 = self.x1;
66        self.x1 = x;
67        self.y2 = self.y1;
68        self.y1 = y;
69    }
70}
71
72/// https://webaudio.github.io/web-audio-api/#biquadfilternode
73#[derive(AudioNodeCommon)]
74pub(crate) struct BiquadFilterNode {
75    channel_info: ChannelInfo,
76    filter: FilterType,
77    frequency: Param,
78    detune: Param,
79    q: Param,
80    gain: Param,
81    /// The computed filter parameter b0
82    /// This is actually b0 / a0, we pre-divide
83    /// for efficiency
84    b0: f64,
85    /// The computed filter parameter b1
86    /// This is actually b1 / a0, we pre-divide
87    /// for efficiency
88    b1: f64,
89    /// The computed filter parameter b2
90    /// This is actually b2 / a0, we pre-divide
91    /// for efficiency
92    b2: f64,
93    /// The computed filter parameter a1
94    /// This is actually a1 / a0, we pre-divide
95    /// for efficiency
96    a1: f64,
97    /// The computed filter parameter a2
98    /// This is actually a2 / a0, we pre-divide
99    /// for efficiency
100    a2: f64,
101    /// Stored filter state, this contains the last two
102    /// frames of input and output values for every
103    /// channel
104    state: SmallVec<[BiquadState; 2]>,
105}
106
107impl BiquadFilterNode {
108    pub fn new(
109        options: BiquadFilterNodeOptions,
110        channel_info: ChannelInfo,
111        sample_rate: f32,
112    ) -> Self {
113        let mut ret = Self {
114            channel_info,
115            filter: options.filter,
116            frequency: Param::new(options.frequency),
117            gain: Param::new(options.gain),
118            q: Param::new(options.q),
119            detune: Param::new(options.detune),
120            b0: 0.,
121            b1: 0.,
122            b2: 0.,
123            a1: 0.,
124            a2: 0.,
125            state: SmallVec::new(),
126        };
127        ret.update_coefficients(sample_rate);
128        ret
129    }
130
131    pub fn update_parameters(&mut self, info: &BlockInfo, tick: Tick) -> bool {
132        let mut changed = self.frequency.update(info, tick);
133        changed |= self.detune.update(info, tick);
134        changed |= self.q.update(info, tick);
135        changed |= self.gain.update(info, tick);
136
137        if changed {
138            self.update_coefficients(info.sample_rate);
139        }
140        changed
141    }
142
143    /// Set to the constant z-transform y[n] = b0 * x[n]
144    fn constant_z_transform(&mut self, b0: f64) {
145        self.b0 = b0;
146        self.b1 = 0.;
147        self.b2 = 0.;
148        self.a1 = 0.;
149        self.a2 = 0.;
150    }
151
152    /// Update the coefficients a1, a2, b0, b1, b2, given the sample_rate
153    ///
154    /// See https://webaudio.github.io/web-audio-api/#filters-characteristics
155    fn update_coefficients(&mut self, fs: f32) {
156        let g: f64 = self.gain.value().into();
157        let q: f64 = self.q.value().into();
158        let freq: f64 = self.frequency.value().into();
159        let f0: f64 = freq * (2.0_f64).powf(self.detune.value() as f64 / 1200.);
160        let fs: f64 = fs.into();
161        // clamp to nominal range
162        // https://webaudio.github.io/web-audio-api/#biquadfilternode
163        let f0 = if f0 > fs / 2. || !f0.is_finite() {
164            fs / 2.
165        } else if f0 < 0. {
166            0.
167        } else {
168            f0
169        };
170
171        let normalized = f0 / fs;
172        let a = 10.0_f64.powf(g / 40.);
173
174        // the boundary values sometimes need limits to
175        // be taken
176        match self.filter {
177            FilterType::LowPass => {
178                if normalized == 1. {
179                    self.constant_z_transform(1.);
180                    return;
181                } else if normalized == 0. {
182                    self.constant_z_transform(0.);
183                    return;
184                }
185            }
186            FilterType::HighPass => {
187                if normalized == 1. {
188                    self.constant_z_transform(0.);
189                    return;
190                } else if normalized == 0. {
191                    self.constant_z_transform(1.);
192                    return;
193                }
194            }
195            FilterType::LowShelf => {
196                if normalized == 1. {
197                    self.constant_z_transform(a * a);
198                    return;
199                } else if normalized == 0. {
200                    self.constant_z_transform(1.);
201                    return;
202                }
203            }
204            FilterType::HighShelf => {
205                if normalized == 1. {
206                    self.constant_z_transform(1.);
207                    return;
208                } else if normalized == 0. {
209                    self.constant_z_transform(a * a);
210                    return;
211                }
212            }
213            FilterType::Peaking => {
214                if normalized == 0. || normalized == 1. {
215                    self.constant_z_transform(1.);
216                    return;
217                } else if q <= 0. {
218                    self.constant_z_transform(a * a);
219                    return;
220                }
221            }
222            FilterType::AllPass => {
223                if normalized == 0. || normalized == 1. {
224                    self.constant_z_transform(1.);
225                    return;
226                } else if q <= 0. {
227                    self.constant_z_transform(-1.);
228                    return;
229                }
230            }
231            FilterType::Notch => {
232                if normalized == 0. || normalized == 1. {
233                    self.constant_z_transform(1.);
234                    return;
235                } else if q <= 0. {
236                    self.constant_z_transform(0.);
237                    return;
238                }
239            }
240            FilterType::BandPass => {
241                if normalized == 0. || normalized == 1. {
242                    self.constant_z_transform(0.);
243                    return;
244                } else if q <= 0. {
245                    self.constant_z_transform(1.);
246                    return;
247                }
248            }
249        }
250
251        let omega0 = 2. * PI * normalized;
252        let sin_omega = omega0.sin();
253        let cos_omega = omega0.cos();
254        let alpha_q = sin_omega / (2. * q);
255        let alpha_q_db = sin_omega / (2. * 10.0_f64.powf(q / 20.));
256        let alpha_s = sin_omega / SQRT_2;
257
258        // we predivide by a0
259        let a0;
260
261        match self.filter {
262            FilterType::LowPass => {
263                self.b0 = (1. - cos_omega) / 2.;
264                self.b1 = 1. - cos_omega;
265                self.b2 = self.b1 / 2.;
266                a0 = 1. + alpha_q_db;
267                self.a1 = -2. * cos_omega;
268                self.a2 = 1. - alpha_q_db;
269            }
270            FilterType::HighPass => {
271                self.b0 = (1. + cos_omega) / 2.;
272                self.b1 = -(1. + cos_omega);
273                self.b2 = -self.b1 / 2.;
274                a0 = 1. + alpha_q_db;
275                self.a1 = -2. * cos_omega;
276                self.a2 = 1. - alpha_q_db;
277            }
278            FilterType::BandPass => {
279                self.b0 = alpha_q;
280                self.b1 = 0.;
281                self.b2 = -alpha_q;
282                a0 = 1. + alpha_q;
283                self.a1 = -2. * cos_omega;
284                self.a2 = 1. - alpha_q;
285            }
286            FilterType::Notch => {
287                self.b0 = 1.;
288                self.b1 = -2. * cos_omega;
289                self.b2 = 1.;
290                a0 = 1. + alpha_q;
291                self.a1 = -2. * cos_omega;
292                self.a2 = 1. - alpha_q;
293            }
294            FilterType::AllPass => {
295                self.b0 = 1. - alpha_q;
296                self.b1 = -2. * cos_omega;
297                self.b2 = 1. + alpha_q;
298                a0 = 1. + alpha_q;
299                self.a1 = -2. * cos_omega;
300                self.a2 = 1. - alpha_q;
301            }
302            FilterType::Peaking => {
303                self.b0 = 1. + alpha_q * a;
304                self.b1 = -2. * cos_omega;
305                self.b2 = 1. - alpha_q * a;
306                a0 = 1. + alpha_q / a;
307                self.a1 = -2. * cos_omega;
308                self.a2 = 1. - alpha_q / a;
309            }
310            FilterType::LowShelf => {
311                let alpha_rt_a = 2. * alpha_s * a.sqrt();
312                self.b0 = a * ((a + 1.) - (a - 1.) * cos_omega + alpha_rt_a);
313                self.b1 = 2. * a * ((a - 1.) - (a + 1.) * cos_omega);
314                self.b2 = a * ((a + 1.) - (a - 1.) * cos_omega - alpha_rt_a);
315                a0 = (a + 1.) + (a - 1.) * cos_omega + alpha_rt_a;
316                self.a1 = -2. * ((a - 1.) + (a + 1.) * cos_omega);
317                self.a2 = (a + 1.) + (a - 1.) * cos_omega - alpha_rt_a;
318            }
319            FilterType::HighShelf => {
320                let alpha_rt_a = 2. * alpha_s * a.sqrt();
321                self.b0 = a * ((a + 1.) + (a - 1.) * cos_omega + alpha_rt_a);
322                self.b1 = -2. * a * ((a - 1.) + (a + 1.) * cos_omega);
323                self.b2 = a * ((a + 1.) + (a - 1.) * cos_omega - alpha_rt_a);
324                a0 = (a + 1.) - (a - 1.) * cos_omega + alpha_rt_a;
325                self.a1 = 2. * ((a - 1.) - (a + 1.) * cos_omega);
326                self.a2 = (a + 1.) - (a - 1.) * cos_omega - alpha_rt_a;
327            }
328        }
329        self.b0 = self.b0 / a0;
330        self.b1 = self.b1 / a0;
331        self.b2 = self.b2 / a0;
332        self.a1 = self.a1 / a0;
333        self.a2 = self.a2 / a0;
334    }
335}
336
337impl AudioNodeEngine for BiquadFilterNode {
338    fn node_type(&self) -> AudioNodeType {
339        AudioNodeType::BiquadFilterNode
340    }
341
342    fn process(&mut self, mut inputs: Chunk, info: &BlockInfo) -> Chunk {
343        debug_assert!(inputs.len() == 1);
344        self.state
345            .resize(inputs.blocks[0].chan_count() as usize, Default::default());
346        self.update_parameters(info, Tick(0));
347
348        // XXXManishearth this node has tail time, so even if the block is silence
349        // we must still compute things on it. However, it is possible to become
350        // a dumb passthrough as long as we reach a quiescent state
351        //
352        // see https://dxr.mozilla.org/mozilla-central/rev/87a95e1b7ec691bef7b938e722fe1b01cce68664/dom/media/webaudio/blink/Biquad.cpp#81-91
353
354        let repeat_or_silence = inputs.blocks[0].is_silence() || inputs.blocks[0].is_repeat();
355
356        if repeat_or_silence && !self.state.iter().all(|s| *s == self.state[0]) {
357            // In case our input is repeat/silence but our states are not identical, we must
358            // explicitly duplicate, since mutate_with will otherwise only operate
359            // on the first channel, ignoring the states of the later ones
360            inputs.blocks[0].explicit_repeat();
361        } else {
362            // In case the states are identical, just make any silence explicit,
363            // since mutate_with can't handle silent blocks
364            inputs.blocks[0].explicit_silence();
365        }
366
367        {
368            let mut iter = inputs.blocks[0].iter();
369            while let Some(mut frame) = iter.next() {
370                self.update_parameters(info, frame.tick());
371                frame.mutate_with(|sample, chan| {
372                    let state = &mut self.state[chan as usize];
373                    let x0 = *sample as f64;
374                    let y0 = self.b0 * x0 + self.b1 * state.x1 + self.b2 * state.x2
375                        - self.a1 * state.y1
376                        - self.a2 * state.y2;
377                    *sample = y0 as f32;
378                    state.update(x0, y0);
379                });
380            }
381        }
382
383        if inputs.blocks[0].is_repeat() {
384            let state = self.state[0];
385            self.state.iter_mut().for_each(|s| *s = state);
386        }
387
388        inputs
389    }
390
391    fn get_param(&mut self, id: ParamType) -> &mut Param {
392        match id {
393            ParamType::Frequency => &mut self.frequency,
394            ParamType::Detune => &mut self.detune,
395            ParamType::Q => &mut self.q,
396            ParamType::Gain => &mut self.gain,
397            _ => panic!("Unknown param {:?} for BiquadFilterNode", id),
398        }
399    }
400
401    fn message_specific(&mut self, message: AudioNodeMessage, sample_rate: f32) {
402        match message {
403            AudioNodeMessage::BiquadFilterNode(m) => match m {
404                BiquadFilterNodeMessage::SetFilterType(f) => {
405                    self.filter = f;
406                    self.update_coefficients(sample_rate);
407                }
408            },
409            _ => (),
410        }
411    }
412}