servo_media_audio/
biquad_filter_node.rs

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