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