Skip to main content

euv_engine/audio/
impl.rs

1use super::*;
2
3/// Implements audio context management for `GameAudioContext`.
4impl GameAudioContext {
5    /// Creates a new audio context with default master volume.
6    ///
7    /// # Returns
8    ///
9    /// - `Option<GameAudioContext>` - The new audio context, or `None` when
10    ///   the browser does not expose a WebAudio `AudioContext`.
11    pub fn create() -> Option<GameAudioContext> {
12        let Ok(context) = AudioContext::new() else {
13            return None;
14        };
15        let Ok(master_gain) = context.create_gain() else {
16            return None;
17        };
18        let _: Result<AudioNode, JsValue> =
19            master_gain.connect_with_audio_node(&context.destination());
20        master_gain.gain().set_value(AUDIO_DEFAULT_VOLUME as f32);
21        Some(GameAudioContext::new(context, master_gain))
22    }
23
24    /// Sets the master volume for all audio output.
25    ///
26    /// # Arguments
27    ///
28    /// - `f64` - The volume level in the range 0.0 to 1.0.
29    pub fn apply_master_volume(&self, volume: f64) {
30        let clamped: f64 = Numeric::clamp(volume, 0.0, 1.0);
31        self.get_master_gain().gain().set_value(clamped as f32);
32    }
33
34    /// Resumes the audio context if it was suspended.
35    pub fn resume(&self) {
36        let _: Result<Promise, JsValue> = self.get_context().resume();
37    }
38
39    /// Suspends the audio context, temporarily halting all audio processing.
40    pub fn suspend(&self) {
41        let _: Result<Promise, JsValue> = self.get_context().suspend();
42    }
43
44    /// Closes the audio context and releases all resources.
45    pub fn close(&self) {
46        let _: Result<Promise, JsValue> = self.get_context().close();
47    }
48
49    /// Returns the current sample rate of the audio context in Hz.
50    ///
51    /// # Returns
52    ///
53    /// - `f64` - The sample rate.
54    pub fn sample_rate(&self) -> f64 {
55        self.get_context().sample_rate() as f64
56    }
57
58    /// Returns the current playback time in seconds.
59    ///
60    /// # Returns
61    ///
62    /// - `f64` - The current time.
63    pub fn current_time(&self) -> f64 {
64        self.get_context().current_time()
65    }
66}
67
68/// Implements playback control for `AudioClip`.
69impl AudioClip {
70    /// Creates a new audio clip from a decoded buffer.
71    ///
72    /// A clip carries no name: playback is driven by `play` / `stop`, which
73    /// address a clip by identity rather than by label, so a name field would
74    /// only ever be stored and never read.
75    ///
76    /// # Arguments
77    ///
78    /// - `AudioBuffer` - The decoded audio data.
79    ///
80    /// # Returns
81    ///
82    /// - `AudioClip` - The new clip.
83    pub fn create(buffer: AudioBuffer) -> AudioClip {
84        AudioClip::new(
85            buffer,
86            AudioPlayState::Stopped,
87            AUDIO_DEFAULT_LOOP,
88            AUDIO_DEFAULT_VOLUME,
89            AUDIO_DEFAULT_PLAYBACK_RATE,
90        )
91    }
92
93    /// Plays this clip through the given audio context.
94    ///
95    /// Creates a new `AudioBufferSourceNode`, connects it through a gain node
96    /// to the master gain, and starts playback. If already playing, does nothing.
97    ///
98    /// # Arguments
99    ///
100    /// - `&GameAudioContext` - The audio context to play through.
101    pub fn play(&mut self, audio_context: &GameAudioContext) {
102        if self.get_state() == AudioPlayState::Playing {
103            return;
104        }
105        let Ok(source) = audio_context.get_context().create_buffer_source() else {
106            return;
107        };
108        source.set_buffer(Some(self.get_buffer()));
109        source.set_loop(self.get_looping());
110        source
111            .playback_rate()
112            .set_value(self.get_playback_rate() as f32);
113        let Ok(gain) = audio_context.get_context().create_gain() else {
114            return;
115        };
116        gain.gain().set_value(self.get_volume() as f32);
117        let _: Result<AudioNode, JsValue> = source.connect_with_audio_node(&gain);
118        let _: Result<AudioNode, JsValue> =
119            gain.connect_with_audio_node(audio_context.get_master_gain());
120        let _: Result<(), JsValue> = source.start();
121        self.set_source_node(Some(source));
122        self.set_state(AudioPlayState::Playing);
123    }
124
125    /// Stops playback of this clip and releases the source node.
126    pub fn stop(&mut self) {
127        if let Some(source) = self.get_mut_source_node().take() {
128            let scheduled: &AudioScheduledSourceNode = source.as_ref();
129            let _: Result<(), JsValue> = scheduled.stop_with_when(0.0);
130            let _: Result<(), JsValue> = source.disconnect();
131        }
132        self.set_state(AudioPlayState::Stopped);
133    }
134
135    /// Sets whether the clip should loop.
136    ///
137    /// # Arguments
138    ///
139    /// - `bool` - True to enable looping.
140    pub fn update_looping(&mut self, looping: bool) {
141        self.set_looping(looping);
142        if let Some(source) = self.get_mut_source_node() {
143            source.set_loop(looping);
144        }
145    }
146
147    /// Sets the volume of this clip.
148    ///
149    /// # Arguments
150    ///
151    /// - `f64` - The volume in the range 0.0 to 1.0.
152    pub fn update_volume(&mut self, volume: f64) {
153        self.set_volume(Numeric::clamp(volume, 0.0, 1.0));
154    }
155
156    /// Sets the playback rate multiplier.
157    ///
158    /// # Arguments
159    ///
160    /// - `f64` - The playback rate (1.0 = normal speed).
161    pub fn update_playback_rate(&mut self, rate: f64) {
162        self.set_playback_rate(rate);
163        if let Some(source) = self.get_mut_source_node() {
164            source.playback_rate().set_value(rate as f32);
165        }
166    }
167
168    /// Returns the duration of the audio buffer in seconds.
169    ///
170    /// # Returns
171    ///
172    /// - `f64` - The duration.
173    pub fn duration(&self) -> f64 {
174        self.get_buffer().duration()
175    }
176
177    /// Returns the number of channels in the audio buffer.
178    ///
179    /// # Returns
180    ///
181    /// - `u32` - The channel count.
182    pub fn channel_count(&self) -> u32 {
183        self.get_buffer().number_of_channels()
184    }
185}