webaudio: Implement IIRFilterNode (#33001)

* Basic IIRFIlterNode bindings

Signed-off-by: Daniel Adams <msub2official@gmail.com>

* Add constructor to BaseAudioContext

Signed-off-by: Daniel Adams <msub2official@gmail.com>

* Update IDL and use statements

Signed-off-by: Daniel Adams <msub2official@gmail.com>

* Update non-crashing test expectations

Signed-off-by: Daniel Adams <msub2official@gmail.com>

* Tidy

Signed-off-by: Daniel Adams <msub2official@gmail.com>

* Add missing spec link

Signed-off-by: Daniel Adams <msub2official@gmail.com>

* Optimize error checks

Signed-off-by: Daniel Adams <msub2official@gmail.com>

* Pass context channel count to servo-media

Signed-off-by: Daniel Adams <msub2official@gmail.com>

* Update test expectations

Signed-off-by: Daniel Adams <msub2official@gmail.com>

* Update legacy expectations

Signed-off-by: Daniel Adams <msub2official@gmail.com>

* Add IIRFilterNode in interfaces.html

Signed-off-by: Daniel Adams <msub2official@gmail.com>

* Update MANIFEST

Signed-off-by: Daniel Adams <msub2official@gmail.com>

---------

Signed-off-by: Daniel Adams <msub2official@gmail.com>
This commit is contained in:
Daniel Adams 2024-08-11 14:27:54 -10:00 committed by GitHub
parent 1af3ad8a74
commit 5520a9eb50
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 206 additions and 175 deletions

View file

@ -58,6 +58,7 @@ impl AudioNode {
count: options.count as u8,
mode: options.mode.into(),
interpretation: options.interpretation.into(),
context_channel_count: context.channel_count() as u8,
};
let node_id = context
.audio_context_impl()

View file

@ -42,6 +42,7 @@ use crate::dom::bindings::codegen::Bindings::ChannelMergerNodeBinding::ChannelMe
use crate::dom::bindings::codegen::Bindings::ChannelSplitterNodeBinding::ChannelSplitterOptions;
use crate::dom::bindings::codegen::Bindings::ConstantSourceNodeBinding::ConstantSourceOptions;
use crate::dom::bindings::codegen::Bindings::GainNodeBinding::GainOptions;
use crate::dom::bindings::codegen::Bindings::IIRFilterNodeBinding::IIRFilterOptions;
use crate::dom::bindings::codegen::Bindings::OscillatorNodeBinding::OscillatorOptions;
use crate::dom::bindings::codegen::Bindings::PannerNodeBinding::PannerOptions;
use crate::dom::bindings::codegen::Bindings::StereoPannerNodeBinding::StereoPannerOptions;
@ -58,6 +59,7 @@ use crate::dom::constantsourcenode::ConstantSourceNode;
use crate::dom::domexception::{DOMErrorName, DOMException};
use crate::dom::eventtarget::EventTarget;
use crate::dom::gainnode::GainNode;
use crate::dom::iirfilternode::IIRFilterNode;
use crate::dom::oscillatornode::OscillatorNode;
use crate::dom::pannernode::PannerNode;
use crate::dom::promise::Promise;
@ -264,6 +266,10 @@ impl BaseAudioContext {
},
}
}
pub fn channel_count(&self) -> u32 {
self.channel_count
}
}
impl BaseAudioContextMethods for BaseAudioContext {
@ -550,6 +556,20 @@ impl BaseAudioContextMethods for BaseAudioContext {
// Step 4.
promise
}
/// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createiirfilter>
fn CreateIIRFilter(
&self,
feedforward: Vec<Finite<f64>>,
feedback: Vec<Finite<f64>>,
) -> Fallible<DomRoot<IIRFilterNode>> {
let opts = IIRFilterOptions {
parent: AudioNodeOptions::empty(),
feedback,
feedforward,
};
IIRFilterNode::new(self.global().as_window(), self, &opts)
}
}
impl From<BaseAudioContextOptions> for AudioContextOptions {

View file

@ -0,0 +1,145 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::sync::Arc;
use dom_struct::dom_struct;
use itertools::Itertools;
use js::gc::CustomAutoRooterGuard;
use js::rust::HandleObject;
use js::typedarray::Float32Array;
use servo_media::audio::iir_filter_node::{IIRFilterNode as IIRFilter, IIRFilterNodeOptions};
use servo_media::audio::node::AudioNodeInit;
use crate::dom::audionode::AudioNode;
use crate::dom::baseaudiocontext::BaseAudioContext;
use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::{
ChannelCountMode, ChannelInterpretation,
};
use crate::dom::bindings::codegen::Bindings::IIRFilterNodeBinding::{
IIRFilterNodeMethods, IIRFilterOptions,
};
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::num::Finite;
use crate::dom::bindings::reflector::reflect_dom_object_with_proto;
use crate::dom::bindings::root::DomRoot;
use crate::dom::window::Window;
#[dom_struct]
pub struct IIRFilterNode {
node: AudioNode,
feedforward: Vec<Finite<f64>>,
feedback: Vec<Finite<f64>>,
}
impl IIRFilterNode {
#[allow(crown::unrooted_must_root)]
pub fn new_inherited(
window: &Window,
context: &BaseAudioContext,
options: &IIRFilterOptions,
) -> Fallible<IIRFilterNode> {
if !(1..=20).contains(&options.feedforward.len()) ||
!(1..=20).contains(&options.feedback.len())
{
return Err(Error::NotSupported);
}
if options.feedforward.iter().all(|v| **v == 0.0) || *options.feedback[0] == 0.0 {
return Err(Error::InvalidState);
}
let node_options =
options
.parent
.unwrap_or(2, ChannelCountMode::Max, ChannelInterpretation::Speakers);
let init_options = options.into();
let node = AudioNode::new_inherited(
AudioNodeInit::IIRFilterNode(init_options),
context,
node_options,
1, // inputs
1, // outputs
)?;
Ok(IIRFilterNode {
node,
feedforward: (*options.feedforward).to_vec(),
feedback: (*options.feedback).to_vec(),
})
}
pub fn new(
window: &Window,
context: &BaseAudioContext,
options: &IIRFilterOptions,
) -> Fallible<DomRoot<IIRFilterNode>> {
Self::new_with_proto(window, None, context, options)
}
#[allow(crown::unrooted_must_root)]
fn new_with_proto(
window: &Window,
proto: Option<HandleObject>,
context: &BaseAudioContext,
options: &IIRFilterOptions,
) -> Fallible<DomRoot<IIRFilterNode>> {
let node = IIRFilterNode::new_inherited(window, context, options)?;
Ok(reflect_dom_object_with_proto(Box::new(node), window, proto))
}
#[allow(non_snake_case)]
pub fn Constructor(
window: &Window,
proto: Option<HandleObject>,
context: &BaseAudioContext,
options: &IIRFilterOptions,
) -> Fallible<DomRoot<IIRFilterNode>> {
IIRFilterNode::new_with_proto(window, proto, context, options)
}
}
impl IIRFilterNodeMethods for IIRFilterNode {
#[allow(unsafe_code)]
/// <https://webaudio.github.io/web-audio-api/#dom-iirfilternode-getfrequencyresponse>
fn GetFrequencyResponse(
&self,
frequency_hz: CustomAutoRooterGuard<Float32Array>,
mut mag_response: CustomAutoRooterGuard<Float32Array>,
mut phase_response: CustomAutoRooterGuard<Float32Array>,
) -> Result<(), Error> {
let len = frequency_hz.len();
if len != mag_response.len() || len != phase_response.len() {
return Err(Error::InvalidAccess);
}
let feedforward: Vec<f64> = (self.feedforward.iter().map(|v| **v).collect_vec()).to_vec();
let feedback: Vec<f64> = (self.feedback.iter().map(|v| **v).collect_vec()).to_vec();
let frequency_hz_vec = frequency_hz.to_vec();
let mut mag_response_vec = mag_response.to_vec();
let mut phase_response_vec = phase_response.to_vec();
IIRFilter::get_frequency_response(
&feedforward,
&feedback,
&frequency_hz_vec,
&mut mag_response_vec,
&mut phase_response_vec,
);
unsafe {
mag_response.update(&mag_response_vec);
phase_response.update(&phase_response_vec);
}
Ok(())
}
}
impl<'a> From<&'a IIRFilterOptions> for IIRFilterNodeOptions {
fn from(options: &'a IIRFilterOptions) -> Self {
let feedforward: Vec<f64> =
(*options.feedforward.iter().map(|v| **v).collect_vec()).to_vec();
let feedback: Vec<f64> = (*options.feedback.iter().map(|v| **v).collect_vec()).to_vec();
Self {
feedforward: Arc::new(feedforward),
feedback: Arc::new(feedback),
}
}
}

View file

@ -440,6 +440,7 @@ pub mod htmlulistelement;
pub mod htmlunknownelement;
pub mod htmlvideoelement;
pub mod identityhub;
pub mod iirfilternode;
pub mod imagebitmap;
pub mod imagedata;
pub mod inputevent;

View file

@ -39,8 +39,8 @@ interface BaseAudioContext : EventTarget {
[Throws] GainNode createGain();
// DelayNode createDelay(optional double maxDelayTime = 1);
[Throws] BiquadFilterNode createBiquadFilter();
// IIRFilterNode createIIRFilter(sequence<double> feedforward,
// sequence<double> feedback);
[Throws] IIRFilterNode createIIRFilter(sequence<double> feedforward,
sequence<double> feedback);
// WaveShaperNode createWaveShaper();
[Throws] PannerNode createPanner();
[Throws] StereoPannerNode createStereoPanner();

View file

@ -0,0 +1,22 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
/*
* The origin of this IDL file is
* https://webaudio.github.io/web-audio-api/#IIRFilterNode
*/
[Exposed=Window]
interface IIRFilterNode : AudioNode {
[Throws] constructor (BaseAudioContext context, IIRFilterOptions options);
[Throws] undefined getFrequencyResponse (
Float32Array frequencyHz,
Float32Array magResponse,
Float32Array phaseResponse
);
};
dictionary IIRFilterOptions : AudioNodeOptions {
required sequence<double> feedforward;
required sequence<double> feedback;
};