web_audio_api/node/audio_node.rs
1use std::sync::{Arc, Mutex};
2
3use crate::context::{AudioContextRegistration, ConcreteBaseAudioContext};
4use crate::events::{ErrorEvent, EventHandler, EventPayload, EventType};
5use crate::message::ControlMessage;
6
7/// How channels must be matched between the node's inputs and outputs.
8#[derive(Copy, Clone, PartialEq, Eq, Debug)]
9pub enum ChannelCountMode {
10 /// `computedNumberOfChannels` is the maximum of the number of channels of all connections to an
11 /// input. In this mode channelCount is ignored.
12 Max,
13 /// `computedNumberOfChannels` is determined as for "max" and then clamped to a maximum value of
14 /// the given channelCount.
15 ClampedMax,
16 /// `computedNumberOfChannels` is the exact value as specified by the channelCount.
17 Explicit,
18}
19
20impl From<u32> for ChannelCountMode {
21 fn from(i: u32) -> Self {
22 use ChannelCountMode::*;
23
24 match i {
25 0 => Max,
26 1 => ClampedMax,
27 2 => Explicit,
28 _ => unreachable!(),
29 }
30 }
31}
32
33/// The meaning of the channels, defining how audio up-mixing and down-mixing will happen.
34#[derive(Copy, Clone, PartialEq, Eq, Debug)]
35pub enum ChannelInterpretation {
36 Speakers,
37 Discrete,
38}
39
40impl From<u32> for ChannelInterpretation {
41 fn from(i: u32) -> Self {
42 use ChannelInterpretation::*;
43
44 match i {
45 0 => Speakers,
46 1 => Discrete,
47 _ => unreachable!(),
48 }
49 }
50}
51
52/// Options that can be used in constructing all AudioNodes.
53#[derive(Clone, Debug)]
54pub struct AudioNodeOptions {
55 /// Desired number of channels for the [`AudioNode::channel_count`] attribute.
56 pub channel_count: usize,
57 /// Desired mode for the [`AudioNode::channel_count_mode`] attribute.
58 pub channel_count_mode: ChannelCountMode,
59 /// Desired mode for the [`AudioNode::channel_interpretation`] attribute.
60 pub channel_interpretation: ChannelInterpretation,
61}
62
63impl Default for AudioNodeOptions {
64 fn default() -> Self {
65 Self {
66 channel_count: 2,
67 channel_count_mode: ChannelCountMode::Max,
68 channel_interpretation: ChannelInterpretation::Speakers,
69 }
70 }
71}
72
73/// Config for up/down-mixing of input channels for audio nodes
74///
75/// Only when implementing the [`AudioNode`] trait manually, this struct is of any concern. The
76/// methods `set_channel_count`, `set_channel_count_mode` and `set_channel_interpretation` from the
77/// audio node interface will use this struct to sync the required info to the render thread.
78///
79/// The only way to construct an instance is with [`AudioNodeOptions`]
80///
81/// ```
82/// use web_audio_api::node::{AudioNodeOptions, ChannelConfig, ChannelInterpretation, ChannelCountMode};
83///
84/// let opts = AudioNodeOptions {
85/// channel_count: 1,
86/// channel_count_mode: ChannelCountMode::Explicit,
87/// channel_interpretation: ChannelInterpretation::Discrete,
88/// };
89/// let _: ChannelConfig = opts.into();
90#[derive(Clone)]
91pub struct ChannelConfig {
92 inner: Arc<Mutex<ChannelConfigInner>>,
93}
94
95#[derive(Debug, Clone)]
96pub(crate) struct ChannelConfigInner {
97 pub(crate) count: usize,
98 pub(crate) count_mode: ChannelCountMode,
99 pub(crate) interpretation: ChannelInterpretation,
100}
101
102impl Default for ChannelConfig {
103 fn default() -> Self {
104 AudioNodeOptions::default().into()
105 }
106}
107
108impl std::fmt::Debug for ChannelConfig {
109 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110 f.debug_struct("ChannelConfig")
111 .field("count", &self.count())
112 .field("count_mode", &self.count_mode())
113 .field("interpretation", &self.interpretation())
114 .finish()
115 }
116}
117
118// All methods on this struct are marked `pub(crate)` because we don't want outside users to be
119// able to change the values directly. These methods are only accessible via the AudioNode
120// interface, so AudioNode's that have channel count/mode constraints should be able to assert
121// those.
122//
123// Uses the canonical ordering for handover of values, i.e. `Acquire` on load and `Release` on
124// store.
125impl ChannelConfig {
126 /// Represents an enumerated value describing the way channels must be matched between the
127 /// node's inputs and outputs.
128 pub(crate) fn count_mode(&self) -> ChannelCountMode {
129 self.inner.lock().unwrap().count_mode
130 }
131
132 pub(super) fn set_count_mode(
133 &self,
134 v: ChannelCountMode,
135 registration: &AudioContextRegistration,
136 ) {
137 let mut guard = self.inner.lock().unwrap();
138 guard.count_mode = v;
139
140 let message = ControlMessage::SetChannelCountMode {
141 id: registration.id(),
142 mode: v,
143 };
144 registration.context().send_control_msg(message);
145
146 drop(guard); // drop guard after sending message to prevent out of order arrivals on
147 // concurrent access
148 }
149
150 /// Represents an enumerated value describing the meaning of the channels. This interpretation
151 /// will define how audio up-mixing and down-mixing will happen.
152 pub(crate) fn interpretation(&self) -> ChannelInterpretation {
153 self.inner.lock().unwrap().interpretation
154 }
155
156 pub(super) fn set_interpretation(
157 &self,
158 v: ChannelInterpretation,
159 registration: &AudioContextRegistration,
160 ) {
161 let mut guard = self.inner.lock().unwrap();
162 guard.interpretation = v;
163
164 let message = ControlMessage::SetChannelInterpretation {
165 id: registration.id(),
166 interpretation: v,
167 };
168 registration.context().send_control_msg(message);
169
170 drop(guard); // drop guard after sending message to prevent out of order arrivals on
171 // concurrent access
172 }
173
174 /// Represents an integer used to determine how many channels are used when up-mixing and
175 /// down-mixing connections to any inputs to the node.
176 pub(crate) fn count(&self) -> usize {
177 self.inner.lock().unwrap().count
178 }
179
180 pub(super) fn set_count(&self, v: usize, registration: &AudioContextRegistration) {
181 crate::assert_valid_number_of_channels(v);
182
183 let mut guard = self.inner.lock().unwrap();
184 guard.count = v;
185
186 let message = ControlMessage::SetChannelCount {
187 id: registration.id(),
188 count: v,
189 };
190 registration.context().send_control_msg(message);
191
192 drop(guard); // drop guard after sending message to prevent out of order arrivals on
193 // concurrent access
194 }
195
196 pub(crate) fn inner(&self) -> ChannelConfigInner {
197 self.inner.lock().unwrap().clone()
198 }
199}
200
201impl From<AudioNodeOptions> for ChannelConfig {
202 fn from(opts: AudioNodeOptions) -> Self {
203 crate::assert_valid_number_of_channels(opts.channel_count);
204
205 let inner = ChannelConfigInner {
206 count: opts.channel_count,
207 count_mode: opts.channel_count_mode,
208 interpretation: opts.channel_interpretation,
209 };
210 Self {
211 inner: Arc::new(Mutex::new(inner)),
212 }
213 }
214}
215
216/// This interface represents audio sources, the audio destination, and intermediate processing
217/// modules.
218///
219/// These modules can be connected together to form processing graphs for rendering audio
220/// to the audio hardware. Each node can have inputs and/or outputs.
221///
222/// Note that the AudioNode is typically constructed together with an `AudioWorkletProcessor`
223/// (the object that lives the render thread). See the [`crate::worklet`] mod.
224pub trait AudioNode {
225 /// Handle of the associated [`BaseAudioContext`](crate::context::BaseAudioContext).
226 ///
227 /// Only when implementing the AudioNode trait manually, this struct is of any concern.
228 fn registration(&self) -> &AudioContextRegistration;
229
230 /// Config for up/down-mixing of input channels for this node.
231 ///
232 /// Only when implementing the [`AudioNode`] trait manually, this struct is of any concern.
233 fn channel_config(&self) -> &ChannelConfig;
234
235 /// The [`BaseAudioContext`](crate::context::BaseAudioContext) concrete type which owns this
236 /// AudioNode.
237 fn context(&self) -> &ConcreteBaseAudioContext {
238 self.registration().context()
239 }
240
241 /// Connect the output of this AudioNode to the input of another node.
242 ///
243 /// # Panics
244 ///
245 /// This function will panic when
246 /// - the AudioContext of the source and destination does not match
247 fn connect<'a>(&self, dest: &'a dyn AudioNode) -> &'a dyn AudioNode {
248 self.connect_from_output_to_input(dest, 0, 0)
249 }
250
251 /// Connect a specific output of this AudioNode to a specific input of another node.
252 ///
253 /// # Panics
254 ///
255 /// This function will panic when
256 /// - the AudioContext of the source and destination does not match
257 /// - if the input port is out of bounds for the destination node
258 /// - if the output port is out of bounds for the source node
259 fn connect_from_output_to_input<'a>(
260 &self,
261 dest: &'a dyn AudioNode,
262 output: usize,
263 input: usize,
264 ) -> &'a dyn AudioNode {
265 assert!(
266 self.context() == dest.context(),
267 "InvalidAccessError - Attempting to connect nodes from different contexts",
268 );
269
270 assert!(
271 self.number_of_outputs() > output,
272 "IndexSizeError - output port {} is out of bounds",
273 output
274 );
275
276 assert!(
277 dest.number_of_inputs() > input,
278 "IndexSizeError - input port {} is out of bounds",
279 input
280 );
281
282 self.context().connect(
283 self.registration().id(),
284 dest.registration().id(),
285 output,
286 input,
287 );
288 dest
289 }
290
291 /// Disconnects all outgoing connections from the AudioNode.
292 fn disconnect(&self) {
293 self.context()
294 .disconnect(self.registration().id(), None, None, None);
295 }
296
297 /// Disconnects all outputs of the AudioNode that go to a specific destination AudioNode.
298 ///
299 /// # Panics
300 ///
301 /// This function will panic when
302 /// - the AudioContext of the source and destination does not match
303 /// - the source node was not connected to the destination node
304 fn disconnect_dest(&self, dest: &dyn AudioNode) {
305 assert!(
306 self.context() == dest.context(),
307 "InvalidAccessError - Attempting to disconnect nodes from different contexts"
308 );
309
310 self.context().disconnect(
311 self.registration().id(),
312 None,
313 Some(dest.registration().id()),
314 None,
315 );
316 }
317
318 /// Disconnects all outgoing connections at the given output port from the AudioNode.
319 ///
320 /// # Panics
321 ///
322 /// This function will panic when
323 /// - if the output port is out of bounds for this node
324 fn disconnect_output(&self, output: usize) {
325 assert!(
326 self.number_of_outputs() > output,
327 "IndexSizeError - output port {} is out of bounds",
328 output
329 );
330
331 self.context()
332 .disconnect(self.registration().id(), Some(output), None, None);
333 }
334
335 /// Disconnects a specific output of the AudioNode to a specific destination AudioNode
336 ///
337 /// # Panics
338 ///
339 /// This function will panic when
340 /// - the AudioContext of the source and destination does not match
341 /// - if the output port is out of bounds for the source node
342 /// - the source node was not connected to the destination node
343 fn disconnect_dest_from_output(&self, dest: &dyn AudioNode, output: usize) {
344 assert!(
345 self.context() == dest.context(),
346 "InvalidAccessError - Attempting to disconnect nodes from different contexts"
347 );
348
349 assert!(
350 self.number_of_outputs() > output,
351 "IndexSizeError - output port {} is out of bounds",
352 output
353 );
354
355 self.context().disconnect(
356 self.registration().id(),
357 Some(output),
358 Some(dest.registration().id()),
359 None,
360 );
361 }
362
363 /// Disconnects a specific output of the AudioNode to a specific input of some destination
364 /// AudioNode
365 ///
366 /// # Panics
367 ///
368 /// This function will panic when
369 /// - the AudioContext of the source and destination does not match
370 /// - if the input port is out of bounds for the destination node
371 /// - if the output port is out of bounds for the source node
372 /// - the source node was not connected to the destination node
373 fn disconnect_dest_from_output_to_input(
374 &self,
375 dest: &dyn AudioNode,
376 output: usize,
377 input: usize,
378 ) {
379 assert!(
380 self.context() == dest.context(),
381 "InvalidAccessError - Attempting to disconnect nodes from different contexts"
382 );
383
384 assert!(
385 self.number_of_outputs() > output,
386 "IndexSizeError - output port {} is out of bounds",
387 output
388 );
389
390 assert!(
391 dest.number_of_inputs() > input,
392 "IndexSizeError - input port {} is out of bounds",
393 input
394 );
395
396 self.context().disconnect(
397 self.registration().id(),
398 Some(output),
399 Some(dest.registration().id()),
400 Some(input),
401 );
402 }
403
404 /// The number of inputs feeding into the AudioNode. For source nodes, this will be 0.
405 fn number_of_inputs(&self) -> usize;
406
407 /// The number of outputs coming out of the AudioNode.
408 fn number_of_outputs(&self) -> usize;
409
410 /// Represents an enumerated value describing the way channels must be matched between the
411 /// node's inputs and outputs.
412 fn channel_count_mode(&self) -> ChannelCountMode {
413 self.channel_config().count_mode()
414 }
415
416 /// Update the `channel_count_mode` attribute
417 fn set_channel_count_mode(&self, v: ChannelCountMode) {
418 self.channel_config().set_count_mode(v, self.registration())
419 }
420
421 /// Represents an enumerated value describing the meaning of the channels. This interpretation
422 /// will define how audio up-mixing and down-mixing will happen.
423 fn channel_interpretation(&self) -> ChannelInterpretation {
424 self.channel_config().interpretation()
425 }
426
427 /// Update the `channel_interpretation` attribute
428 fn set_channel_interpretation(&self, v: ChannelInterpretation) {
429 self.channel_config()
430 .set_interpretation(v, self.registration())
431 }
432 /// Represents an integer used to determine how many channels are used when up-mixing and
433 /// down-mixing connections to any inputs to the node.
434 fn channel_count(&self) -> usize {
435 self.channel_config().count()
436 }
437
438 /// Update the `channel_count` attribute
439 fn set_channel_count(&self, v: usize) {
440 self.channel_config().set_count(v, self.registration())
441 }
442
443 /// Register callback to run when an unhandled exception occurs in the audio processor.
444 ///
445 /// Note that once a unhandled exception is thrown, the processor will output silence throughout its lifetime.
446 ///
447 /// Only a single event handler is active at any time. Calling this method multiple times will
448 /// override the previous event handler.
449 fn set_onprocessorerror(&self, callback: Box<dyn FnOnce(ErrorEvent) + Send + 'static>) {
450 let callback = move |v| match v {
451 EventPayload::ProcessorError(v) => callback(v),
452 _ => unreachable!(),
453 };
454
455 self.context().set_event_handler(
456 EventType::ProcessorError(self.registration().id()),
457 EventHandler::Once(Box::new(callback)),
458 );
459 }
460
461 /// Unset the callback to run when an unhandled exception occurs in the audio processor.
462 fn clear_onprocessorerror(&self) {
463 self.context()
464 .clear_event_handler(EventType::ProcessorError(self.registration().id()));
465 }
466}