web_audio_api/node/destination.rs
1use crate::context::{AudioContextRegistration, BaseAudioContext};
2use crate::render::{
3    AudioParamValues, AudioProcessor, AudioRenderQuantum, AudioWorkletGlobalScope,
4};
5
6use super::{AudioNode, AudioNodeOptions, ChannelConfig, ChannelCountMode, ChannelInterpretation};
7
8/// The AudioDestinationNode interface represents the terminal node of an audio
9/// graph in a given context. usually the speakers of your device, or the node that
10/// will "record" the audio data with an OfflineAudioContext.
11///
12/// The output of a AudioDestinationNode is produced by summing its input, allowing to capture
13/// the output of an AudioContext into, for example, a MediaStreamAudioDestinationNode, or a
14/// MediaRecorder.
15///
16/// - MDN documentation: <https://developer.mozilla.org/en-US/docs/Web/API/AudioDestinationNode>
17/// - specification: <https://webaudio.github.io/web-audio-api/#AudioDestinationNode>
18/// - see also: [`BaseAudioContext::destination`]
19///
20/// # Usage
21///
22/// ```no_run
23/// use web_audio_api::context::{BaseAudioContext, AudioContext};
24/// use web_audio_api::node::{AudioNode, AudioScheduledSourceNode};
25///
26/// let context = AudioContext::default();
27///
28/// let mut osc = context.create_oscillator();
29/// osc.connect(&context.destination());
30/// osc.start();
31/// ```
32///
33#[derive(Debug)]
34pub struct AudioDestinationNode {
35    registration: AudioContextRegistration,
36    channel_config: ChannelConfig,
37}
38
39impl AudioNode for AudioDestinationNode {
40    fn registration(&self) -> &AudioContextRegistration {
41        &self.registration
42    }
43
44    fn channel_config(&self) -> &ChannelConfig {
45        &self.channel_config
46    }
47
48    fn number_of_inputs(&self) -> usize {
49        1
50    }
51    fn number_of_outputs(&self) -> usize {
52        1
53    }
54
55    fn set_channel_count(&self, v: usize) {
56        // <https://webaudio.github.io/web-audio-api/#AudioDestinationNode>
57        // numberOfChannels is the number of channels specified when constructing the
58        // OfflineAudioContext. This value may not be changed; a NotSupportedError exception MUST
59        // be thrown if channelCount is changed to a different value.
60        //
61        // <https://webaudio.github.io/web-audio-api/#dom-audionode-channelcount>
62        // The channel count cannot be changed. An InvalidStateError exception MUST be thrown for
63        // any attempt to change the value.
64        //
65        // TODO spec issue: NotSupportedError or InvalidStateError?
66        assert!(
67            !self.registration.context().offline() || v == self.max_channel_count(),
68            "NotSupportedError - not allowed to change OfflineAudioContext destination channel count"
69        );
70
71        // <https://webaudio.github.io/web-audio-api/#dom-audionode-channelcount>
72        // The channel count MUST be between 1 and maxChannelCount. An IndexSizeError exception
73        // MUST be thrown for any attempt to set the count outside this range.
74        assert!(
75            v <= self.max_channel_count(),
76            "IndexSizeError - channel count cannot be greater than maxChannelCount ({})",
77            self.max_channel_count()
78        );
79
80        self.channel_config.set_count(v, self.registration());
81    }
82
83    fn set_channel_count_mode(&self, v: ChannelCountMode) {
84        // <https://webaudio.github.io/web-audio-api/#AudioDestinationNode>
85        // For an OfflineAudioContext, the defaults are [..] channelCountMode: "explicit"
86        //
87        // <https://webaudio.github.io/web-audio-api/#dom-audionode-channelcountmode>
88        // If the AudioDestinationNode is the destination node of an
89        // OfflineAudioContext, then the channel count mode cannot be changed.
90        // An InvalidStateError exception MUST be thrown for any attempt to change the value.
91        assert!(
92            !self.registration.context().offline() || v == ChannelCountMode::Explicit,
93            "InvalidStateError - AudioDestinationNode has channel count mode constraints",
94        );
95
96        self.channel_config.set_count_mode(v, self.registration());
97    }
98}
99
100impl AudioDestinationNode {
101    pub(crate) fn new<C: BaseAudioContext>(context: &C, channel_count: usize) -> Self {
102        context.base().register(move |registration| {
103            let channel_config = AudioNodeOptions {
104                channel_count,
105                channel_count_mode: ChannelCountMode::Explicit,
106                channel_interpretation: ChannelInterpretation::Speakers,
107            }
108            .into();
109            let node = Self {
110                registration,
111                channel_config,
112            };
113            let proc = DestinationRenderer {};
114
115            (node, Box::new(proc))
116        })
117    }
118
119    pub(crate) fn into_channel_config(self) -> ChannelConfig {
120        self.channel_config
121    }
122
123    pub(crate) fn from_raw_parts(
124        registration: AudioContextRegistration,
125        channel_config: ChannelConfig,
126    ) -> Self {
127        Self {
128            registration,
129            channel_config,
130        }
131    }
132    /// The maximum number of channels that the channelCount attribute can be set to (the max
133    /// number of channels that the hardware is capable of supporting).
134    /// <https://www.w3.org/TR/webaudio/#dom-audiodestinationnode-maxchannelcount>
135    pub fn max_channel_count(&self) -> usize {
136        self.registration.context().base().max_channel_count()
137    }
138}
139
140struct DestinationRenderer {}
141
142impl AudioProcessor for DestinationRenderer {
143    fn process(
144        &mut self,
145        inputs: &[AudioRenderQuantum],
146        outputs: &mut [AudioRenderQuantum],
147        _params: AudioParamValues<'_>,
148        _scope: &AudioWorkletGlobalScope,
149    ) -> bool {
150        // single input/output node
151        let input = &inputs[0];
152        let output = &mut outputs[0];
153
154        // just move input to output
155        *output = input.clone();
156
157        true
158    }
159
160    fn has_side_effects(&self) -> bool {
161        true // speaker output
162    }
163}