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