From 381670f489cd6dfe718a050d7b0e6620b4408c3a Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Tue, 18 Sep 2018 18:05:04 +0530 Subject: [PATCH 1/8] biquad: BiquadFilterNode skeleton --- audio/src/biquad_filter_node.rs | 49 +++++++++++++++++++++++++++++++++ audio/src/lib.rs | 1 + audio/src/node.rs | 3 +- audio/src/render_thread.rs | 4 +++ 4 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 audio/src/biquad_filter_node.rs diff --git a/audio/src/biquad_filter_node.rs b/audio/src/biquad_filter_node.rs new file mode 100644 index 00000000..d65f5bfa --- /dev/null +++ b/audio/src/biquad_filter_node.rs @@ -0,0 +1,49 @@ +use block::Chunk; +use block::Tick; +use node::AudioNodeEngine; +use node::BlockInfo; +use node::{AudioNodeType, ChannelInfo}; +use param::{Param, ParamType}; + +#[derive(Copy, Clone, Debug)] +pub struct BiquadFilterNodeOptions { +} + +impl Default for BiquadFilterNodeOptions { + fn default() -> Self { + BiquadFilterNodeOptions { } + } +} + +#[derive(AudioNodeCommon)] +pub(crate) struct BiquadFilterNode { + channel_info: ChannelInfo, +} + +impl BiquadFilterNode { + pub fn new(options: BiquadFilterNodeOptions, channel_info: ChannelInfo) -> Self { + Self { + channel_info, + } + } + + pub fn update_parameters(&mut self, info: &BlockInfo, tick: Tick) -> bool { + true + } +} + +impl AudioNodeEngine for BiquadFilterNode { + fn node_type(&self) -> AudioNodeType { + AudioNodeType::BiquadFilterNode + } + + fn process(&mut self, mut inputs: Chunk, info: &BlockInfo) -> Chunk { + inputs + } + + fn get_param(&mut self, id: ParamType) -> &mut Param { + match id { + _ => panic!("Unknown param {:?} for BiquadFilterNode", id), + } + } +} diff --git a/audio/src/lib.rs b/audio/src/lib.rs index 20d2d2c0..a6f98134 100644 --- a/audio/src/lib.rs +++ b/audio/src/lib.rs @@ -14,6 +14,7 @@ extern crate smallvec; pub mod macros; pub mod analyser_node; +pub mod biquad_filter_node; pub mod block; pub mod buffer_source_node; pub mod channel_node; diff --git a/audio/src/node.rs b/audio/src/node.rs index 1b9b3908..ae96d8fe 100644 --- a/audio/src/node.rs +++ b/audio/src/node.rs @@ -1,3 +1,4 @@ +use biquad_filter_node::BiquadFilterNodeOptions; use boxfnonce::SendBoxFnOnce; use block::{Block, Chunk, Tick}; use buffer_source_node::{AudioBufferSourceNodeMessage, AudioBufferSourceNodeOptions}; @@ -11,7 +12,7 @@ use std::sync::mpsc::Sender; /// Information required to construct an audio node pub enum AudioNodeInit { AnalyserNode(Box), - BiquadFilterNode, + BiquadFilterNode(BiquadFilterNodeOptions), AudioBuffer, AudioBufferSourceNode(AudioBufferSourceNodeOptions), ChannelMergerNode(ChannelNodeOptions), diff --git a/audio/src/render_thread.rs b/audio/src/render_thread.rs index 357de814..16dfe84e 100644 --- a/audio/src/render_thread.rs +++ b/audio/src/render_thread.rs @@ -1,4 +1,5 @@ use analyser_node::AnalyserNode; +use biquad_filter_node::BiquadFilterNode; use block::{Chunk, Tick, FRAMES_PER_BLOCK}; use buffer_source_node::AudioBufferSourceNode; use channel_node::{ChannelMergerNode, ChannelSplitterNode}; @@ -134,6 +135,9 @@ impl AudioRenderThread { AudioNodeInit::AudioBufferSourceNode(options) => { Box::new(AudioBufferSourceNode::new(options, ch)) } + AudioNodeInit::BiquadFilterNode(options) => { + Box::new(BiquadFilterNode::new(options, ch)) + } AudioNodeInit::GainNode(options) => Box::new(GainNode::new(options, ch)), AudioNodeInit::PannerNode(options) => { needs_listener = true; From b3ee343ca03f445c3e5dde0ee33a2af25230638b Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Tue, 18 Sep 2018 18:52:43 +0530 Subject: [PATCH 2/8] biquad: Add params and messages --- audio/src/biquad_filter_node.rs | 64 +++++++++++++++++++++++++++++++-- audio/src/node.rs | 3 +- audio/src/param.rs | 1 + 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/audio/src/biquad_filter_node.rs b/audio/src/biquad_filter_node.rs index d65f5bfa..a8103f54 100644 --- a/audio/src/biquad_filter_node.rs +++ b/audio/src/biquad_filter_node.rs @@ -2,33 +2,75 @@ use block::Chunk; use block::Tick; use node::AudioNodeEngine; use node::BlockInfo; -use node::{AudioNodeType, ChannelInfo}; +use node::{AudioNodeMessage, AudioNodeType, ChannelInfo}; use param::{Param, ParamType}; #[derive(Copy, Clone, Debug)] pub struct BiquadFilterNodeOptions { + filter: FilterType, + frequency: f32, + detune: f32, + q: f32, + gain: f32, +} + +#[derive(Copy, Clone, Debug)] +pub enum FilterType { + LowPass, + HighPass, + BandPass, + LowShelf, + HighShelf, + Peaking, + Notch, + Allpass } impl Default for BiquadFilterNodeOptions { fn default() -> Self { - BiquadFilterNodeOptions { } + BiquadFilterNodeOptions { + filter: FilterType::LowPass, + frequency: 350., + detune: 0., + q: 1., + gain: 0., + } } } +#[derive(Copy, Clone, Debug)] +pub enum BiquadFilterNodeMessage { + SetFilterType(FilterType) +} + #[derive(AudioNodeCommon)] pub(crate) struct BiquadFilterNode { channel_info: ChannelInfo, + filter: FilterType, + frequency: Param, + detune: Param, + q: Param, + gain: Param, } impl BiquadFilterNode { pub fn new(options: BiquadFilterNodeOptions, channel_info: ChannelInfo) -> Self { Self { channel_info, + filter: options.filter, + frequency: Param::new(options.frequency), + gain: Param::new(options.gain), + q: Param::new(options.q), + detune: Param::new(options.detune), } } pub fn update_parameters(&mut self, info: &BlockInfo, tick: Tick) -> bool { - true + let mut changed = self.frequency.update(info, tick); + changed |= self.detune.update(info, tick); + changed |= self.q.update(info, tick); + changed |= self.gain.update(info, tick); + changed } } @@ -38,12 +80,28 @@ impl AudioNodeEngine for BiquadFilterNode { } fn process(&mut self, mut inputs: Chunk, info: &BlockInfo) -> Chunk { + // TODO inputs } fn get_param(&mut self, id: ParamType) -> &mut Param { match id { + ParamType::Frequency => &mut self.frequency, + ParamType::Detune => &mut self.detune, + ParamType::Q => &mut self.q, + ParamType::Gain => &mut self.gain, _ => panic!("Unknown param {:?} for BiquadFilterNode", id), } } + + fn message_specific(&mut self, message: AudioNodeMessage, _sample_rate: f32) { + match message { + AudioNodeMessage::BiquadFilterNode(m) => { + match m { + BiquadFilterNodeMessage::SetFilterType(f) => self.filter = f, + } + } + _ => () + } + } } diff --git a/audio/src/node.rs b/audio/src/node.rs index ae96d8fe..aa33d737 100644 --- a/audio/src/node.rs +++ b/audio/src/node.rs @@ -1,4 +1,4 @@ -use biquad_filter_node::BiquadFilterNodeOptions; +use biquad_filter_node::{BiquadFilterNodeMessage, BiquadFilterNodeOptions}; use boxfnonce::SendBoxFnOnce; use block::{Block, Chunk, Tick}; use buffer_source_node::{AudioBufferSourceNodeMessage, AudioBufferSourceNodeOptions}; @@ -179,6 +179,7 @@ pub(crate) trait AudioNodeEngine: Send + AudioNodeCommon { pub enum AudioNodeMessage { AudioBufferSourceNode(AudioBufferSourceNodeMessage), AudioScheduledSourceNode(AudioScheduledSourceNodeMessage), + BiquadFilterNode(BiquadFilterNodeMessage), PannerNode(PannerNodeMessage), GetParamValue(ParamType, Sender), SetChannelCount(u8), diff --git a/audio/src/param.rs b/audio/src/param.rs index 2e7af915..5806abba 100644 --- a/audio/src/param.rs +++ b/audio/src/param.rs @@ -8,6 +8,7 @@ pub enum ParamType { Frequency, Detune, Gain, + Q, PlaybackRate, Position(ParamDir), Forward(ParamDir), From ef6129b50ac5218d1b1d2fa931bff6307c51ee14 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Tue, 18 Sep 2018 20:59:36 +0530 Subject: [PATCH 3/8] biquad: Add parameter equations --- audio/src/biquad_filter_node.rs | 150 ++++++++++++++++++++++++++++++-- audio/src/render_thread.rs | 2 +- 2 files changed, 145 insertions(+), 7 deletions(-) diff --git a/audio/src/biquad_filter_node.rs b/audio/src/biquad_filter_node.rs index a8103f54..b51d0872 100644 --- a/audio/src/biquad_filter_node.rs +++ b/audio/src/biquad_filter_node.rs @@ -4,6 +4,7 @@ use node::AudioNodeEngine; use node::BlockInfo; use node::{AudioNodeMessage, AudioNodeType, ChannelInfo}; use param::{Param, ParamType}; +use std::f32::consts::{PI, SQRT_2}; #[derive(Copy, Clone, Debug)] pub struct BiquadFilterNodeOptions { @@ -23,7 +24,7 @@ pub enum FilterType { HighShelf, Peaking, Notch, - Allpass + AllPass } impl Default for BiquadFilterNodeOptions { @@ -43,6 +44,7 @@ pub enum BiquadFilterNodeMessage { SetFilterType(FilterType) } +/// https://webaudio.github.io/web-audio-api/#biquadfilternode #[derive(AudioNodeCommon)] pub(crate) struct BiquadFilterNode { channel_info: ChannelInfo, @@ -51,18 +53,43 @@ pub(crate) struct BiquadFilterNode { detune: Param, q: Param, gain: Param, + /// The computed filter parameter b0 + /// This is actually b0 / a0, we pre-divide + /// for efficiency + b0: f32, + /// The computed filter parameter b1 + /// This is actually b1 / a0, we pre-divide + /// for efficiency + b1: f32, + /// The computed filter parameter b2 + /// This is actually b2 / a0, we pre-divide + /// for efficiency + b2: f32, + /// The computed filter parameter a1 + /// This is actually a1 / a0, we pre-divide + /// for efficiency + a1: f32, + /// The computed filter parameter a2 + /// This is actually a2 / a0, we pre-divide + /// for efficiency + a2: f32, } impl BiquadFilterNode { - pub fn new(options: BiquadFilterNodeOptions, channel_info: ChannelInfo) -> Self { - Self { + pub fn new(options: BiquadFilterNodeOptions, + channel_info: ChannelInfo, + sample_rate: f32) -> Self { + let mut ret = Self { channel_info, filter: options.filter, frequency: Param::new(options.frequency), gain: Param::new(options.gain), q: Param::new(options.q), detune: Param::new(options.detune), - } + b0: 0., b1: 0., b2: 0., a1: 0., a2: 0., + }; + ret.update_coefficients(sample_rate); + ret } pub fn update_parameters(&mut self, info: &BlockInfo, tick: Tick) -> bool { @@ -70,8 +97,116 @@ impl BiquadFilterNode { changed |= self.detune.update(info, tick); changed |= self.q.update(info, tick); changed |= self.gain.update(info, tick); + + if changed { + self.update_coefficients(info.sample_rate); + } changed } + + /// Update the coefficients a1, a2, b0, b1, b2, given the sample_rate + /// + /// See https://webaudio.github.io/web-audio-api/#filters-characteristics + fn update_coefficients(&mut self, fs: f32) { + let g = self.gain.value(); + let q = self.q.value(); + let f0 = self.frequency.value() * (2.0_f32).powf(self.detune.value() / 1200.); + + // clamp to nominal range + // https://webaudio.github.io/web-audio-api/#biquadfilternode + let f0 = if f0 > fs / 2. || !f0.is_finite() { + fs / 2. + } else if f0 < 0. { + 0. + } else { + f0 + }; + + let a = 10.0_f32.powf(g / 40.); + let omega0 = 2. * PI * f0 / fs; + let sin_omega = omega0.sin(); + let cos_omega = omega0.cos(); + let alpha_q = sin_omega / (2. * q); + let alpha_q_db = sin_omega / (2. * 10.0_f32.powf(q / 20.)); + let alpha_s = sin_omega / SQRT_2; + + // we predivide by a0 + let a0; + + match self.filter { + FilterType::LowPass => { + self.b0 = (1. - cos_omega) / 2.; + self.b1 = 1. - cos_omega; + self.b2 = self.b1 / 2.; + a0 = 1. + alpha_q_db; + self.a1 = -2. * cos_omega; + self.a2 = 1. - alpha_q_db; + } + FilterType::HighPass => { + self.b0 = (1. + cos_omega) / 2.; + self.b1 = - (1. + cos_omega); + self.b2 = - self.b1 / 2.; + a0 = 1. + alpha_q_db; + self.a1 = -2. * cos_omega; + self.a2 = 1. - alpha_q_db; + } + FilterType::BandPass => { + self.b0 = alpha_q; + self.b1 = 0.; + self.b2 = -alpha_q; + a0 = 1. + alpha_q; + self.a1 = - 2. * cos_omega; + self.a2 = 1. - alpha_q; + } + FilterType::Notch => { + self.b0 = 1.; + self.b1 = -2. * cos_omega; + self.b2 = 1.; + a0 = 1. + alpha_q; + self.a1 = -2. * cos_omega; + self.a2 = 1. - alpha_q; + } + FilterType::AllPass => { + self.b0 = 1. - alpha_q; + self.b1 = -2. * cos_omega; + self.b2 = 1. + alpha_q; + a0 = 1. + alpha_q; + self.a1 = -2. * cos_omega; + self.a2 = 1. - alpha_q; + } + FilterType::Peaking => { + self.b0 = 1. + alpha_q * a; + self.b1 = -2. * cos_omega; + self.b2 = 1. - alpha_q * a; + a0 = 1. + alpha_q / a; + self.a1 = -2. * cos_omega; + self.a2 = 1. - alpha_q / a; + } + FilterType::LowShelf => { + let alpha_rt_a = 2. * alpha_s * a.sqrt(); + self.b0 = a * ((a + 1.) - (a - 1.) * cos_omega + alpha_rt_a); + self.b1 = 2. * a * ((a - 1.) - (a + 1.) * cos_omega); + self.b2 = a * ((a + 1.) - (a - 1.) * cos_omega - alpha_rt_a); + a0 = (a + 1.) + (a - 1.) * cos_omega + alpha_rt_a; + self.a1 = -2. * ((a - 1.) + (a + 1.) * cos_omega); + self.a2 = (a + 1.) + (a - 1.) * cos_omega - alpha_rt_a; + } + FilterType::HighShelf => { + let alpha_rt_a = 2. * alpha_s * a.sqrt(); + self.b0 = a * ((a + 1.) + (a - 1.) * cos_omega + alpha_rt_a); + self.b1 = -2. * a * ((a - 1.) + (a + 1.) * cos_omega); + self.b2 = a * ((a + 1.) + (a - 1.) * cos_omega - alpha_rt_a); + a0 = (a + 1.) - (a - 1.) * cos_omega + alpha_rt_a; + self.a1 = 2. * ((a - 1.) - (a + 1.) * cos_omega); + self.a2 = (a + 1.) - (a - 1.) * cos_omega - alpha_rt_a; + } + } + self.b0 = self.b0 / a0; + self.b1 = self.b1 / a0; + self.b2 = self.b2 / a0; + self.a1 = self.a1 / a0; + self.a2 = self.a2 / a0; + } } impl AudioNodeEngine for BiquadFilterNode { @@ -94,11 +229,14 @@ impl AudioNodeEngine for BiquadFilterNode { } } - fn message_specific(&mut self, message: AudioNodeMessage, _sample_rate: f32) { + fn message_specific(&mut self, message: AudioNodeMessage, sample_rate: f32) { match message { AudioNodeMessage::BiquadFilterNode(m) => { match m { - BiquadFilterNodeMessage::SetFilterType(f) => self.filter = f, + BiquadFilterNodeMessage::SetFilterType(f) => { + self.filter = f; + self.update_coefficients(sample_rate); + } } } _ => () diff --git a/audio/src/render_thread.rs b/audio/src/render_thread.rs index 16dfe84e..5e7a1b41 100644 --- a/audio/src/render_thread.rs +++ b/audio/src/render_thread.rs @@ -136,7 +136,7 @@ impl AudioRenderThread { Box::new(AudioBufferSourceNode::new(options, ch)) } AudioNodeInit::BiquadFilterNode(options) => { - Box::new(BiquadFilterNode::new(options, ch)) + Box::new(BiquadFilterNode::new(options, ch, self.sample_rate)) } AudioNodeInit::GainNode(options) => Box::new(GainNode::new(options, ch)), AudioNodeInit::PannerNode(options) => { From bc0a48296839b4d8f66e494e260f524f5dc845e3 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Tue, 18 Sep 2018 21:02:50 +0530 Subject: [PATCH 4/8] biquad: Add BiquadState --- audio/src/biquad_filter_node.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/audio/src/biquad_filter_node.rs b/audio/src/biquad_filter_node.rs index b51d0872..a645921f 100644 --- a/audio/src/biquad_filter_node.rs +++ b/audio/src/biquad_filter_node.rs @@ -4,6 +4,7 @@ use node::AudioNodeEngine; use node::BlockInfo; use node::{AudioNodeMessage, AudioNodeType, ChannelInfo}; use param::{Param, ParamType}; +use smallvec::SmallVec; use std::f32::consts::{PI, SQRT_2}; #[derive(Copy, Clone, Debug)] @@ -44,6 +45,30 @@ pub enum BiquadFilterNodeMessage { SetFilterType(FilterType) } +/// The last two input and output values, per-channel +// Default sets all fields to zero +#[derive(Default, Copy, Clone)] +struct BiquadState { + /// The input value from last frame + x1: f32, + /// The input value from two frames ago + x2: f32, + /// The output value from last frame + y1: f32, + /// The output value from two frames ago + y2: f32, +} + +impl BiquadState { + /// Update with new input/output values from this frame + fn update(&mut self, x: f32, y: f32) { + self.x2 = self.x1; + self.x1 = x; + self.y2 = self.y1; + self.y1 = y; + } +} + /// https://webaudio.github.io/web-audio-api/#biquadfilternode #[derive(AudioNodeCommon)] pub(crate) struct BiquadFilterNode { @@ -73,6 +98,10 @@ pub(crate) struct BiquadFilterNode { /// This is actually a2 / a0, we pre-divide /// for efficiency a2: f32, + /// Stored filter state, this contains the last two + /// frames of input and output values for every + /// channel + state: SmallVec<[BiquadState; 2]>, } impl BiquadFilterNode { @@ -87,6 +116,7 @@ impl BiquadFilterNode { q: Param::new(options.q), detune: Param::new(options.detune), b0: 0., b1: 0., b2: 0., a1: 0., a2: 0., + state: SmallVec::new(), }; ret.update_coefficients(sample_rate); ret From a0cb1e6fe84929cf6df444c86a416d1d2fa43df1 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Tue, 18 Sep 2018 22:05:31 +0530 Subject: [PATCH 5/8] biquad: Add Block::is_repeat(), pass down channel in FrameRef::mutate_with() --- audio/src/block.rs | 15 +++++++++++---- audio/src/gain_node.rs | 2 +- audio/src/oscillator_node.rs | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/audio/src/block.rs b/audio/src/block.rs index 6f41fd56..6a0adb3d 100644 --- a/audio/src/block.rs +++ b/audio/src/block.rs @@ -182,6 +182,10 @@ impl Block { self.buffer.is_empty() } + pub fn is_repeat(&self) -> bool { + self.repeat + } + pub fn data_chan_frame(&self, frame: usize, chan: u8) -> f32 { if self.is_silence() { 0. @@ -519,10 +523,12 @@ impl<'a> FrameRef<'a> { /// (Helpers for the other cases will eventually exist) /// /// Block must not be silence + /// + /// The second parameter to f is the channel number, 0 in case of a repeat() #[inline] - pub fn mutate_with(&mut self, f: F) + pub fn mutate_with(&mut self, mut f: F) where - F: Fn(&mut f32), + F: FnMut(&mut f32, u8), { debug_assert!( !self.block.is_silence(), @@ -530,11 +536,12 @@ impl<'a> FrameRef<'a> { call .explicit_silence() if you wish to use this" ); if self.block.repeat { - f(&mut self.block.buffer[self.frame.0 as usize]) + f(&mut self.block.buffer[self.frame.0 as usize], 0) } else { for chan in 0..self.block.channels { f(&mut self.block.buffer - [chan as usize * FRAMES_PER_BLOCK_USIZE + self.frame.0 as usize]) + [chan as usize * FRAMES_PER_BLOCK_USIZE + self.frame.0 as usize], + chan) } } } diff --git a/audio/src/gain_node.rs b/audio/src/gain_node.rs index 23eca4d6..a5697f77 100644 --- a/audio/src/gain_node.rs +++ b/audio/src/gain_node.rs @@ -55,7 +55,7 @@ impl AudioNodeEngine for GainNode { if self.update_parameters(info, frame.tick()) { gain = self.gain.value(); } - frame.mutate_with(|sample| *sample = *sample * gain); + frame.mutate_with(|sample, _| *sample = *sample * gain); } } inputs diff --git a/audio/src/oscillator_node.rs b/audio/src/oscillator_node.rs index afd08bd2..9249f754 100644 --- a/audio/src/oscillator_node.rs +++ b/audio/src/oscillator_node.rs @@ -118,7 +118,7 @@ impl AudioNodeEngine for OscillatorNode { step = two_pi * self.frequency.value() as f64 / sample_rate; } let value = vol * f32::sin(NumCast::from(self.phase).unwrap()); - frame.mutate_with(|sample| *sample = value); + frame.mutate_with(|sample, _| *sample = value); self.phase += step; if self.phase >= two_pi { From f140fbd54dc4765f0f1daacec51e59e38261d87b Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Tue, 18 Sep 2018 22:05:42 +0530 Subject: [PATCH 6/8] biquad: Implement the actual BiquadFilter --- audio/src/biquad_filter_node.rs | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/audio/src/biquad_filter_node.rs b/audio/src/biquad_filter_node.rs index a645921f..73711a0d 100644 --- a/audio/src/biquad_filter_node.rs +++ b/audio/src/biquad_filter_node.rs @@ -245,7 +245,36 @@ impl AudioNodeEngine for BiquadFilterNode { } fn process(&mut self, mut inputs: Chunk, info: &BlockInfo) -> Chunk { - // TODO + debug_assert!(inputs.len() == 1); + self.state.resize(inputs.blocks[0].chan_count() as usize, Default::default()); + self.update_parameters(info, info.frame); + + // XXXManishearth this node has tail time, so even if the block is silence + // we must still compute things on it. However, it is possible to become + // a dumb passthrough as long as we reach a quiescent state + // + // see https://dxr.mozilla.org/mozilla-central/rev/87a95e1b7ec691bef7b938e722fe1b01cce68664/dom/media/webaudio/blink/Biquad.cpp#81-91 + + // mutate_with can't deal with silent nodes, and will also incorrectly only use + // the first channel's state for the repeat block + // + // This can potentially be optimized to run explicit_silence() and only + // call explicit_repeat() if the states are not identical + inputs.blocks[0].explicit_repeat(); + { + let mut iter = inputs.blocks[0].iter(); + while let Some(mut frame) = iter.next() { + self.update_parameters(info, frame.tick()); + frame.mutate_with(|sample, chan| { + let state = &mut self.state[chan as usize]; + let x0 = *sample; + *sample = self.b0 * x0 + self.b1 * state.x1 + self.b2 * state.x2 + - self.a1 * state.y1 - self.a2 * state.y2; + state.update(x0, *sample); + }); + } + + } inputs } From 36c14ff86806863d32a9053e9e2d3240fafb57ce Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Tue, 18 Sep 2018 22:24:17 +0530 Subject: [PATCH 7/8] biquad: Optimize for repeat blocks --- audio/src/biquad_filter_node.rs | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/audio/src/biquad_filter_node.rs b/audio/src/biquad_filter_node.rs index 73711a0d..342d704a 100644 --- a/audio/src/biquad_filter_node.rs +++ b/audio/src/biquad_filter_node.rs @@ -47,7 +47,7 @@ pub enum BiquadFilterNodeMessage { /// The last two input and output values, per-channel // Default sets all fields to zero -#[derive(Default, Copy, Clone)] +#[derive(Default, Copy, Clone, PartialEq)] struct BiquadState { /// The input value from last frame x1: f32, @@ -255,12 +255,20 @@ impl AudioNodeEngine for BiquadFilterNode { // // see https://dxr.mozilla.org/mozilla-central/rev/87a95e1b7ec691bef7b938e722fe1b01cce68664/dom/media/webaudio/blink/Biquad.cpp#81-91 - // mutate_with can't deal with silent nodes, and will also incorrectly only use - // the first channel's state for the repeat block - // - // This can potentially be optimized to run explicit_silence() and only - // call explicit_repeat() if the states are not identical - inputs.blocks[0].explicit_repeat(); + let repeat_or_silence = inputs.blocks[0].is_silence() || inputs.blocks[0].is_repeat(); + + + if repeat_or_silence && !self.state.iter().all(|s| *s == self.state[0]) { + // In case our input is repeat/silence but our states are not identical, we must + // explicitly duplicate, since mutate_with will otherwise only operate + // on the first channel, ignoring the states of the later ones + inputs.blocks[0].explicit_repeat(); + } else { + // In case the states are identical, just make any silence explicit, + // since mutate_with can't handle silent blocks + inputs.blocks[0].explicit_silence(); + } + { let mut iter = inputs.blocks[0].iter(); while let Some(mut frame) = iter.next() { @@ -275,6 +283,12 @@ impl AudioNodeEngine for BiquadFilterNode { } } + + if inputs.blocks[0].is_repeat() { + let state = self.state[0]; + self.state.iter_mut().for_each(|s| *s = state); + } + inputs } From 1050d041080ffd5605bc8428a43639e7aa22ae2f Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Wed, 19 Sep 2018 14:03:29 +0530 Subject: [PATCH 8/8] Add test --- audio/src/biquad_filter_node.rs | 10 +++--- examples/Cargo.toml | 4 +++ examples/biquad.rs | 59 +++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 examples/biquad.rs diff --git a/audio/src/biquad_filter_node.rs b/audio/src/biquad_filter_node.rs index 342d704a..c5cd46ab 100644 --- a/audio/src/biquad_filter_node.rs +++ b/audio/src/biquad_filter_node.rs @@ -9,11 +9,11 @@ use std::f32::consts::{PI, SQRT_2}; #[derive(Copy, Clone, Debug)] pub struct BiquadFilterNodeOptions { - filter: FilterType, - frequency: f32, - detune: f32, - q: f32, - gain: f32, + pub filter: FilterType, + pub frequency: f32, + pub detune: f32, + pub q: f32, + pub gain: f32, } #[derive(Copy, Clone, Debug)] diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 70675560..3c6ccc7e 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -19,6 +19,10 @@ ipc-channel = "0.11" name = "audio_decoder" path = "audio_decoder.rs" +[[bin]] +name = "biquad" +path = "biquad.rs" + [[bin]] name = "channels" path = "channels.rs" diff --git a/examples/biquad.rs b/examples/biquad.rs new file mode 100644 index 00000000..9691818c --- /dev/null +++ b/examples/biquad.rs @@ -0,0 +1,59 @@ +extern crate servo_media; + +use servo_media::audio::biquad_filter_node::{BiquadFilterNodeMessage, BiquadFilterNodeOptions, FilterType}; +use servo_media::audio::oscillator_node::OscillatorNodeOptions; +use servo_media::audio::node::{AudioNodeInit, AudioNodeMessage, AudioScheduledSourceNodeMessage}; +use servo_media::audio::param::{ParamType, RampKind, UserAutomationEvent}; +use servo_media::ServoMedia; +use std::sync::Arc; +use std::{thread, time}; + +fn run_example(servo_media: Arc) { + let context = servo_media.create_audio_context(Default::default()); + let dest = context.dest_node(); + let mut options = OscillatorNodeOptions::default(); + options.freq = 100.; + let osc1 = context.create_node(AudioNodeInit::OscillatorNode(options), Default::default()); + options.freq = 800.; + let osc2 = context.create_node(AudioNodeInit::OscillatorNode(options), Default::default()); + let mut options = BiquadFilterNodeOptions::default(); + options.frequency = 50.; + options.filter = FilterType::LowPass; + let biquad = context.create_node(AudioNodeInit::BiquadFilterNode(options), Default::default()); + context.connect_ports(osc1.output(0), biquad.input(0)); + context.connect_ports(osc2.output(0), biquad.input(0)); + context.connect_ports(biquad.output(0), dest.input(0)); + let _ = context.resume(); + context.message_node( + osc1, + AudioNodeMessage::AudioScheduledSourceNode(AudioScheduledSourceNodeMessage::Start(0.)), + ); + context.message_node( + osc2, + AudioNodeMessage::AudioScheduledSourceNode(AudioScheduledSourceNodeMessage::Start(0.)), + ); + context.message_node( + biquad, + AudioNodeMessage::SetParam( + ParamType::Frequency, + UserAutomationEvent::RampToValueAtTime(RampKind::Linear, 1000., 2.), + ), + ); + + thread::sleep(time::Duration::from_millis(2200)); + context.message_node( + biquad, + AudioNodeMessage::BiquadFilterNode(BiquadFilterNodeMessage::SetFilterType(FilterType::BandPass)), + ); + + thread::sleep(time::Duration::from_millis(1000)); + +} + +fn main() { + if let Ok(servo_media) = ServoMedia::get() { + run_example(servo_media); + } else { + unreachable!(); + } +}