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}