Skip to main content

script/dom/audio/
baseaudiocontext.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::cell::Cell;
6use std::collections::hash_map::Entry;
7use std::collections::{HashMap, VecDeque};
8use std::rc::Rc;
9use std::sync::{Arc, Mutex};
10
11use dom_struct::dom_struct;
12use js::context::JSContext;
13use js::rust::CustomAutoRooterGuard;
14use js::typedarray::ArrayBuffer;
15use script_bindings::cell::DomRefCell;
16use script_bindings::cformat;
17use servo_base::id::PipelineId;
18use servo_media::audio::context::{
19    AudioContext, AudioContextOptions, OfflineAudioContextOptions, ProcessingState,
20    RealTimeAudioContextOptions,
21};
22use servo_media::audio::decoder::AudioDecoderCallbacksBuilder;
23use servo_media::audio::graph::NodeId;
24use servo_media::{ClientContextId, ServoMedia};
25use uuid::Uuid;
26
27use crate::conversions::Convert;
28use crate::dom::audio::analysernode::AnalyserNode;
29use crate::dom::audio::audiobuffer::AudioBuffer;
30use crate::dom::audio::audiobuffersourcenode::AudioBufferSourceNode;
31use crate::dom::audio::audiodestinationnode::AudioDestinationNode;
32use crate::dom::audio::audiolistener::AudioListener;
33use crate::dom::audio::audionode::MAX_CHANNEL_COUNT;
34use crate::dom::audio::biquadfilternode::BiquadFilterNode;
35use crate::dom::audio::channelmergernode::ChannelMergerNode;
36use crate::dom::audio::channelsplitternode::ChannelSplitterNode;
37use crate::dom::audio::constantsourcenode::ConstantSourceNode;
38use crate::dom::audio::gainnode::GainNode;
39use crate::dom::audio::iirfilternode::IIRFilterNode;
40use crate::dom::audio::oscillatornode::OscillatorNode;
41use crate::dom::audio::pannernode::PannerNode;
42use crate::dom::audio::stereopannernode::StereoPannerNode;
43use crate::dom::bindings::callback::ExceptionHandling;
44use crate::dom::bindings::codegen::Bindings::AnalyserNodeBinding::AnalyserOptions;
45use crate::dom::bindings::codegen::Bindings::AudioBufferSourceNodeBinding::AudioBufferSourceOptions;
46use crate::dom::bindings::codegen::Bindings::AudioNodeBinding::{
47    AudioNodeOptions, ChannelCountMode, ChannelInterpretation,
48};
49use crate::dom::bindings::codegen::Bindings::BaseAudioContextBinding::{
50    AudioContextState, BaseAudioContextMethods, DecodeErrorCallback, DecodeSuccessCallback,
51};
52use crate::dom::bindings::codegen::Bindings::BiquadFilterNodeBinding::BiquadFilterOptions;
53use crate::dom::bindings::codegen::Bindings::ChannelMergerNodeBinding::ChannelMergerOptions;
54use crate::dom::bindings::codegen::Bindings::ChannelSplitterNodeBinding::ChannelSplitterOptions;
55use crate::dom::bindings::codegen::Bindings::ConstantSourceNodeBinding::ConstantSourceOptions;
56use crate::dom::bindings::codegen::Bindings::GainNodeBinding::GainOptions;
57use crate::dom::bindings::codegen::Bindings::IIRFilterNodeBinding::IIRFilterOptions;
58use crate::dom::bindings::codegen::Bindings::OscillatorNodeBinding::OscillatorOptions;
59use crate::dom::bindings::codegen::Bindings::PannerNodeBinding::PannerOptions;
60use crate::dom::bindings::codegen::Bindings::StereoPannerNodeBinding::StereoPannerOptions;
61use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
62use crate::dom::bindings::inheritance::Castable;
63use crate::dom::bindings::num::Finite;
64use crate::dom::bindings::refcounted::Trusted;
65use crate::dom::bindings::reflector::DomGlobal;
66use crate::dom::bindings::root::{DomRoot, MutNullableDom};
67use crate::dom::domexception::{DOMErrorName, DOMException};
68use crate::dom::eventtarget::EventTarget;
69use crate::dom::promise::Promise;
70use crate::realms::InRealm;
71use crate::script_runtime::CanGc;
72
73pub(crate) enum BaseAudioContextOptions {
74    AudioContext(RealTimeAudioContextOptions),
75    OfflineAudioContext(OfflineAudioContextOptions),
76}
77
78#[derive(JSTraceable, MallocSizeOf)]
79struct DecodeResolver {
80    #[conditional_malloc_size_of]
81    pub(crate) promise: Rc<Promise>,
82    #[conditional_malloc_size_of]
83    pub(crate) success_callback: Option<Rc<DecodeSuccessCallback>>,
84    #[conditional_malloc_size_of]
85    pub(crate) error_callback: Option<Rc<DecodeErrorCallback>>,
86}
87
88type BoxedSliceOfPromises = Box<[Rc<Promise>]>;
89
90#[dom_struct]
91pub(crate) struct BaseAudioContext {
92    eventtarget: EventTarget,
93    #[ignore_malloc_size_of = "servo_media"]
94    #[no_trace]
95    audio_context_impl: Arc<Mutex<AudioContext>>,
96    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-destination>
97    destination: MutNullableDom<AudioDestinationNode>,
98    listener: MutNullableDom<AudioListener>,
99    /// Resume promises which are soon to be fulfilled by a queued task.
100    #[conditional_malloc_size_of]
101    in_flight_resume_promises_queue: DomRefCell<VecDeque<(BoxedSliceOfPromises, ErrorResult)>>,
102    /// <https://webaudio.github.io/web-audio-api/#pendingresumepromises>
103    #[conditional_malloc_size_of]
104    pending_resume_promises: DomRefCell<Vec<Rc<Promise>>>,
105    decode_resolvers: DomRefCell<HashMap<String, DecodeResolver>>,
106    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-samplerate>
107    sample_rate: f32,
108    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-state>
109    /// Although servo-media already keeps track of the control thread state,
110    /// we keep a state flag here as well. This is so that we can synchronously
111    /// throw when trying to do things on the context when the context has just
112    /// been "closed()".
113    state: Cell<AudioContextState>,
114    channel_count: u32,
115}
116
117impl BaseAudioContext {
118    pub(crate) fn new_inherited(
119        options: BaseAudioContextOptions,
120        pipeline_id: PipelineId,
121    ) -> Fallible<BaseAudioContext> {
122        let (sample_rate, channel_count) = match options {
123            BaseAudioContextOptions::AudioContext(ref opt) => (opt.sample_rate, 2),
124            BaseAudioContextOptions::OfflineAudioContext(ref opt) => {
125                (opt.sample_rate, opt.channels)
126            },
127        };
128
129        let client_context_id =
130            ClientContextId::build(pipeline_id.namespace_id.0, pipeline_id.index.0.get());
131        let audio_context_impl = ServoMedia::get()
132            .create_audio_context(&client_context_id, options.convert())
133            .map_err(|_| Error::NotSupported(None))?;
134
135        Ok(BaseAudioContext {
136            eventtarget: EventTarget::new_inherited(),
137            audio_context_impl,
138            destination: Default::default(),
139            listener: Default::default(),
140            in_flight_resume_promises_queue: Default::default(),
141            pending_resume_promises: Default::default(),
142            decode_resolvers: Default::default(),
143            sample_rate,
144            state: Cell::new(AudioContextState::Suspended),
145            channel_count: channel_count.into(),
146        })
147    }
148
149    /// Tells whether this is an OfflineAudioContext or not.
150    pub(crate) fn is_offline(&self) -> bool {
151        false
152    }
153
154    pub(crate) fn audio_context_impl(&self) -> Arc<Mutex<AudioContext>> {
155        self.audio_context_impl.clone()
156    }
157
158    pub(crate) fn destination_node(&self) -> NodeId {
159        self.audio_context_impl.lock().unwrap().dest_node()
160    }
161
162    pub(crate) fn listener(&self) -> NodeId {
163        self.audio_context_impl.lock().unwrap().listener()
164    }
165
166    // https://webaudio.github.io/web-audio-api/#allowed-to-start
167    pub(crate) fn is_allowed_to_start(&self) -> bool {
168        self.state.get() == AudioContextState::Suspended
169    }
170
171    fn push_pending_resume_promise(&self, promise: &Rc<Promise>) {
172        self.pending_resume_promises
173            .borrow_mut()
174            .push(promise.clone());
175    }
176
177    /// Takes the pending resume promises.
178    ///
179    /// The result with which these promises will be fulfilled is passed here
180    /// and this method returns nothing because we actually just move the
181    /// current list of pending resume promises to the
182    /// `in_flight_resume_promises_queue` field.
183    ///
184    /// Each call to this method must be followed by a call to
185    /// `fulfill_in_flight_resume_promises`, to actually fulfill the promises
186    /// which were taken and moved to the in-flight queue.
187    fn take_pending_resume_promises(&self, result: ErrorResult) {
188        let pending_resume_promises =
189            std::mem::take(&mut *self.pending_resume_promises.borrow_mut());
190        self.in_flight_resume_promises_queue
191            .borrow_mut()
192            .push_back((pending_resume_promises.into(), result));
193    }
194
195    /// Fulfills the next in-flight resume promises queue after running a closure.
196    ///
197    /// See the comment on `take_pending_resume_promises` for why this method
198    /// does not take a list of promises to fulfill. Callers cannot just pop
199    /// the front list off of `in_flight_resume_promises_queue` and later fulfill
200    /// the promises because that would mean putting
201    /// `#[cfg_attr(crown, expect(crown::unrooted_must_root))]` on even more functions, potentially
202    /// hiding actual safety bugs.
203    fn fulfill_in_flight_resume_promises<F>(&self, f: F)
204    where
205        F: FnOnce(),
206    {
207        let (promises, result) = self
208            .in_flight_resume_promises_queue
209            .borrow_mut()
210            .pop_front()
211            .expect("there should be at least one list of in flight resume promises");
212        f();
213        for promise in &*promises {
214            match result {
215                Ok(ref value) => promise.resolve_native(value, CanGc::deprecated_note()),
216                Err(ref error) => promise.reject_error(error.clone(), CanGc::deprecated_note()),
217            }
218        }
219    }
220
221    /// Control thread processing state
222    pub(crate) fn control_thread_state(&self) -> ProcessingState {
223        self.audio_context_impl.lock().unwrap().state()
224    }
225
226    /// Set audio context state
227    pub(crate) fn set_state_attribute(&self, state: AudioContextState) {
228        self.state.set(state);
229    }
230
231    pub(crate) fn resume(&self) {
232        let this = Trusted::new(self);
233        // Set the rendering thread state to 'running' and start
234        // rendering the audio graph.
235        match self.audio_context_impl.lock().unwrap().resume() {
236            Some(()) => {
237                self.take_pending_resume_promises(Ok(()));
238                self.global().task_manager().dom_manipulation_task_source().queue(
239                    task!(resume_success: move || {
240                        let this = this.root();
241                        this.fulfill_in_flight_resume_promises(|| {
242                            if this.state.get() != AudioContextState::Running {
243                                this.state.set(AudioContextState::Running);
244                                this.global().task_manager().dom_manipulation_task_source().queue_simple_event(
245                                    this.upcast(),
246                                    atom!("statechange"),
247                                    );
248                            }
249                        });
250                    })
251                );
252            },
253            None => {
254                self.take_pending_resume_promises(Err(Error::Type(
255                    c"Something went wrong".to_owned(),
256                )));
257                self.global()
258                    .task_manager()
259                    .dom_manipulation_task_source()
260                    .queue(task!(resume_error: move || {
261                        this.root().fulfill_in_flight_resume_promises(|| {})
262                    }));
263            },
264        }
265    }
266
267    pub(crate) fn channel_count(&self) -> u32 {
268        self.channel_count
269    }
270}
271
272impl BaseAudioContextMethods<crate::DomTypeHolder> for BaseAudioContext {
273    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-samplerate>
274    fn SampleRate(&self) -> Finite<f32> {
275        Finite::wrap(self.sample_rate)
276    }
277
278    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-currenttime>
279    fn CurrentTime(&self) -> Finite<f64> {
280        let current_time = self.audio_context_impl.lock().unwrap().current_time();
281        Finite::wrap(current_time)
282    }
283
284    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-state>
285    fn State(&self) -> AudioContextState {
286        self.state.get()
287    }
288
289    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-resume>
290    fn Resume(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
291        // Step 1.
292        let promise = Promise::new_in_current_realm(comp, can_gc);
293
294        // Step 2.
295        if self.audio_context_impl.lock().unwrap().state() == ProcessingState::Closed {
296            promise.reject_error(Error::InvalidState(None), can_gc);
297            return promise;
298        }
299
300        // Step 3.
301        if self.state.get() == AudioContextState::Running {
302            promise.resolve_native(&(), can_gc);
303            return promise;
304        }
305
306        self.push_pending_resume_promise(&promise);
307
308        // Step 4.
309        if !self.is_allowed_to_start() {
310            return promise;
311        }
312
313        // Steps 5 and 6.
314        self.resume();
315
316        // Step 7.
317        promise
318    }
319
320    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-destination>
321    fn Destination(&self, can_gc: CanGc) -> DomRoot<AudioDestinationNode> {
322        let global = self.global();
323        self.destination.or_init(|| {
324            let mut options = AudioNodeOptions::empty();
325            options.channelCount = Some(self.channel_count);
326            options.channelCountMode = Some(ChannelCountMode::Explicit);
327            options.channelInterpretation = Some(ChannelInterpretation::Speakers);
328            AudioDestinationNode::new(&global, self, &options, can_gc)
329        })
330    }
331
332    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-listener>
333    fn Listener(&self, can_gc: CanGc) -> DomRoot<AudioListener> {
334        let global = self.global();
335        let window = global.as_window();
336        self.listener
337            .or_init(|| AudioListener::new(window, self, can_gc))
338    }
339
340    // https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-onstatechange
341    event_handler!(statechange, GetOnstatechange, SetOnstatechange);
342
343    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createoscillator>
344    fn CreateOscillator(&self, can_gc: CanGc) -> Fallible<DomRoot<OscillatorNode>> {
345        OscillatorNode::new(
346            self.global().as_window(),
347            self,
348            &OscillatorOptions::empty(),
349            can_gc,
350        )
351    }
352
353    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-creategain>
354    fn CreateGain(&self, can_gc: CanGc) -> Fallible<DomRoot<GainNode>> {
355        GainNode::new(
356            self.global().as_window(),
357            self,
358            &GainOptions::empty(),
359            can_gc,
360        )
361    }
362
363    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createpanner>
364    fn CreatePanner(&self, can_gc: CanGc) -> Fallible<DomRoot<PannerNode>> {
365        PannerNode::new(
366            self.global().as_window(),
367            self,
368            &PannerOptions::empty(),
369            can_gc,
370        )
371    }
372
373    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createanalyser>
374    fn CreateAnalyser(&self, can_gc: CanGc) -> Fallible<DomRoot<AnalyserNode>> {
375        AnalyserNode::new(
376            self.global().as_window(),
377            self,
378            &AnalyserOptions::empty(),
379            can_gc,
380        )
381    }
382
383    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbiquadfilter>
384    fn CreateBiquadFilter(&self, can_gc: CanGc) -> Fallible<DomRoot<BiquadFilterNode>> {
385        BiquadFilterNode::new(
386            self.global().as_window(),
387            self,
388            &BiquadFilterOptions::empty(),
389            can_gc,
390        )
391    }
392
393    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createstereopanner>
394    fn CreateStereoPanner(&self, can_gc: CanGc) -> Fallible<DomRoot<StereoPannerNode>> {
395        StereoPannerNode::new(
396            self.global().as_window(),
397            self,
398            &StereoPannerOptions::empty(),
399            can_gc,
400        )
401    }
402
403    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createconstantsource>
404    fn CreateConstantSource(&self, can_gc: CanGc) -> Fallible<DomRoot<ConstantSourceNode>> {
405        ConstantSourceNode::new(
406            self.global().as_window(),
407            self,
408            &ConstantSourceOptions::empty(),
409            can_gc,
410        )
411    }
412
413    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createchannelmerger>
414    fn CreateChannelMerger(
415        &self,
416        count: u32,
417        can_gc: CanGc,
418    ) -> Fallible<DomRoot<ChannelMergerNode>> {
419        let mut opts = ChannelMergerOptions::empty();
420        opts.numberOfInputs = count;
421        ChannelMergerNode::new(self.global().as_window(), self, &opts, can_gc)
422    }
423
424    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createchannelsplitter>
425    fn CreateChannelSplitter(
426        &self,
427        count: u32,
428        can_gc: CanGc,
429    ) -> Fallible<DomRoot<ChannelSplitterNode>> {
430        let mut opts = ChannelSplitterOptions::empty();
431        opts.numberOfOutputs = count;
432        ChannelSplitterNode::new(self.global().as_window(), self, &opts, can_gc)
433    }
434
435    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbuffer>
436    fn CreateBuffer(
437        &self,
438        cx: &mut JSContext,
439        number_of_channels: u32,
440        length: u32,
441        sample_rate: Finite<f32>,
442    ) -> Fallible<DomRoot<AudioBuffer>> {
443        if number_of_channels == 0 ||
444            number_of_channels > MAX_CHANNEL_COUNT ||
445            length == 0 ||
446            *sample_rate <= 0.
447        {
448            return Err(Error::NotSupported(None));
449        }
450        Ok(AudioBuffer::new(
451            cx,
452            self.global().as_window(),
453            number_of_channels,
454            length,
455            *sample_rate,
456            None,
457        ))
458    }
459
460    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbuffersource>
461    fn CreateBufferSource(&self, can_gc: CanGc) -> Fallible<DomRoot<AudioBufferSourceNode>> {
462        AudioBufferSourceNode::new(
463            self.global().as_window(),
464            self,
465            &AudioBufferSourceOptions::empty(),
466            can_gc,
467        )
468    }
469
470    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-decodeaudiodata>
471    fn DecodeAudioData(
472        &self,
473        audio_data: CustomAutoRooterGuard<ArrayBuffer>,
474        decode_success_callback: Option<Rc<DecodeSuccessCallback>>,
475        decode_error_callback: Option<Rc<DecodeErrorCallback>>,
476        comp: InRealm,
477        can_gc: CanGc,
478    ) -> Rc<Promise> {
479        // Step 1.
480        let promise = Promise::new_in_current_realm(comp, can_gc);
481
482        if audio_data.len() > 0 {
483            // Step 2.
484            // XXX detach array buffer.
485            let uuid = Uuid::new_v4().simple().to_string();
486            let uuid_ = uuid.clone();
487            self.decode_resolvers.borrow_mut().insert(
488                uuid.clone(),
489                DecodeResolver {
490                    promise: promise.clone(),
491                    success_callback: decode_success_callback,
492                    error_callback: decode_error_callback,
493                },
494            );
495            let audio_data = audio_data.to_vec();
496            let decoded_audio = Arc::new(Mutex::new(Vec::new()));
497            let decoded_audio_ = decoded_audio.clone();
498            let decoded_audio__ = decoded_audio.clone();
499            // servo-media returns an audio channel position along
500            // with the AudioDecoderCallback progress callback, which
501            // may not be the same as the index of the decoded_audio
502            // Vec.
503            let channels = Arc::new(Mutex::new(HashMap::new()));
504            let this = Trusted::new(self);
505            let this_ = this.clone();
506            let task_source = self
507                .global()
508                .task_manager()
509                .dom_manipulation_task_source()
510                .to_sendable();
511            let task_source_clone = task_source.clone();
512            let callbacks = AudioDecoderCallbacksBuilder::default()
513                .ready(move |channel_count| {
514                    decoded_audio
515                        .lock()
516                        .unwrap()
517                        .resize(channel_count as usize, Vec::new());
518                })
519                .progress(move |buffer, channel_pos_mask| {
520                    let mut decoded_audio = decoded_audio_.lock().unwrap();
521                    let mut channels = channels.lock().unwrap();
522                    let channel = match channels.entry(channel_pos_mask) {
523                        Entry::Occupied(entry) => *entry.get(),
524                        Entry::Vacant(entry) => {
525                            let x = (channel_pos_mask as f32).log2() as usize;
526                            *entry.insert(x)
527                        },
528                    };
529                    decoded_audio[channel].extend_from_slice((*buffer).as_ref());
530                })
531                .eos(move || {
532                    task_source.queue(task!(audio_decode_eos: move |cx| {
533                        let this = this.root();
534                        let decoded_audio = decoded_audio__.lock().unwrap();
535                        let length = if !decoded_audio.is_empty() {
536                            decoded_audio[0].len()
537                        } else {
538                            0
539                        };
540                        let buffer = AudioBuffer::new(
541                            cx,
542                            this.global().as_window(),
543                            decoded_audio.len() as u32 /* number of channels */,
544                            length as u32,
545                            this.sample_rate,
546                            Some(decoded_audio.as_slice()),
547                        );
548                        let mut resolvers = this.decode_resolvers.borrow_mut();
549                        assert!(resolvers.contains_key(&uuid_));
550                        let resolver = resolvers.remove(&uuid_).unwrap();
551                        if let Some(callback) = resolver.success_callback {
552                            let _ = callback.Call__(cx, &buffer, ExceptionHandling::Report);
553                        }
554                        resolver.promise.resolve_native(&buffer, CanGc::from_cx(cx));
555                    }));
556                })
557                .error(move |error| {
558                    task_source_clone.queue(task!(audio_decode_eos: move |cx| {
559                        let this = this_.root();
560                        let mut resolvers = this.decode_resolvers.borrow_mut();
561                        assert!(resolvers.contains_key(&uuid));
562                        let resolver = resolvers.remove(&uuid).unwrap();
563                        if let Some(callback) = resolver.error_callback {
564                            let exception = DOMException::new(
565                                &this.global(),
566                                DOMErrorName::DataCloneError,
567                                CanGc::from_cx(cx)
568                            );
569                            let _ = callback.Call__(cx, &exception, ExceptionHandling::Report);
570                        }
571                        let error = cformat!("Audio decode error {:?}", error);
572                        resolver.promise.reject_error(Error::Type(error), CanGc::from_cx(cx));
573                    }));
574                })
575                .build();
576            self.audio_context_impl
577                .lock()
578                .unwrap()
579                .decode_audio_data(audio_data, callbacks);
580        } else {
581            // Step 3.
582            promise.reject_error(Error::DataClone(None), can_gc);
583            return promise;
584        }
585
586        // Step 4.
587        promise
588    }
589
590    /// <https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createiirfilter>
591    fn CreateIIRFilter(
592        &self,
593        feedforward: Vec<Finite<f64>>,
594        feedback: Vec<Finite<f64>>,
595        can_gc: CanGc,
596    ) -> Fallible<DomRoot<IIRFilterNode>> {
597        let opts = IIRFilterOptions {
598            parent: AudioNodeOptions::empty(),
599            feedback,
600            feedforward,
601        };
602        IIRFilterNode::new(self.global().as_window(), self, &opts, can_gc)
603    }
604}
605
606impl Convert<AudioContextOptions> for BaseAudioContextOptions {
607    fn convert(self) -> AudioContextOptions {
608        match self {
609            BaseAudioContextOptions::AudioContext(options) => {
610                AudioContextOptions::RealTimeAudioContext(options)
611            },
612            BaseAudioContextOptions::OfflineAudioContext(options) => {
613                AudioContextOptions::OfflineAudioContext(options)
614            },
615        }
616    }
617}
618
619impl Convert<AudioContextState> for ProcessingState {
620    fn convert(self) -> AudioContextState {
621        match self {
622            ProcessingState::Suspended => AudioContextState::Suspended,
623            ProcessingState::Running => AudioContextState::Running,
624            ProcessingState::Closed => AudioContextState::Closed,
625        }
626    }
627}