1use crate::prelude::*;
5
6use super::{AudioQueue, AudioRequest, SoundId, clamp_volume, secs};
7use crate::input::frame_seconds;
8
9pub(crate) const OUTSIDE_THE_STACK: usize = usize::MAX;
12
13#[derive(Debug, Clone, PartialEq)]
15pub(super) struct Handle {
16 owner: usize,
18 volume: f32,
20 fade: Option<Fade>,
22}
23
24#[derive(Debug, Clone, PartialEq)]
27struct Fade {
28 from: f32,
29 secs: f32,
30 elapsed: f32,
31}
32
33impl<V> AudioQueue<V> {
34 pub fn start_sound(&mut self, cue: &str) -> SoundId {
40 self.start_sound_at(cue, 1.0)
41 }
42
43 pub fn start_sound_at(&mut self, cue: &str, volume: f32) -> SoundId {
47 self.last_id = self.last_id.wrapping_add(1).max(1);
49 let id = SoundId(self.last_id);
50 if self.added.silences(cue) {
51 return id;
52 }
53 let volume = clamp_volume(volume);
54 let handle = Handle {
55 owner: self.owner,
56 volume,
57 fade: None,
58 };
59 self.handles.insert(id, handle);
60 self.requests.push(AudioRequest::StartSound {
61 id,
62 cue: cue.to_owned(),
63 volume,
64 });
65 id
66 }
67
68 pub fn stop_sound(&mut self, id: SoundId, fade_ms: u32) {
74 let Some(handle) = self.handles.get_mut(&id) else {
75 return;
76 };
77 if fade_ms == 0 {
78 self.stop_now(id);
79 } else if handle.fade.is_none() {
80 handle.fade = Some(Fade {
81 from: handle.volume,
82 secs: secs(fade_ms),
83 elapsed: 0.0,
84 });
85 }
86 }
87
88 pub fn set_sound_volume(&mut self, id: SoundId, volume: f32) {
91 let Some(handle) = self.handles.get_mut(&id).filter(|h| h.fade.is_none()) else {
92 return;
93 };
94 handle.volume = clamp_volume(volume);
95 self.requests.push(AudioRequest::SoundVolume {
96 id,
97 volume: handle.volume,
98 });
99 }
100
101 pub fn sound_is_live(&self, id: SoundId) -> bool {
105 self.handles.contains_key(&id)
106 }
107
108 pub fn advance(&mut self, dt: f32) {
113 let dt = frame_seconds(dt);
114 if dt <= 0.0 {
115 return;
116 }
117 let mut ended = Vec::new();
118 for (&id, handle) in &mut self.handles {
119 let Some(fade) = &mut handle.fade else {
120 continue;
121 };
122 fade.elapsed += dt;
123 if fade.elapsed >= fade.secs {
124 ended.push(id);
125 } else {
126 handle.volume = fade.from * (1.0 - fade.elapsed / fade.secs);
127 self.requests.push(AudioRequest::SoundVolume {
128 id,
129 volume: handle.volume,
130 });
131 }
132 }
133 for id in ended {
134 self.stop_now(id);
135 }
136 }
137
138 pub fn pause_sounds(&mut self) {
148 if self.pauses.is_empty() {
149 self.requests.push(AudioRequest::PauseSounds);
150 }
151 self.pauses.insert(self.owner);
152 }
153
154 pub fn resume_sounds(&mut self) {
157 self.end_pause(self.owner);
158 }
159
160 pub fn sounds_paused(&self) -> bool {
162 !self.pauses.is_empty()
163 }
164
165 pub(crate) fn set_owner(&mut self, depth: usize) {
169 self.owner = depth;
170 }
171
172 pub(crate) fn release(&mut self, depth: usize) {
175 debug_assert_ne!(depth, OUTSIDE_THE_STACK, "no pop releases it");
176 let owned: Vec<SoundId> = self
177 .handles
178 .iter()
179 .filter(|(_, h)| h.owner == depth && h.fade.is_none())
180 .map(|(&id, _)| id)
181 .collect();
182 for id in owned {
183 self.stop_now(id);
184 }
185 self.end_pause(depth);
186 }
187
188 fn end_pause(&mut self, owner: usize) {
189 if self.pauses.remove(&owner) && self.pauses.is_empty() {
190 self.requests.push(AudioRequest::ResumeSounds);
191 }
192 }
193
194 #[cfg(test)]
197 pub(crate) fn owners(&self) -> Vec<(SoundId, usize)> {
198 self.handles.iter().map(|(&id, h)| (id, h.owner)).collect()
199 }
200
201 fn stop_now(&mut self, id: SoundId) {
202 if self.handles.remove(&id).is_some() {
203 self.requests.push(AudioRequest::StopSound { id });
204 }
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211 use crate::toy::Voice;
212
213 type AudioQueue = super::AudioQueue<Voice>;
214 type AudioRequest = super::AudioRequest<Voice>;
215
216 const FRAME: f32 = 1.0 / 60.0;
217
218 fn start(id: u32, volume: f32) -> AudioRequest {
219 AudioRequest::StartSound {
220 id: SoundId(id),
221 cue: "hum".into(),
222 volume,
223 }
224 }
225
226 #[test]
227 fn ids_count_from_one_and_are_the_same_on_every_run() {
228 let run = || {
229 let mut audio = AudioQueue::default();
230 let ids = [audio.start_sound("hum"), audio.start_sound("hum")];
231 audio.stop_sound(ids[0], 0);
232 (ids, audio.start_sound("hum"), audio.take())
233 };
234 let (ids, third, requests) = run();
235 assert_eq!(ids.map(SoundId::get), [1, 2]);
236 assert_eq!(third.get(), 3);
237 assert_eq!(
238 requests,
239 [
240 start(1, 1.0),
241 start(2, 1.0),
242 AudioRequest::StopSound { id: SoundId(1) },
243 start(3, 1.0),
244 ]
245 );
246 assert_eq!(run().2, requests);
247 }
248
249 #[test]
250 fn volumes_are_clamped() {
251 let mut audio = AudioQueue::default();
252 let loud = audio.start_sound_at("hum", 3.0);
253 audio.start_sound_at("hum", f32::NAN);
254 audio.set_sound_volume(loud, -1.0);
255 assert_eq!(
256 audio.take(),
257 [
258 start(1, 1.0),
259 start(2, 0.0),
260 AudioRequest::SoundVolume {
261 id: loud,
262 volume: 0.0
263 },
264 ]
265 );
266 }
267
268 #[test]
269 fn a_stop_without_a_fade_sends_one_stop_and_the_id_is_dead_after() {
270 let mut audio = AudioQueue::default();
271 let id = audio.start_sound("hum");
272 audio.take();
273 audio.stop_sound(id, 0);
274 audio.stop_sound(id, 0);
275 audio.stop_sound(id, 500);
276 audio.set_sound_volume(id, 0.5);
277 audio.advance(1.0);
278 assert_eq!(audio.take(), [AudioRequest::StopSound { id }]);
279 assert!(!audio.sound_is_live(id));
280 }
281
282 #[test]
283 fn an_unknown_id_does_nothing() {
284 let mut audio = AudioQueue::default();
285 audio.stop_sound(SoundId(9), 0);
286 audio.stop_sound(SoundId(9), 100);
287 audio.set_sound_volume(SoundId(9), 0.5);
288 audio.advance(1.0);
289 let got = audio.take();
290 assert!(got.is_empty(), "{got:?}");
291 }
292
293 #[test]
294 fn a_fade_sends_falling_volumes_then_one_stop_on_the_frame_it_ends() {
295 let mut audio = AudioQueue::default();
296 let id = audio.start_sound_at("hum", 0.8);
297 audio.take();
298 audio.stop_sound(id, 500);
299 audio.set_sound_volume(id, 1.0);
301 let mut volumes = Vec::new();
302 let mut frames = 0;
303 let stopped = loop {
304 frames += 1;
305 audio.advance(FRAME);
306 let requests = audio.take();
307 if let [AudioRequest::SoundVolume { volume, .. }] = requests[..] {
308 volumes.push(volume);
309 } else {
310 break requests;
311 }
312 assert!(frames < 100, "the fade never ends");
313 };
314 assert_eq!(stopped, [AudioRequest::StopSound { id }]);
315 assert!((30..=31).contains(&frames), "{frames}");
318 assert!(volumes.windows(2).all(|w| w[1] < w[0]), "{volumes:?}");
319 assert!(volumes[0] < 0.8 && volumes[0] > 0.75, "{volumes:?}");
320 audio.advance(FRAME);
321 let got = audio.take();
322 assert!(got.is_empty(), "{got:?}");
323 }
324
325 #[test]
326 fn bad_frame_times_pass_no_time() {
327 let mut audio = AudioQueue::default();
328 let id = audio.start_sound("hum");
329 audio.stop_sound(id, 100);
330 audio.take();
331 for dt in [f32::NAN, -1.0, f32::INFINITY, 0.0] {
332 audio.advance(dt);
333 }
334 let got = audio.take();
335 assert!(got.is_empty(), "{got:?}");
336 assert!(audio.sound_is_live(id));
337 audio.advance(0.05);
338 assert_eq!(
339 audio.take(),
340 [AudioRequest::SoundVolume { id, volume: 0.5 }]
341 );
342 }
343
344 #[test]
345 fn a_release_stops_its_owners_sounds_and_lets_a_fade_finish() {
346 let mut audio = AudioQueue::default();
347 let below = audio.start_sound("hum");
348 audio.set_owner(1);
349 let [on_top, fading] = [audio.start_sound("hum"), audio.start_sound("hum")];
350 audio.stop_sound(fading, 100);
351 audio.take();
352 audio.release(1);
353 assert_eq!(audio.take(), [AudioRequest::StopSound { id: on_top }]);
354 assert_eq!(audio.owners(), [(below, 0), (fading, 1)]);
355 audio.advance(0.1);
356 assert_eq!(audio.take(), [AudioRequest::StopSound { id: fading }]);
357 assert!(audio.sound_is_live(below));
358 }
359
360 #[test]
361 fn a_screen_pausing_twice_pauses_once_and_its_resume_ends_it() {
362 let mut audio = AudioQueue::default();
363 audio.set_owner(1);
364 audio.pause_sounds();
365 audio.pause_sounds();
366 assert!(audio.sounds_paused());
367 audio.set_owner(0);
369 audio.resume_sounds();
370 assert_eq!(audio.take(), [AudioRequest::PauseSounds]);
371 audio.set_owner(1);
372 audio.resume_sounds();
373 audio.resume_sounds();
374 assert_eq!(audio.take(), [AudioRequest::ResumeSounds]);
375 assert!(!audio.sounds_paused());
376 }
377
378 #[test]
379 fn pauses_by_two_owners_send_one_pause_and_one_resume_when_the_last_ends() {
380 let mut audio = AudioQueue::default();
381 audio.set_owner(1);
382 audio.pause_sounds();
383 audio.set_owner(OUTSIDE_THE_STACK);
384 audio.pause_sounds();
385 audio.set_owner(1);
386 audio.resume_sounds();
387 assert_eq!(audio.take(), [AudioRequest::PauseSounds]);
388 assert!(audio.sounds_paused(), "the outside pause holds");
389 audio.set_owner(OUTSIDE_THE_STACK);
390 audio.resume_sounds();
391 assert_eq!(audio.take(), [AudioRequest::ResumeSounds]);
392 }
393
394 #[test]
395 fn a_release_ends_the_leaving_screens_pause_after_stopping_its_sounds() {
396 let mut audio = AudioQueue::default();
397 audio.set_owner(1);
398 audio.pause_sounds();
399 let menu = audio.start_sound("hum");
400 audio.take();
401 audio.release(2);
402 let got = audio.take();
403 assert!(got.is_empty(), "{got:?}");
404 audio.release(1);
405 assert_eq!(
406 audio.take(),
407 [
408 AudioRequest::StopSound { id: menu },
409 AudioRequest::ResumeSounds
410 ]
411 );
412 assert!(!audio.sounds_paused());
413 }
414}