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