Skip to main content

web_audio_api/context/
base.rs

1//! The `BaseAudioContext` interface
2
3use crate::buffer::{AudioBuffer, AudioBufferOptions};
4use crate::context::{
5    AudioContextRegistration, AudioContextState, AudioParamId, ConcreteBaseAudioContext,
6    DESTINATION_NODE_ID,
7};
8use crate::decoding::decode_media_data;
9use crate::events::{Event, EventHandler, EventType};
10use crate::node::{AudioNode, AudioNodeOptions};
11use crate::param::AudioParamDescriptor;
12use crate::periodic_wave::{PeriodicWave, PeriodicWaveOptions};
13use crate::{node, AudioListener, RENDER_QUANTUM_SIZE};
14
15use std::future::Future;
16
17/// The interface representing an audio-processing graph built from audio modules linked together,
18/// each represented by an `AudioNode`.
19///
20/// An audio context controls both the creation of the nodes it contains and the execution of the
21/// audio processing, or decoding.
22#[allow(clippy::module_name_repetitions)]
23pub trait BaseAudioContext {
24    /// Returns the [`BaseAudioContext`] concrete type associated with this `AudioContext`
25    #[doc(hidden)] // we'd rather not expose the ConcreteBaseAudioContext
26    fn base(&self) -> &ConcreteBaseAudioContext;
27
28    /// Decode an [`AudioBuffer`] from a given input stream.
29    ///
30    /// The current implementation supports Symphonia's audio formats and codecs,
31    /// including AIFF, CAF, ISO/MP4, MKV/WebM, Ogg, WAV, AAC, ADPCM, ALAC,
32    /// FLAC, MP1/MP2/MP3, PCM, and Vorbis.
33    ///
34    /// In addition to the official spec, the input parameter can be any byte stream (not just an
35    /// array). This means you can decode audio data from a file, network stream, or in memory
36    /// buffer, and any other [`std::io::Read`] implementer. The data is buffered internally so you
37    /// should not wrap the source in a `BufReader`.
38    ///
39    /// This function operates synchronously, which may be undesirable on the control thread. The
40    /// example shows how to avoid this. See also the async method [`Self::decode_audio_data`].
41    ///
42    /// # Errors
43    ///
44    /// This method returns an Error in various cases (IO, mime sniffing, decoding).
45    ///
46    /// # Usage
47    ///
48    /// ```no_run
49    /// use std::io::Cursor;
50    /// use web_audio_api::context::{BaseAudioContext, OfflineAudioContext};
51    ///
52    /// let input = Cursor::new(vec![0; 32]); // or a File, TcpStream, ...
53    ///
54    /// let context = OfflineAudioContext::new(2, 44_100, 44_100.);
55    /// let handle = std::thread::spawn(move || context.decode_audio_data_sync(input));
56    ///
57    /// // do other things
58    ///
59    /// // await result from the decoder thread
60    /// let decode_buffer_result = handle.join();
61    /// ```
62    ///
63    /// # Examples
64    ///
65    /// The following example shows how to use a thread pool for audio buffer decoding:
66    ///
67    /// `cargo run --release --example decode_multithreaded`
68    fn decode_audio_data_sync<R: std::io::Read + Send + Sync + 'static>(
69        &self,
70        input: R,
71    ) -> Result<AudioBuffer, Box<dyn std::error::Error + Send + Sync>> {
72        decode_media_data(input, self.sample_rate())
73    }
74
75    /// Decode an [`AudioBuffer`] from a given input stream.
76    ///
77    /// The current implementation supports Symphonia's audio formats and codecs,
78    /// including AIFF, CAF, ISO/MP4, MKV/WebM, Ogg, WAV, AAC, ADPCM, ALAC,
79    /// FLAC, MP1/MP2/MP3, PCM, and Vorbis.
80    ///
81    /// In addition to the official spec, the input parameter can be any byte stream (not just an
82    /// array). This means you can decode audio data from a file, network stream, or in memory
83    /// buffer, and any other [`std::io::Read`] implementer. The data is buffered internally so you
84    /// should not wrap the source in a `BufReader`.
85    ///
86    /// Warning, the current implementation still uses blocking IO so it's best to use Tokio's
87    /// `spawn_blocking` to run the decoding on a thread dedicated to blocking operations. See also
88    /// the async method [`Self::decode_audio_data_sync`].
89    ///
90    /// # Errors
91    ///
92    /// This method returns an Error in various cases (IO, mime sniffing, decoding).
93    // Use of `async fn` in public traits is discouraged as auto trait bounds cannot be specified,
94    // hence we use `-> impl Future + ..` instead.
95    fn decode_audio_data<R: std::io::Read + Send + Sync + 'static>(
96        &self,
97        input: R,
98    ) -> impl Future<Output = Result<AudioBuffer, Box<dyn std::error::Error + Send + Sync>>>
99           + Send
100           + 'static {
101        let sample_rate = self.sample_rate();
102        async move { decode_media_data(input, sample_rate) }
103    }
104
105    /// Create an new "in-memory" `AudioBuffer` with the given number of channels,
106    /// length (i.e. number of samples per channel) and sample rate.
107    ///
108    /// Note: In most cases you will want the sample rate to match the current
109    /// audio context sample rate.
110    #[must_use]
111    fn create_buffer(
112        &self,
113        number_of_channels: usize,
114        length: usize,
115        sample_rate: f32,
116    ) -> AudioBuffer {
117        let options = AudioBufferOptions {
118            number_of_channels,
119            length,
120            sample_rate,
121        };
122
123        AudioBuffer::new(options)
124    }
125
126    /// Creates a `AnalyserNode`
127    #[must_use]
128    fn create_analyser(&self) -> node::AnalyserNode {
129        node::AnalyserNode::new(self.base(), node::AnalyserOptions::default())
130    }
131
132    /// Creates an `BiquadFilterNode` which implements a second order filter
133    #[must_use]
134    fn create_biquad_filter(&self) -> node::BiquadFilterNode {
135        node::BiquadFilterNode::new(self.base(), node::BiquadFilterOptions::default())
136    }
137
138    /// Creates an `AudioBufferSourceNode`
139    #[must_use]
140    fn create_buffer_source(&self) -> node::AudioBufferSourceNode {
141        node::AudioBufferSourceNode::new(self.base(), node::AudioBufferSourceOptions::default())
142    }
143
144    /// Creates an `ConstantSourceNode`, a source representing a constant value
145    #[must_use]
146    fn create_constant_source(&self) -> node::ConstantSourceNode {
147        node::ConstantSourceNode::new(self.base(), node::ConstantSourceOptions::default())
148    }
149
150    /// Creates an `ConvolverNode`, a processing node which applies linear convolution
151    #[must_use]
152    fn create_convolver(&self) -> node::ConvolverNode {
153        node::ConvolverNode::new(self.base(), node::ConvolverOptions::default())
154    }
155
156    /// Creates a `ChannelMergerNode`
157    #[must_use]
158    fn create_channel_merger(&self, number_of_inputs: usize) -> node::ChannelMergerNode {
159        let opts = node::ChannelMergerOptions {
160            number_of_inputs,
161            ..node::ChannelMergerOptions::default()
162        };
163        node::ChannelMergerNode::new(self.base(), opts)
164    }
165
166    /// Creates a `ChannelSplitterNode`
167    #[must_use]
168    fn create_channel_splitter(&self, number_of_outputs: usize) -> node::ChannelSplitterNode {
169        let opts = node::ChannelSplitterOptions {
170            number_of_outputs,
171            ..node::ChannelSplitterOptions::default()
172        };
173        node::ChannelSplitterNode::new(self.base(), opts)
174    }
175
176    /// Creates a `DelayNode`, delaying the audio signal
177    #[must_use]
178    fn create_delay(&self, max_delay_time: f64) -> node::DelayNode {
179        let opts = node::DelayOptions {
180            max_delay_time,
181            ..node::DelayOptions::default()
182        };
183        node::DelayNode::new(self.base(), opts)
184    }
185
186    /// Creates a `DynamicsCompressorNode`, compressing the audio signal
187    #[must_use]
188    fn create_dynamics_compressor(&self) -> node::DynamicsCompressorNode {
189        node::DynamicsCompressorNode::new(self.base(), node::DynamicsCompressorOptions::default())
190    }
191
192    /// Creates an `GainNode`, to control audio volume
193    #[must_use]
194    fn create_gain(&self) -> node::GainNode {
195        node::GainNode::new(self.base(), node::GainOptions::default())
196    }
197
198    /// Creates an `IirFilterNode`
199    ///
200    /// # Arguments
201    ///
202    /// * `feedforward` - An array of the feedforward (numerator) coefficients for the transfer function of the IIR filter.
203    ///   The maximum length of this array is 20
204    /// * `feedback` - An array of the feedback (denominator) coefficients for the transfer function of the IIR filter.
205    ///   The maximum length of this array is 20
206    #[must_use]
207    fn create_iir_filter(&self, feedforward: Vec<f64>, feedback: Vec<f64>) -> node::IIRFilterNode {
208        let options = node::IIRFilterOptions {
209            audio_node_options: AudioNodeOptions::default(),
210            feedforward,
211            feedback,
212        };
213        node::IIRFilterNode::new(self.base(), options)
214    }
215
216    /// Creates an `OscillatorNode`, a source representing a periodic waveform.
217    #[must_use]
218    fn create_oscillator(&self) -> node::OscillatorNode {
219        node::OscillatorNode::new(self.base(), node::OscillatorOptions::default())
220    }
221
222    /// Creates a `PannerNode`
223    #[must_use]
224    fn create_panner(&self) -> node::PannerNode {
225        node::PannerNode::new(self.base(), node::PannerOptions::default())
226    }
227
228    /// Creates a periodic wave
229    ///
230    /// Please note that this constructor deviates slightly from the spec by requiring a single
231    /// argument with the periodic wave options.
232    #[must_use]
233    fn create_periodic_wave(&self, options: PeriodicWaveOptions) -> PeriodicWave {
234        PeriodicWave::new(self.base(), options)
235    }
236
237    /// Creates an `ScriptProcessorNode` for custom audio processing (deprecated);
238    ///
239    /// # Panics
240    ///
241    /// This function panics if:
242    /// - `buffer_size` is not 256, 512, 1024, 2048, 4096, 8192, or 16384
243    /// - the number of input and output channels are both zero
244    /// - either of the channel counts exceed [`crate::MAX_CHANNELS`]
245    #[must_use]
246    fn create_script_processor(
247        &self,
248        buffer_size: usize,
249        number_of_input_channels: usize,
250        number_of_output_channels: usize,
251    ) -> node::ScriptProcessorNode {
252        let options = node::ScriptProcessorOptions {
253            buffer_size,
254            number_of_input_channels,
255            number_of_output_channels,
256        };
257
258        node::ScriptProcessorNode::new(self.base(), options)
259    }
260
261    /// Creates an `StereoPannerNode` to pan a stereo output
262    #[must_use]
263    fn create_stereo_panner(&self) -> node::StereoPannerNode {
264        node::StereoPannerNode::new(self.base(), node::StereoPannerOptions::default())
265    }
266
267    /// Creates a `WaveShaperNode`
268    #[must_use]
269    fn create_wave_shaper(&self) -> node::WaveShaperNode {
270        node::WaveShaperNode::new(self.base(), node::WaveShaperOptions::default())
271    }
272
273    /// Returns an `AudioDestinationNode` representing the final destination of all audio in the
274    /// context. It can be thought of as the audio-rendering device.
275    #[must_use]
276    fn destination(&self) -> node::AudioDestinationNode {
277        let registration = AudioContextRegistration {
278            id: DESTINATION_NODE_ID,
279            context: self.base().clone(),
280        };
281        let channel_config = self.base().destination_channel_config();
282        node::AudioDestinationNode::from_raw_parts(registration, channel_config)
283    }
284
285    /// Returns the `AudioListener` which is used for 3D spatialization
286    #[must_use]
287    fn listener(&self) -> AudioListener {
288        self.base().listener()
289    }
290
291    /// The sample rate (in sample-frames per second) at which the `AudioContext` handles audio.
292    #[must_use]
293    fn sample_rate(&self) -> f32 {
294        self.base().sample_rate()
295    }
296
297    /// Returns state of current context
298    #[must_use]
299    fn state(&self) -> AudioContextState {
300        self.base().state()
301    }
302
303    /// This is the time in seconds of the sample frame immediately following the last sample-frame
304    /// in the block of audio most recently processed by the context’s rendering graph.
305    #[must_use]
306    fn current_time(&self) -> f64 {
307        self.base().current_time()
308    }
309
310    // Number of frames computed on each render block
311    #[must_use]
312    fn render_quantum_size(&self) -> usize {
313        RENDER_QUANTUM_SIZE
314    }
315
316    /// Create an `AudioParam`.
317    ///
318    /// Call this inside the `register` closure when setting up your `AudioNode`
319    #[must_use]
320    fn create_audio_param(
321        &self,
322        opts: AudioParamDescriptor,
323        dest: &AudioContextRegistration,
324    ) -> (crate::param::AudioParam, AudioParamId) {
325        let param = self.base().register(move |registration| {
326            let (node, proc) = crate::param::audio_param_pair(opts, registration);
327
328            (node, Box::new(proc))
329        });
330
331        // Connect the param to the node, once the node is registered inside the audio graph.
332        self.base().queue_audio_param_connect(&param, dest.id());
333
334        let proc_id = AudioParamId(param.registration().id().0);
335        (param, proc_id)
336    }
337
338    /// Register callback to run when the state of the AudioContext has changed
339    ///
340    /// Only a single event handler is active at any time. Calling this method multiple times will
341    /// override the previous event handler.
342    fn set_onstatechange<F: FnMut(Event) + Send + 'static>(&self, mut callback: F) {
343        let callback = move |_| {
344            callback(Event {
345                type_: "statechange",
346            })
347        };
348
349        self.base().set_event_handler(
350            EventType::StateChange,
351            EventHandler::Multiple(Box::new(callback)),
352        );
353    }
354
355    /// Unset the callback to run when the state of the AudioContext has changed
356    fn clear_onstatechange(&self) {
357        self.base().clear_event_handler(EventType::StateChange);
358    }
359
360    #[cfg(test)]
361    fn mock_registration(&self) -> AudioContextRegistration {
362        AudioContextRegistration {
363            id: crate::context::AudioNodeId(0),
364            context: self.base().clone(),
365        }
366    }
367}
368
369#[cfg(test)]
370mod tests {
371    use super::*;
372    use crate::context::OfflineAudioContext;
373
374    use float_eq::assert_float_eq;
375
376    fn require_send_sync_static<T: Send + Sync + 'static>(_: T) {}
377
378    #[test]
379    fn test_decode_audio_data_sync() {
380        let context = OfflineAudioContext::new(1, 1, 44100.);
381        let file = std::fs::File::open("samples/sample.wav").unwrap();
382        let audio_buffer = context.decode_audio_data_sync(file).unwrap();
383
384        assert_eq!(audio_buffer.sample_rate(), 44100.);
385        assert_eq!(audio_buffer.length(), 142_187);
386        assert_eq!(audio_buffer.number_of_channels(), 2);
387        assert_float_eq!(audio_buffer.duration(), 3.224, abs_all <= 0.001);
388
389        let left_start = &audio_buffer.get_channel_data(0)[0..100];
390        let right_start = &audio_buffer.get_channel_data(1)[0..100];
391        // assert distinct two channel data
392        assert!(left_start != right_start);
393    }
394
395    #[test]
396    fn test_decode_audio_data_future_send_static() {
397        let context = OfflineAudioContext::new(1, 1, 44100.);
398        let file = std::fs::File::open("samples/sample.wav").unwrap();
399        let future = context.decode_audio_data(file);
400        require_send_sync_static(future);
401    }
402
403    #[test]
404    fn test_decode_audio_data_async() {
405        use futures::executor;
406        let context = OfflineAudioContext::new(1, 1, 44100.);
407        let file = std::fs::File::open("samples/sample.wav").unwrap();
408        let future = context.decode_audio_data(file);
409        let audio_buffer = executor::block_on(future).unwrap();
410
411        assert_eq!(audio_buffer.sample_rate(), 44100.);
412        assert_eq!(audio_buffer.length(), 142_187);
413        assert_eq!(audio_buffer.number_of_channels(), 2);
414        assert_float_eq!(audio_buffer.duration(), 3.224, abs_all <= 0.001);
415
416        let left_start = &audio_buffer.get_channel_data(0)[0..100];
417        let right_start = &audio_buffer.get_channel_data(1)[0..100];
418        // assert distinct two channel data
419        assert!(left_start != right_start);
420    }
421
422    // #[test]
423    // disabled: symphonia cannot handle empty WAV-files
424    #[allow(dead_code)]
425    fn test_decode_audio_data_empty() {
426        let context = OfflineAudioContext::new(1, 1, 44100.);
427        let file = std::fs::File::open("samples/empty_2c.wav").unwrap();
428        let audio_buffer = context.decode_audio_data_sync(file).unwrap();
429        assert_eq!(audio_buffer.length(), 0);
430    }
431
432    #[test]
433    fn test_decode_audio_data_decoding_error() {
434        let context = OfflineAudioContext::new(1, 1, 44100.);
435        let file = std::fs::File::open("samples/corrupt.wav").unwrap();
436        assert!(context.decode_audio_data_sync(file).is_err());
437    }
438
439    #[test]
440    fn test_create_buffer() {
441        let number_of_channels = 3;
442        let length = 2000;
443        let sample_rate = 96_000.;
444
445        let context = OfflineAudioContext::new(1, 1, 44100.);
446        let buffer = context.create_buffer(number_of_channels, length, sample_rate);
447
448        assert_eq!(buffer.number_of_channels(), 3);
449        assert_eq!(buffer.length(), 2000);
450        assert_float_eq!(buffer.sample_rate(), 96000., abs_all <= 0.);
451    }
452}