Skip to main content

web_audio_api/context/
mod.rs

1//! The `BaseAudioContext` interface and the `AudioContext` and `OfflineAudioContext` types
2
3use std::{any::Any, ops::Range};
4
5mod base;
6pub use base::*;
7
8mod concrete_base;
9pub use concrete_base::*;
10
11#[cfg(feature = "diagnostics")]
12mod diagnostics;
13#[cfg(feature = "diagnostics")]
14pub use diagnostics::*;
15
16mod offline;
17pub use offline::*;
18
19mod online;
20pub use online::*;
21
22// magic node values
23/// Destination node id is always at index 0
24pub(crate) const DESTINATION_NODE_ID: AudioNodeId = AudioNodeId(0);
25/// listener node id is always at index 1
26const LISTENER_NODE_ID: AudioNodeId = AudioNodeId(1);
27/// listener audio parameters ids are always at index 2 through 10
28const LISTENER_PARAM_IDS: Range<u64> = 2..11;
29/// listener audio parameters ids are always at index 2 through 10
30pub(crate) const LISTENER_AUDIO_PARAM_IDS: [AudioParamId; 9] = [
31    AudioParamId(2),
32    AudioParamId(3),
33    AudioParamId(4),
34    AudioParamId(5),
35    AudioParamId(6),
36    AudioParamId(7),
37    AudioParamId(8),
38    AudioParamId(9),
39    AudioParamId(10),
40];
41
42/// Unique identifier for audio nodes.
43///
44/// Used for internal bookkeeping.
45#[derive(Hash, PartialEq, Eq, Clone, Copy)]
46pub(crate) struct AudioNodeId(pub u64);
47
48impl std::fmt::Debug for AudioNodeId {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        write!(f, "AudioNodeId({})", self.0)
51    }
52}
53
54/// Unique identifier for audio params.
55///
56/// Store these in your `AudioProcessor` to get access to `AudioParam` values.
57#[derive(Debug)]
58pub struct AudioParamId(u64);
59
60// bit contrived, but for type safety only the context mod can access the inner u64
61impl From<&AudioParamId> for AudioNodeId {
62    fn from(i: &AudioParamId) -> Self {
63        Self(i.0)
64    }
65}
66
67/// Describes the current state of the `AudioContext`
68#[derive(Debug, Copy, Clone, PartialEq, Eq)]
69pub enum AudioContextState {
70    /// This context is currently suspended (context time is not proceeding,
71    /// audio hardware may be powered down/released).
72    Suspended,
73    /// Audio is being processed.
74    Running,
75    /// This context has been released, and can no longer be used to process audio.
76    /// All system audio resources have been released.
77    Closed,
78}
79
80impl From<u8> for AudioContextState {
81    fn from(value: u8) -> Self {
82        match value {
83            0 => Self::Suspended,
84            1 => Self::Running,
85            2 => Self::Closed,
86            _ => unreachable!(),
87        }
88    }
89}
90
91/// Handle of the [`AudioNode`](crate::node::AudioNode) to its associated [`BaseAudioContext`].
92///
93/// Only when implementing the AudioNode trait manually, this struct is of any concern.
94///
95/// This object allows for communication with the render thread and dynamic lifetime management.
96// The only way to construct this object is by calling [`BaseAudioContext::register`].
97// This struct should not derive Clone because of the Drop handler.
98pub struct AudioContextRegistration {
99    /// the audio context in which nodes and connections lives
100    context: ConcreteBaseAudioContext,
101    /// identify a specific `AudioNode`
102    id: AudioNodeId,
103}
104
105impl std::fmt::Debug for AudioContextRegistration {
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        f.debug_struct("AudioContextRegistration")
108            .field("id", &self.id)
109            .field(
110                "context",
111                &format!("BaseAudioContext@{}", self.context.address()),
112            )
113            .finish()
114    }
115}
116
117impl AudioContextRegistration {
118    /// Get the audio node id of the registration
119    #[must_use]
120    pub(crate) fn id(&self) -> AudioNodeId {
121        self.id
122    }
123
124    /// Get the [`BaseAudioContext`] concrete type associated with this `AudioContext`
125    #[must_use]
126    pub(crate) fn context(&self) -> &ConcreteBaseAudioContext {
127        &self.context
128    }
129
130    /// Send a message to the corresponding audio processor of this node
131    ///
132    /// The message will be handled by
133    /// [`AudioProcessor::onmessage`](crate::render::AudioProcessor::onmessage).
134    pub(crate) fn post_message<M: Any + Send + 'static>(&self, msg: M) {
135        let wrapped = crate::message::ControlMessage::NodeMessage {
136            id: self.id,
137            msg: llq::Node::new(Box::new(msg)),
138        };
139        self.context.send_control_msg(wrapped);
140    }
141}
142
143impl Drop for AudioContextRegistration {
144    fn drop(&mut self) {
145        self.context.mark_node_dropped(self.id);
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152    use crate::node::AudioNode;
153
154    fn require_send_sync_static<T: Send + Sync + 'static>(_: T) {}
155
156    #[test]
157    fn test_audio_context_registration_traits() {
158        let context = OfflineAudioContext::new(1, 1, 44100.);
159        let registration = context.mock_registration();
160
161        // we want to be able to ship AudioNodes to another thread, so the Registration should be
162        // Send, Sync and 'static
163        require_send_sync_static(registration);
164    }
165
166    #[test]
167    fn test_offline_audio_context_send_sync() {
168        let context = OfflineAudioContext::new(1, 1, 44100.);
169        require_send_sync_static(context);
170    }
171
172    #[test]
173    fn test_online_audio_context_send_sync() {
174        let options = AudioContextOptions {
175            sink_id: "none".into(),
176            ..AudioContextOptions::default()
177        };
178        let context = AudioContext::new(options);
179        require_send_sync_static(context);
180    }
181
182    #[test]
183    fn test_context_equals() {
184        let context = OfflineAudioContext::new(1, 48000, 96000.);
185        let dest = context.destination();
186        assert!(dest.context() == context.base());
187    }
188}