1pub(crate) mod voice;
28
29use alloc::collections::{BTreeMap, BTreeSet};
30use alloc::format;
31use core::fmt;
32use core::future::Future;
33
34use shuyi_assets::audio::{AudioManifest, Files};
35use shuyi_ui::audio::{MusicCommand, SharedSamples, SoundId};
36
37use self::voice::Voices;
38use super::{Audio, AudioFrame, SoundRequest};
39use crate::alloc_prelude::*;
40use crate::platform::Millis;
41
42pub const RATE_ONE: u32 = 1 << 16;
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub struct Unsupported;
50
51impl fmt::Display for Unsupported {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 f.write_str("this backend doesn't have this feature")
54 }
55}
56
57impl core::error::Error for Unsupported {}
58
59pub trait Backend {
65 type Sound;
67 type Loading;
69 type Playing;
71
72 fn load_sound(&mut self, bytes: &[u8]) -> impl Future<Output = Result<Self::Sound, String>>;
74 fn load_music(&mut self, path: &str) -> Self::Loading;
76 fn poll_music(&mut self, loading: &mut Self::Loading) -> Option<Result<Self::Sound, String>>;
78 fn length(&self, sound: &Self::Sound) -> Option<f64>;
81 fn play(&mut self, sound: &Self::Sound, volume: f32, looped: bool) -> Self::Playing;
83 fn set_volume(&mut self, sound: &Self::Sound, volume: f32);
85 fn stop(&mut self, sound: &Self::Sound);
87 fn set_playing_volume(&mut self, playing: &Self::Playing, volume: f32);
89 fn stop_playing(&mut self, playing: Self::Playing);
91
92 fn load_sound_file(
97 &mut self,
98 file: &str,
99 files: &Files<'_>,
100 ) -> impl Future<Output = Result<Self::Sound, String>> {
101 let bytes = (files.sound)(file);
102 async move {
103 match bytes {
104 Some(bytes) => self.load_sound(&bytes).await,
105 None => Err("not in the asset bundle".to_owned()),
106 }
107 }
108 }
109
110 fn pause(&mut self, playing: &Self::Playing) -> Result<(), Unsupported> {
113 let _ = playing;
114 Err(Unsupported)
115 }
116
117 fn resume(&mut self, playing: &Self::Playing) -> Result<(), Unsupported> {
119 let _ = playing;
120 Err(Unsupported)
121 }
122
123 fn set_rate(&mut self, sound: &Self::Sound, rate: u32) -> Result<(), Unsupported> {
127 let _ = (sound, rate);
128 Err(Unsupported)
129 }
130
131 fn add_sound(&mut self, samples: &SharedSamples) -> Result<Self::Sound, String> {
135 let _ = samples;
136 Err(Unsupported.to_string())
137 }
138
139 fn remove_sound(&mut self, sound: Self::Sound) {
143 self.stop(&sound);
144 }
145
146 fn hold_output(&mut self, on: bool) {
149 let _ = on;
150 }
151
152 fn warn(&mut self, _line: &str) {}
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
159enum Feature {
160 Pause,
161 Rate,
162}
163
164impl Feature {
165 const fn warning(self) -> &'static str {
167 match self {
168 Self::Pause => "this backend can't pause sounds: they are muted instead",
169 Self::Rate => "this backend can't change a sound's rate: it plays as recorded",
170 }
171 }
172}
173
174#[derive(Debug, Default)]
176pub(crate) struct Provided {
177 warned: BTreeSet<Feature>,
179 mutes: bool,
181}
182
183impl Provided {
184 fn call(
187 &mut self,
188 feature: Feature,
189 result: Result<(), Unsupported>,
190 warnings: &mut Vec<String>,
191 ) -> bool {
192 if result.is_ok() {
193 return true;
194 }
195 if self.warned.insert(feature) {
196 warnings.push(feature.warning().to_owned());
197 }
198 self.mutes |= feature == Feature::Pause;
199 false
200 }
201
202 pub(crate) fn hold<B: Backend>(
204 &mut self,
205 backend: &mut B,
206 playing: &B::Playing,
207 on: bool,
208 warnings: &mut Vec<String>,
209 ) -> bool {
210 if self.mutes {
211 return false;
212 }
213 let result = if on {
214 backend.pause(playing)
215 } else {
216 backend.resume(playing)
217 };
218 self.call(Feature::Pause, result, warnings)
219 }
220}
221
222pub struct Player<B: Backend> {
224 manifest: AudioManifest,
225 sounds: BTreeMap<String, Vec<B::Sound>>,
227 added: BTreeMap<String, B::Sound>,
229 tracks: BTreeMap<String, Track<B>>,
231 abandoned: Vec<B::Loading>,
234 music_dir: String,
236 voices: Voices<B>,
238 rng: VariantRng,
239 warnings: Vec<String>,
241 music_volume: f32,
244 sound_volume: f32,
245 handled: BTreeMap<SoundId, Handled<B>>,
247 voice_volume: f32,
249 suspended: bool,
251 provided: Provided,
253 now: Option<f64>,
255}
256
257struct Handled<B: Backend> {
260 playing: B::Playing,
261 cue_volume: f32,
263 volume: f32,
265 paused: bool,
267}
268
269impl<B: Backend> Handled<B> {
270 fn level(&self, sound: f32, suspended: bool, mutes: bool) -> f32 {
274 if suspended || (self.paused && mutes) {
275 0.0
276 } else {
277 self.cue_volume * self.volume * sound
278 }
279 }
280}
281
282struct Track<B: Backend> {
283 state: TrackState<B>,
284 playing: bool,
286 playback: Option<B::Playing>,
288 started_at: Option<f64>,
291 held_at: Option<f64>,
294 gain: f32,
296 volume: f32,
298 looped: bool,
299}
300
301enum TrackState<B: Backend> {
302 Loading(B::Loading),
303 Ready(B::Sound),
304 Failed,
305}
306
307impl<B: Backend> Player<B> {
308 pub async fn load(
313 backend: &mut B,
314 manifest: AudioManifest,
315 files: &Files<'_>,
316 music_dir: String,
317 voice_dir: String,
318 seed: u64,
319 ) -> Self {
320 let mut warnings = Vec::new();
321 let mut sounds = BTreeMap::new();
322 for (cue, sound) in &manifest.sounds {
323 let mut variants = Vec::new();
324 for file in &sound.files {
325 match backend.load_sound_file(file, files).await {
326 Ok(s) => variants.push(s),
327 Err(e) => {
328 let shown = (files.sound_shown)(file);
329 warnings.push(format!("sound \"{cue}\": {shown}: {e}"));
330 }
331 }
332 }
333 sounds.insert(cue.clone(), variants);
334 }
335 Self {
336 manifest,
337 sounds,
338 added: BTreeMap::new(),
339 tracks: BTreeMap::new(),
340 abandoned: Vec::new(),
341 music_dir,
342 voices: Voices::new(voice_dir),
343 rng: VariantRng::new(seed),
344 warnings,
345 music_volume: 1.0,
346 sound_volume: 1.0,
347 handled: BTreeMap::new(),
348 voice_volume: 1.0,
349 suspended: false,
350 provided: Provided::default(),
351 now: None,
352 }
353 }
354
355 pub fn set_volumes(&mut self, backend: &mut B, music: f32, sound: f32) {
359 if (sound - self.sound_volume).abs() > f32::EPSILON {
360 self.sound_volume = sound;
361 self.set_handled_volumes(backend);
362 }
363 if (music - self.music_volume).abs() <= f32::EPSILON {
364 return;
365 }
366 self.music_volume = music;
367 self.set_track_volumes(backend);
368 }
369
370 fn music_level(&self) -> f32 {
373 if self.suspended {
374 0.0
375 } else {
376 self.music_volume
377 }
378 }
379
380 fn set_track_volumes(&self, backend: &mut B) {
383 let music = self.music_level();
384 for track in self.tracks.values() {
385 if let (true, TrackState::Ready(sound)) = (track.playing, &track.state) {
386 backend.set_volume(sound, track.volume * track.gain * music);
387 }
388 }
389 }
390
391 fn level_of(&self, handled: &Handled<B>) -> f32 {
393 handled.level(self.sound_volume, self.suspended, self.provided.mutes)
394 }
395
396 fn set_handled_volumes(&self, backend: &mut B) {
398 for handled in self.handled.values() {
399 backend.set_playing_volume(&handled.playing, self.level_of(handled));
400 }
401 }
402
403 fn hold_all(&mut self, backend: &mut B, on: bool) {
407 let (provided, warnings) = (&mut self.provided, &mut self.warnings);
408 for handled in self.handled.values().filter(|handled| !handled.paused) {
409 provided.hold(backend, &handled.playing, on, warnings);
410 }
411 for track in self.tracks.values_mut() {
412 if let (true, Some(playback)) = (track.playing, &track.playback)
413 && provided.hold(backend, playback, on, warnings)
414 && on
415 {
416 track.held_at = self.now;
417 }
418 }
419 self.voices.hold(backend, on, self.now, provided, warnings);
420 }
421
422 pub fn suspend(&mut self, backend: &mut B) {
427 if self.suspended {
428 return;
429 }
430 self.suspended = true;
431 self.hold_all(backend, true);
432 if self.provided.mutes {
433 self.set_handled_volumes(backend);
434 self.set_track_volumes(backend);
435 self.voices.set_volume(backend, 0.0);
436 }
437 }
438
439 pub fn resume(&mut self, backend: &mut B) {
443 if !self.suspended {
444 return;
445 }
446 self.suspended = false;
447 self.hold_all(backend, false);
448 self.set_handled_volumes(backend);
449 self.set_track_volumes(backend);
450 self.voices.set_volume(backend, self.voice_volume);
451 }
452
453 pub fn play(
458 &mut self,
459 backend: &mut B,
460 requests: &[SoundRequest],
461 music: &[MusicCommand],
462 now: f64,
463 ) {
464 self.now = Some(now);
465 if !self.suspended {
466 self.release_clocks(now);
467 }
468 for request in requests {
469 self.sound(backend, request);
470 self.voices
471 .request(backend, request, now, &mut self.abandoned);
472 }
473 for command in music {
474 self.music(backend, command, now);
475 }
476 self.poll(backend, now);
477 }
478
479 fn release_clocks(&mut self, now: f64) {
481 for track in self.tracks.values_mut() {
482 if let (Some(at), Some(started_at)) = (track.held_at.take(), &mut track.started_at) {
483 *started_at += (now - at).max(0.0);
484 }
485 }
486 self.voices.release_clock(now);
487 }
488
489 pub fn music_playing(&self, now: f64) -> Option<(&str, f64)> {
497 self.tracks
498 .iter()
499 .filter_map(|(cue, track)| Some((cue.as_str(), track.started_at?, track.held_at)))
500 .max_by(|a, b| a.1.total_cmp(&b.1))
501 .map(|(cue, started_at, held_at)| (cue, (held_at.unwrap_or(now) - started_at).max(0.0)))
502 }
503
504 pub fn voice_playing(&self, now: f64) -> Option<&str> {
510 self.voices.sounding(now)
511 }
512
513 fn sound(&mut self, backend: &mut B, request: &SoundRequest) {
515 match request {
516 SoundRequest::Sound { cue, volume } => {
517 self.play_sound(backend, cue, *volume, false);
520 }
521 SoundRequest::Start {
522 id, cue, volume, ..
523 } => {
524 self.start_sound(backend, *id, cue, *volume);
525 }
526 SoundRequest::Stop { id } => {
527 if let Some(handled) = self.handled.remove(id) {
528 backend.stop_playing(handled.playing);
529 }
530 }
531 SoundRequest::Volume { id, volume } => {
532 if let Some(handled) = self.handled.get_mut(id) {
533 handled.volume = *volume;
534 let (sound, suspended) = (self.sound_volume, self.suspended);
535 let level = handled.level(sound, suspended, self.provided.mutes);
536 backend.set_playing_volume(&handled.playing, level);
537 }
538 }
539 SoundRequest::PauseSounds => self.pause_handled(backend, true),
540 SoundRequest::ResumeSounds => self.pause_handled(backend, false),
541 SoundRequest::Add { cue, samples, .. } => self.add_sound(backend, cue, samples),
542 SoundRequest::Remove { cue } => {
543 if let Some(sound) = self.added.remove(cue) {
544 backend.remove_sound(sound);
545 }
546 }
547 _ => {}
548 }
549 }
550
551 fn add_sound(&mut self, backend: &mut B, cue: &str, samples: &SharedSamples) {
553 if let Some(old) = self.added.remove(cue) {
554 backend.remove_sound(old);
555 }
556 match backend.add_sound(samples) {
557 Ok(sound) => {
558 self.added.insert(cue.to_owned(), sound);
559 }
560 Err(e) => self
561 .warnings
562 .push(format!("sound \"{cue}\": can't be added: {e}")),
563 }
564 }
565
566 fn play_sound(
570 &mut self,
571 backend: &mut B,
572 cue: &str,
573 volume: f32,
574 looped: bool,
575 ) -> Option<B::Playing> {
576 let manifest = self.manifest.sounds.get(cue);
577 let (variants, own) = match (manifest, self.sounds.get(cue), self.added.get(cue)) {
578 (Some(def), Some(variants), _) => (variants.as_slice(), percent(def.volume)),
579 (None, _, Some(added)) => (core::slice::from_ref(added), 1.0),
581 _ => {
582 self.warnings.push(format!("unknown sound cue \"{cue}\""));
583 return None;
584 }
585 };
586 if variants.is_empty() {
587 return None; }
589 let sound = &variants[self.rng.below(variants.len())];
590 let volume = own * volume * self.sound_volume;
591 Some(backend.play(sound, volume, looped))
592 }
593
594 fn pause_handled(&mut self, backend: &mut B, paused: bool) {
597 let (sound, suspended) = (self.sound_volume, self.suspended);
598 let (provided, warnings) = (&mut self.provided, &mut self.warnings);
599 for handled in self.handled.values_mut() {
600 if handled.paused != paused {
601 handled.paused = paused;
602 if !suspended {
603 provided.hold(backend, &handled.playing, paused, warnings);
604 }
605 if provided.mutes {
606 let level = handled.level(sound, suspended, true);
607 backend.set_playing_volume(&handled.playing, level);
608 }
609 }
610 }
611 }
612
613 fn start_sound(&mut self, backend: &mut B, id: SoundId, cue: &str, volume: f32) {
615 let def = self.manifest.sounds.get(cue);
616 let (looped, cue_volume) = def.map_or((false, 1.0), |d| (d.looped, percent(d.volume)));
617 if let Some(playing) = self.play_sound(backend, cue, volume, looped) {
618 let handled = Handled {
619 playing,
620 cue_volume,
621 volume,
622 paused: false,
623 };
624 if self.suspended {
625 backend.set_playing_volume(&handled.playing, 0.0);
626 }
627 if let Some(old) = self.handled.insert(id, handled) {
628 backend.stop_playing(old.playing);
629 }
630 }
631 }
632
633 fn music(&mut self, backend: &mut B, command: &MusicCommand, now: f64) {
634 match command {
635 MusicCommand::Load { cue } => {
636 self.stop_track(backend, cue);
637 let Some(def) = self.manifest.music.get(cue) else {
638 self.warnings.push(format!("unknown music cue \"{cue}\""));
639 return;
640 };
641 let path = format!("{}/{}", self.music_dir, def.file);
642 let track = Track {
643 state: TrackState::Loading(backend.load_music(&path)),
644 playing: false,
645 playback: None,
646 started_at: None,
647 held_at: None,
648 gain: 1.0,
649 volume: percent(def.volume),
650 looped: def.looped,
651 };
652 self.tracks.insert(cue.clone(), track);
653 }
654 MusicCommand::Start { cue, gain, .. } => {
655 let music = self.music_level();
656 if let Some(track) = self.tracks.get_mut(cue) {
657 track.playing = true;
658 track.gain = *gain;
659 if let TrackState::Ready(sound) = &track.state {
660 let volume = track.volume * track.gain * music;
661 track.playback = Some(backend.play(sound, volume, track.looped));
662 track.started_at = Some(now);
663 track.held_at = None;
664 }
665 }
666 }
667 MusicCommand::Gain { cue, gain } => {
668 let music = self.music_level();
669 if let Some(track) = self.tracks.get_mut(cue) {
670 track.gain = *gain;
671 if let (true, TrackState::Ready(sound)) = (track.playing, &track.state) {
672 let volume = track.volume * track.gain * music;
673 backend.set_volume(sound, volume);
674 }
675 }
676 }
677 MusicCommand::Stop { cue } => self.stop_track(backend, cue),
678 }
679 }
680
681 fn stop_track(&mut self, backend: &mut B, cue: &str) {
683 let Some(track) = self.tracks.remove(cue) else {
684 return;
685 };
686 match track.state {
687 TrackState::Ready(sound) if track.playing => backend.stop(&sound),
688 TrackState::Loading(loading) => self.abandoned.push(loading),
689 TrackState::Ready(_) | TrackState::Failed => {}
690 }
691 }
692
693 pub fn stop_all(&mut self, backend: &mut B) {
696 let cues: Vec<String> = self.tracks.keys().cloned().collect();
697 for cue in cues {
698 self.stop_track(backend, &cue);
699 }
700 for handled in core::mem::take(&mut self.handled).into_values() {
701 backend.stop_playing(handled.playing);
702 }
703 self.voices.stop_all(backend, &mut self.abandoned);
704 }
705
706 pub fn set_voice_volume(&mut self, backend: &mut B, volume: f32) {
709 self.voice_volume = volume;
710 if !self.suspended {
711 self.voices.set_volume(backend, volume);
712 }
713 }
714
715 pub fn set_rate(&mut self, backend: &mut B, cue: &str, rate: u32) {
719 for sound in self.sounds.get(cue).into_iter().flatten() {
720 let result = backend.set_rate(sound, rate);
721 self.provided
722 .call(Feature::Rate, result, &mut self.warnings);
723 }
724 }
725
726 fn poll(&mut self, backend: &mut B, now: f64) {
728 let music = self.music_level();
729 for (cue, track) in &mut self.tracks {
730 let TrackState::Loading(loading) = &mut track.state else {
731 continue;
732 };
733 match backend.poll_music(loading) {
734 None => {}
735 Some(Ok(sound)) => {
736 if track.playing {
737 let volume = track.volume * track.gain * music;
738 track.playback = Some(backend.play(&sound, volume, track.looped));
739 track.started_at = Some(now);
740 }
741 track.state = TrackState::Ready(sound);
742 }
743 Some(Err(e)) => {
744 let file = self
745 .manifest
746 .music
747 .get(cue)
748 .map_or("?", |m| m.file.as_str());
749 let dir = &self.music_dir;
750 self.warnings
751 .push(format!("music \"{cue}\": {dir}/{file}: {e}"));
752 track.state = TrackState::Failed;
753 }
754 }
755 }
756 self.voices
757 .poll(backend, now, &mut self.warnings, &mut self.abandoned);
758 self.abandoned
759 .retain_mut(|loading| backend.poll_music(loading).is_none());
760 }
761
762 pub fn take_warnings(&mut self) -> Vec<String> {
764 core::mem::take(&mut self.warnings)
765 }
766}
767
768pub struct PlayerDevice<B: Backend> {
772 backend: B,
773 player: Option<Player<B>>,
774}
775
776impl<B: Backend> PlayerDevice<B> {
777 pub const fn new(backend: B) -> Self {
779 Self {
780 backend,
781 player: None,
782 }
783 }
784
785 pub const fn backend_mut(&mut self) -> &mut B {
787 &mut self.backend
788 }
789
790 pub fn set_player(&mut self, player: Player<B>) {
792 self.player = Some(player);
793 }
794
795 fn log_warnings(&mut self) {
797 if let Some(player) = &mut self.player {
798 for warning in player.take_warnings() {
799 self.backend.warn(&warning);
800 }
801 }
802 }
803}
804
805impl<B: Backend> fmt::Debug for PlayerDevice<B> {
806 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
807 f.debug_struct("PlayerDevice")
808 .field("loaded", &self.player.is_some())
809 .finish_non_exhaustive()
810 }
811}
812
813impl<B: Backend> Audio for PlayerDevice<B> {
814 fn music_playing(&self, now: Millis) -> Option<(&str, f64)> {
815 self.player.as_ref()?.music_playing(now.as_secs_f64())
816 }
817
818 fn voice_playing(&self, now: Millis) -> Option<&str> {
819 self.player.as_ref()?.voice_playing(now.as_secs_f64())
820 }
821
822 fn play(&mut self, frame: &AudioFrame<'_>, now: Millis) {
823 let Some(player) = &mut self.player else {
824 return;
825 };
826 let backend = &mut self.backend;
827 player.set_volumes(backend, frame.music_volume, frame.sound_volume);
828 player.set_voice_volume(backend, frame.voice_volume);
829 player.play(backend, frame.requests, frame.music, now.as_secs_f64());
830 self.log_warnings();
831 }
832
833 fn stop_all(&mut self) {
834 if let Some(player) = &mut self.player {
835 player.stop_all(&mut self.backend);
836 }
837 }
838
839 fn suspend(&mut self) {
840 if let Some(player) = &mut self.player {
841 player.suspend(&mut self.backend);
842 }
843 self.backend.hold_output(true);
844 self.log_warnings();
845 }
846
847 fn resume(&mut self) {
848 self.backend.hold_output(false);
849 if let Some(player) = &mut self.player {
850 player.resume(&mut self.backend);
851 }
852 self.log_warnings();
853 }
854}
855
856fn percent(volume: u8) -> f32 {
858 f32::from(volume) / 100.0
859}
860
861pub(crate) struct VariantRng(u64);
865
866impl VariantRng {
867 pub(crate) fn new(seed: u64) -> Self {
868 Self(seed)
869 }
870
871 fn next_u64(&mut self) -> u64 {
872 self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
873 let mut z = self.0;
874 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
875 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
876 z ^ (z >> 31)
877 }
878
879 pub(crate) fn below(&mut self, n: usize) -> usize {
881 let n = u64::try_from(n.max(1)).unwrap_or(u64::MAX);
882 usize::try_from(self.next_u64() % n).unwrap_or(0)
883 }
884}
885
886#[cfg(test)]
887mod tests {
888 use core::cell::RefCell;
889
890 use alloc::rc::Rc;
891 use core::pin::pin;
892 use core::task::{Context, Poll, Waker};
893
894 use alloc::borrow::Cow;
895
896 use proptest::prelude::*;
897 use serde::Deserialize;
898 use shuyi_assets::audio::{Bytes, CreditRef, MusicCue, SoundCue};
899 use shuyi_assets::voice::ClipVariant;
900 use shuyi_ui::audio::AudioRequest;
901
902 use super::Player as Audio;
903 use super::*;
904
905 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Deserialize)]
908 pub(crate) enum Take {
909 #[default]
911 Only,
912 Second,
914 }
915
916 impl ClipVariant for Take {
917 fn suffix(self) -> &'static str {
918 match self {
919 Self::Only => "",
920 Self::Second => ".2",
921 }
922 }
923
924 fn split() -> &'static [Self] {
925 &[Self::Second]
926 }
927 }
928
929 pub(crate) type Request = AudioRequest<Take>;
931
932 impl<B: Backend> Audio<B> {
933 pub(crate) fn hear(
936 &mut self,
937 backend: &mut B,
938 requests: &[Request],
939 music: &[MusicCommand],
940 now: f64,
941 ) {
942 let requests: Vec<SoundRequest> = requests
943 .iter()
944 .filter_map(SoundRequest::from_request)
945 .collect();
946 self.play(backend, &requests, music, now);
947 }
948 }
949
950 pub(crate) fn block_on<F: Future>(future: F) -> F::Output {
952 let mut future = pin!(future);
953 let mut cx = Context::from_waker(Waker::noop());
954 loop {
955 if let Poll::Ready(v) = future.as_mut().poll(&mut cx) {
956 return v;
957 }
958 }
959 }
960
961 fn wav(samples: u16) -> Vec<u8> {
964 let data_len = u32::from(samples) * 2;
965 let mut w = Vec::new();
966 w.extend_from_slice(b"RIFF");
967 w.extend_from_slice(&(36 + data_len).to_le_bytes());
968 w.extend_from_slice(b"WAVEfmt ");
969 w.extend_from_slice(&16u32.to_le_bytes());
970 w.extend_from_slice(&1u16.to_le_bytes()); w.extend_from_slice(&1u16.to_le_bytes()); w.extend_from_slice(&22_050u32.to_le_bytes());
973 w.extend_from_slice(&44_100u32.to_le_bytes());
974 w.extend_from_slice(&2u16.to_le_bytes());
975 w.extend_from_slice(&16u16.to_le_bytes());
976 w.extend_from_slice(b"data");
977 w.extend_from_slice(&data_len.to_le_bytes());
978 for i in 0..samples {
979 let s: i16 = if i % 50 < 25 { 8_000 } else { -8_000 };
980 w.extend_from_slice(&s.to_le_bytes());
981 }
982 w
983 }
984
985 fn decode_wav(bytes: &[u8]) -> Result<usize, String> {
988 let field = |at: usize| {
989 bytes
990 .get(at..at + 4)
991 .and_then(|b| <[u8; 4]>::try_from(b).ok())
992 };
993 let ok =
994 field(0) == Some(*b"RIFF") && field(8) == Some(*b"WAVE") && field(36) == Some(*b"data");
995 let len = field(40).map(u32::from_le_bytes).unwrap_or_default() as usize;
996 if ok && bytes.len() == 44 + len {
997 Ok(len / 2)
998 } else {
999 Err("not a PCM WAV".to_owned())
1000 }
1001 }
1002
1003 pub(crate) type Log = Rc<RefCell<Vec<String>>>;
1004
1005 #[derive(Default)]
1010 pub(crate) struct Fake {
1011 pub(crate) log: Log,
1012 pub(crate) ready: Vec<String>,
1013 pub(crate) broken: Vec<String>,
1014 pub(crate) length: Option<f64>,
1015 pub(crate) plays: u32,
1016 }
1017
1018 impl Backend for Fake {
1019 type Sound = String;
1020 type Loading = String;
1021 type Playing = u32;
1022
1023 fn load_sound(&mut self, bytes: &[u8]) -> impl Future<Output = Result<String, String>> {
1024 core::future::ready(decode_wav(bytes).map(|samples| format!("wav{samples}")))
1025 }
1026
1027 fn load_music(&mut self, path: &str) -> String {
1028 self.log.borrow_mut().push(format!("load {path}"));
1029 path.to_owned()
1030 }
1031
1032 fn poll_music(&mut self, loading: &mut String) -> Option<Result<String, String>> {
1033 if self.broken.contains(loading) {
1034 Some(Err("no such file".into()))
1035 } else if self.ready.contains(loading) {
1036 Some(Ok(loading.clone()))
1037 } else {
1038 None
1039 }
1040 }
1041
1042 fn length(&self, _sound: &String) -> Option<f64> {
1043 self.length
1044 }
1045
1046 fn play(&mut self, sound: &String, volume: f32, looped: bool) -> u32 {
1047 let looped = if looped { " looped" } else { "" };
1048 self.log
1049 .borrow_mut()
1050 .push(format!("play {sound} {volume:.2}{looped}"));
1051 self.plays += 1;
1052 self.plays
1053 }
1054
1055 fn set_volume(&mut self, sound: &String, volume: f32) {
1056 self.log
1057 .borrow_mut()
1058 .push(format!("volume {sound} {volume:.2}"));
1059 }
1060
1061 fn stop(&mut self, sound: &String) {
1062 self.log.borrow_mut().push(format!("stop {sound}"));
1063 }
1064
1065 fn set_playing_volume(&mut self, playing: &u32, volume: f32) {
1066 self.log
1067 .borrow_mut()
1068 .push(format!("volume #{playing} {volume:.2}"));
1069 }
1070
1071 fn stop_playing(&mut self, playing: u32) {
1072 self.log.borrow_mut().push(format!("stop #{playing}"));
1073 }
1074
1075 fn add_sound(&mut self, samples: &SharedSamples) -> Result<String, String> {
1076 let sound = format!("pcm{}", samples.frames());
1077 self.log.borrow_mut().push(format!("add {sound}"));
1078 Ok(sound)
1079 }
1080
1081 fn warn(&mut self, line: &str) {
1082 self.log.borrow_mut().push(format!("warn {line}"));
1083 }
1084 }
1085
1086 impl Fake {
1087 pub(crate) fn calls(&self) -> Vec<String> {
1088 core::mem::take(&mut self.log.borrow_mut())
1089 }
1090 }
1091
1092 fn manifest() -> AudioManifest {
1093 let mut m = AudioManifest::default();
1094 let sound = |files: &[&str], volume| {
1095 SoundCue::new(
1096 files.iter().map(|f| (*f).to_owned()).collect(),
1097 volume,
1098 CreditRef::Own,
1099 )
1100 };
1101 m.sounds.insert("beep".into(), sound(&["sfx/beep.wav"], 50));
1102 let rain = sound(&["sfx/beep.wav"], 50).with_looped(true);
1103 m.sounds.insert("rain".into(), rain);
1104 let steps = ["sfx/step_1.wav", "sfx/step_2.wav", "sfx/step_3.wav"];
1105 m.sounds.insert("step".into(), sound(&steps, 100));
1106 m.sounds
1107 .insert("broken".into(), sound(&["sfx/broken.wav"], 100));
1108 let track = |file: &str, volume, looped| MusicCue {
1109 file: file.into(),
1110 volume,
1111 looped,
1112 length_ms: 60_000,
1113 credit: CreditRef::Own,
1114 };
1115 m.music.insert("title".into(), track("title.ogg", 80, true));
1116 m.music
1117 .insert("sting".into(), track("sting.ogg", 100, false));
1118 m.music.insert("lost".into(), track("lost.ogg", 100, true));
1119 m
1120 }
1121
1122 fn bytes(file: &str) -> Bytes {
1124 thread_local! {
1125 static WAVS: (&'static [u8], &'static [u8], &'static [u8], &'static [u8]) = (
1126 Vec::leak(wav(100)),
1127 Vec::leak(wav(200)),
1128 Vec::leak(wav(300)),
1129 Vec::leak(wav(400)),
1130 );
1131 }
1132 let wav = WAVS.with(|w| match file {
1133 "sfx/beep.wav" => Some(w.0),
1134 "sfx/step_1.wav" => Some(w.1),
1135 "sfx/step_2.wav" => Some(w.2),
1136 "sfx/step_3.wav" => Some(w.3),
1137 "sfx/broken.wav" => Some(&w.0[..20]),
1138 _ => None,
1139 });
1140 wav.map(Cow::Borrowed)
1141 }
1142
1143 fn shown(file: &str) -> String {
1145 format!("bundle/{file}")
1146 }
1147
1148 pub(crate) fn files() -> Files<'static> {
1150 Files::new(&bytes, &shown, &|_: &str| false, "music")
1151 }
1152
1153 pub(crate) fn audio(backend: &mut Fake) -> Audio<Fake> {
1154 let (music, voice) = ("music".into(), "voice/en".into());
1155 block_on(Audio::load(backend, manifest(), &files(), music, voice, 7))
1156 }
1157
1158 fn sound(cue: &str, volume: f32) -> Request {
1159 Request::PlaySound {
1160 cue: cue.into(),
1161 volume,
1162 }
1163 }
1164
1165 fn cmd(f: fn(String) -> MusicCommand, cue: &str) -> MusicCommand {
1166 f(cue.into())
1167 }
1168
1169 fn load(cue: String) -> MusicCommand {
1170 MusicCommand::Load { cue }
1171 }
1172
1173 fn start(cue: String) -> MusicCommand {
1174 MusicCommand::start(cue, 1.0)
1175 }
1176
1177 fn stop(cue: String) -> MusicCommand {
1178 MusicCommand::Stop { cue }
1179 }
1180
1181 fn gain(cue: &str, gain: f32) -> MusicCommand {
1182 MusicCommand::Gain {
1183 cue: cue.into(),
1184 gain,
1185 }
1186 }
1187
1188 #[test]
1189 fn a_started_sound_is_one_playback_its_requests_and_the_sound_volume_reach() {
1190 let mut fake = Fake::default();
1191 let mut audio = audio(&mut fake);
1192 audio.take_warnings();
1193 let mut queue = shuyi_ui::audio::AudioQueue::<Take>::default();
1194 let [first, second] = [queue.start_sound("rain"), queue.start_sound("rain")];
1195 let once = queue.start_sound("beep");
1196 audio.hear(&mut fake, &queue.take(), &[], 0.0);
1197 assert_eq!(
1198 fake.calls(),
1199 [
1200 "play wav100 0.50 looped",
1201 "play wav100 0.50 looped",
1202 "play wav100 0.50"
1203 ]
1204 );
1205 queue.set_sound_volume(first, 0.5);
1206 audio.hear(&mut fake, &queue.take(), &[], 0.0);
1207 assert_eq!(fake.calls(), ["volume #1 0.25"]);
1208 audio.set_volumes(&mut fake, 1.0, 0.4);
1210 assert_eq!(
1211 fake.calls(),
1212 ["volume #1 0.10", "volume #2 0.20", "volume #3 0.20"]
1213 );
1214 queue.stop_sound(first, 0);
1216 queue.stop_sound(once, 0);
1217 audio.hear(&mut fake, &queue.take(), &[], 0.0);
1218 assert_eq!(fake.calls(), ["stop #1", "stop #3"]);
1219 audio.hear(&mut fake, &[], &[], 0.0);
1220 let got = fake.calls();
1221 assert!(got.is_empty(), "{got:?}");
1222 audio.stop_all(&mut fake);
1223 assert_eq!(fake.calls(), ["stop #2"]);
1224 queue.set_sound_volume(second, 1.0);
1225 queue.stop_sound(second, 0);
1226 audio.hear(&mut fake, &queue.take(), &[], 0.0);
1227 assert!(fake.calls().is_empty(), "a stopped id is gone");
1228 }
1229
1230 #[test]
1231 fn a_pause_mutes_the_sounds_playing_and_its_resume_restores_them() {
1232 let mut fake = Fake::default();
1233 let mut audio = audio(&mut fake);
1234 let mut queue = shuyi_ui::audio::AudioQueue::<Take>::default();
1235 let below = queue.start_sound("rain");
1236 queue.pause_sounds();
1237 let menu = queue.start_sound_at("rain", 0.5);
1238 audio.hear(&mut fake, &queue.take(), &[], 0.0);
1239 assert_eq!(
1240 fake.calls(),
1241 [
1242 "play wav100 0.50 looped",
1243 "volume #1 0.00",
1244 "play wav100 0.25 looped"
1245 ]
1246 );
1247 queue.set_sound_volume(below, 0.5);
1249 audio.hear(&mut fake, &queue.take(), &[], 0.0);
1250 audio.set_volumes(&mut fake, 1.0, 0.5);
1251 assert_eq!(
1252 fake.calls(),
1253 ["volume #1 0.00", "volume #1 0.00", "volume #2 0.12"]
1254 );
1255 queue.resume_sounds();
1256 audio.hear(&mut fake, &queue.take(), &[], 0.0);
1257 assert_eq!(fake.calls(), ["volume #1 0.12"]);
1258 assert!(queue.sound_is_live(menu));
1259 }
1260
1261 #[test]
1262 fn a_suspend_mutes_the_sounds_and_the_music_and_the_resume_restores_them() {
1263 let mut fake = Fake {
1264 ready: vec!["music/title.ogg".into()],
1265 ..Fake::default()
1266 };
1267 let mut audio = audio(&mut fake);
1268 let mut queue = shuyi_ui::audio::AudioQueue::<Take>::default();
1269 let [below, other] = [queue.start_sound("rain"), queue.start_sound("rain")];
1270 queue.pause_sounds();
1271 let music = [cmd(load, "title"), cmd(start, "title")];
1272 audio.hear(&mut fake, &queue.take(), &music, 0.0);
1273 fake.calls();
1274 audio.suspend(&mut fake);
1275 assert_eq!(
1276 fake.calls(),
1277 [
1278 "volume #1 0.00",
1279 "volume #2 0.00",
1280 "volume music/title.ogg 0.00"
1281 ]
1282 );
1283 audio.suspend(&mut fake);
1284 assert!(fake.calls().is_empty(), "a second suspend does nothing");
1285 assert_eq!(audio.music_playing(2.0), Some(("title", 2.0)));
1287 audio.resume(&mut fake);
1288 assert_eq!(
1290 fake.calls(),
1291 [
1292 "volume #1 0.00",
1293 "volume #2 0.00",
1294 "volume music/title.ogg 0.80"
1295 ]
1296 );
1297 audio.resume(&mut fake);
1298 assert!(fake.calls().is_empty(), "a second resume does nothing");
1299 queue.resume_sounds();
1300 audio.hear(&mut fake, &queue.take(), &[], 3.0);
1301 assert_eq!(fake.calls(), ["volume #1 0.50", "volume #2 0.50"]);
1302 assert!(queue.sound_is_live(below) && queue.sound_is_live(other));
1303 }
1304
1305 fn added(frames: usize) -> shuyi_ui::audio::Samples {
1307 let samples = shuyi_ui::audio::Samples::from_u8(11_025, 1, &vec![128; frames]);
1308 samples.unwrap_or_else(|e| panic!("{e}"))
1309 }
1310
1311 #[test]
1312 fn an_added_sound_plays_as_a_cue_at_its_own_volume_until_it_is_removed() {
1313 let mut fake = Fake::default();
1314 let mut audio = audio(&mut fake);
1315 audio.take_warnings();
1316 fake.calls();
1317 let mut queue = shuyi_ui::audio::AudioQueue::<Take>::default();
1318 assert_eq!(queue.add_sound("tone", added(1102)), Ok(()));
1319 queue.play_sound_at("tone", 0.5);
1320 let id = queue.start_sound("tone");
1321 audio.hear(&mut fake, &queue.take(), &[], 0.0);
1322 assert_eq!(
1323 fake.calls(),
1324 ["add pcm1102", "play pcm1102 0.50", "play pcm1102 1.00"]
1325 );
1326 audio.set_volumes(&mut fake, 1.0, 0.5);
1328 fake.calls();
1329 queue.play_sound("tone");
1330 audio.hear(&mut fake, &queue.take(), &[], 0.0);
1331 assert_eq!(fake.calls(), ["play pcm1102 0.50"]);
1332 assert_eq!(queue.add_sound("tone", added(551)), Ok(()));
1334 queue.play_sound("tone");
1335 queue.remove_sound("tone");
1336 queue.stop_sound(id, 0);
1337 audio.hear(&mut fake, &queue.take(), &[], 0.0);
1338 assert_eq!(
1339 fake.calls(),
1340 [
1341 "stop pcm1102",
1342 "add pcm551",
1343 "play pcm551 0.50",
1344 "stop pcm551",
1345 "stop #2"
1346 ]
1347 );
1348 let found = audio.take_warnings();
1349 assert!(found.is_empty(), "{found:?}");
1350 audio.hear(&mut fake, &[sound("tone", 1.0)], &[], 0.0);
1352 let found = fake.calls();
1353 assert!(found.is_empty(), "{found:?}");
1354 assert_eq!(audio.take_warnings(), ["unknown sound cue \"tone\""]);
1355 }
1356
1357 #[test]
1358 fn a_looped_cue_played_without_a_handle_plays_once() {
1359 let mut fake = Fake::default();
1360 let mut audio = audio(&mut fake);
1361 audio.hear(&mut fake, &[sound("rain", 1.0)], &[], 0.0);
1362 assert_eq!(fake.calls(), ["play wav100 0.50"]);
1363 }
1364
1365 #[test]
1366 fn a_generated_wav_plays_through_the_app_path() {
1367 let mut fake = Fake::default();
1368 let mut audio = audio(&mut fake);
1369 assert_eq!(
1370 audio.take_warnings(),
1371 ["sound \"broken\": bundle/sfx/broken.wav: not a PCM WAV"]
1372 );
1373 let music_request = Request::play_music("title", None);
1374 audio.hear(
1375 &mut fake,
1376 &[sound("beep", 1.0), music_request, sound("beep", 0.5)],
1377 &[],
1378 0.0,
1379 );
1380 assert_eq!(fake.calls(), ["play wav100 0.50", "play wav100 0.25"]);
1381 audio.hear(&mut fake, &[sound("broken", 1.0)], &[], 0.0);
1383 let got = fake.calls();
1384 assert!(got.is_empty(), "{got:?}");
1385 let got = audio.take_warnings();
1386 assert!(got.is_empty(), "{got:?}");
1387 }
1388
1389 #[test]
1390 fn unknown_or_missing_files_are_warnings() {
1391 let mut fake = Fake::default();
1392 let mut m = manifest();
1393 if let Some(beep) = m.sounds.get_mut("beep") {
1394 beep.files.push("sfx/gone.wav".into());
1395 }
1396 let dirs = ("music".into(), "voice/en".into());
1397 let mut audio = block_on(Audio::load(&mut fake, m, &files(), dirs.0, dirs.1, 1));
1398 assert_eq!(
1399 audio.take_warnings()[0],
1400 "sound \"beep\": bundle/sfx/gone.wav: not in the asset bundle"
1401 );
1402 audio.hear(&mut fake, &[sound("nope", 1.0)], &[cmd(load, "nope")], 0.0);
1403 assert_eq!(
1404 audio.take_warnings(),
1405 ["unknown sound cue \"nope\"", "unknown music cue \"nope\""]
1406 );
1407 let got = fake.calls();
1408 assert!(got.is_empty(), "{got:?}");
1409 }
1410
1411 #[test]
1412 fn variants_are_picked_at_random() {
1413 let mut fake = Fake::default();
1414 let mut audio = audio(&mut fake);
1415 let steps: Vec<_> = (0..30).map(|_| sound("step", 1.0)).collect();
1416 audio.hear(&mut fake, &steps, &[], 0.0);
1417 let calls = fake.calls();
1418 assert_eq!(calls.len(), 30);
1419 for variant in ["wav200", "wav300", "wav400"] {
1420 assert!(
1421 calls.iter().any(|c| c.contains(variant)),
1422 "{variant} never played: {calls:?}"
1423 );
1424 }
1425 }
1426
1427 #[test]
1428 fn music_loads_then_starts_when_ready() {
1429 let mut fake = Fake::default();
1430 let mut audio = audio(&mut fake);
1431 audio.play(
1432 &mut fake,
1433 &[],
1434 &[cmd(load, "title"), cmd(start, "title")],
1435 0.0,
1436 );
1437 assert_eq!(fake.calls(), ["load music/title.ogg"]);
1438 audio.play(&mut fake, &[], &[], 0.0);
1439 assert!(fake.calls().is_empty(), "still loading");
1440 fake.ready.push("music/title.ogg".into());
1441 audio.play(&mut fake, &[], &[], 0.0);
1442 assert_eq!(fake.calls(), ["play music/title.ogg 0.80 looped"]);
1443 audio.play(&mut fake, &[], &[], 0.0);
1444 assert!(fake.calls().is_empty(), "started once");
1445 audio.play(&mut fake, &[], &[gain("title", 0.5)], 0.0);
1447 assert_eq!(fake.calls(), ["volume music/title.ogg 0.40"]);
1448 audio.play(&mut fake, &[], &[cmd(stop, "title")], 0.0);
1449 assert_eq!(fake.calls(), ["stop music/title.ogg"]);
1450 assert!(audio.tracks.is_empty());
1451 }
1452
1453 #[test]
1455 fn the_players_volumes_scale_sounds_and_music() {
1456 let mut fake = Fake::default();
1457 fake.ready.push("music/title.ogg".into());
1458 let mut audio = audio(&mut fake);
1459 audio.set_volumes(&mut fake, 0.5, 0.2);
1460 assert!(fake.calls().is_empty(), "nothing is playing yet");
1461 audio.hear(
1463 &mut fake,
1464 &[sound("beep", 1.0), sound("beep", 0.5)],
1465 &[],
1466 0.0,
1467 );
1468 assert_eq!(fake.calls(), ["play wav100 0.10", "play wav100 0.05"]);
1469 let begin = [cmd(load, "title"), cmd(start, "title")];
1471 audio.play(&mut fake, &[], &begin, 0.0);
1472 assert_eq!(
1473 fake.calls(),
1474 ["load music/title.ogg", "play music/title.ogg 0.40 looped"]
1475 );
1476 audio.set_volumes(&mut fake, 0.5, 0.2);
1479 let got = fake.calls();
1480 assert!(got.is_empty(), "{got:?}");
1481 audio.set_volumes(&mut fake, 1.0, 0.2);
1482 assert_eq!(fake.calls(), ["volume music/title.ogg 0.80"]);
1483 audio.play(&mut fake, &[], &[gain("title", 0.5)], 0.0);
1484 assert_eq!(fake.calls(), ["volume music/title.ogg 0.40"]);
1485 audio.set_volumes(&mut fake, 0.5, 0.2);
1486 assert_eq!(fake.calls(), ["volume music/title.ogg 0.20"]);
1487 audio.set_volumes(&mut fake, 0.0, 0.0);
1489 assert_eq!(fake.calls(), ["volume music/title.ogg 0.00"]);
1490 audio.hear(&mut fake, &[sound("beep", 1.0)], &[], 0.0);
1491 assert_eq!(fake.calls(), ["play wav100 0.00"]);
1492 }
1493
1494 #[test]
1497 fn a_track_that_starts_later_uses_the_players_volume() {
1498 let mut fake = Fake::default();
1499 let mut audio = audio(&mut fake);
1500 let begin = [cmd(load, "title"), cmd(start, "title")];
1501 audio.play(&mut fake, &[], &begin, 0.0);
1502 fake.calls();
1503 audio.set_volumes(&mut fake, 0.25, 1.0);
1505 let got = fake.calls();
1506 assert!(got.is_empty(), "{got:?}");
1507 fake.ready.push("music/title.ogg".into());
1508 audio.play(&mut fake, &[], &[], 0.0);
1509 assert_eq!(fake.calls(), ["play music/title.ogg 0.20 looped"]);
1510 audio.play(&mut fake, &[], &[cmd(start, "title")], 0.0);
1512 assert_eq!(fake.calls(), ["play music/title.ogg 0.20 looped"]);
1513 }
1514
1515 #[test]
1516 fn a_loaded_track_starts_at_once_and_plays_once_if_not_looped() {
1517 let mut fake = Fake::default();
1518 fake.ready.push("music/sting.ogg".into());
1519 let mut audio = audio(&mut fake);
1520 audio.play(&mut fake, &[], &[cmd(load, "sting")], 0.0);
1521 assert_eq!(fake.calls(), ["load music/sting.ogg"]);
1522 audio.play(
1523 &mut fake,
1524 &[],
1525 &[gain("sting", 0.5), cmd(start, "sting")],
1526 0.0,
1527 );
1528 assert_eq!(fake.calls(), ["play music/sting.ogg 1.00"]);
1529 }
1530
1531 #[test]
1532 fn a_gain_before_the_track_is_ready_applies_when_it_starts() {
1533 let mut fake = Fake::default();
1534 let mut audio = audio(&mut fake);
1535 audio.play(
1536 &mut fake,
1537 &[],
1538 &[cmd(load, "title"), cmd(start, "title")],
1539 0.0,
1540 );
1541 audio.play(&mut fake, &[], &[gain("title", 0.5)], 0.0);
1542 fake.ready.push("music/title.ogg".into());
1543 audio.play(&mut fake, &[], &[], 0.0);
1544 assert_eq!(
1545 fake.calls(),
1546 ["load music/title.ogg", "play music/title.ogg 0.40 looped"]
1547 );
1548 }
1549
1550 #[test]
1551 fn a_stopped_load_is_polled_until_done_then_dropped() {
1552 let mut fake = Fake::default();
1553 let mut audio = audio(&mut fake);
1554 audio.play(
1555 &mut fake,
1556 &[],
1557 &[cmd(load, "title"), cmd(stop, "title")],
1558 0.0,
1559 );
1560 assert_eq!(audio.abandoned.len(), 1);
1561 audio.play(&mut fake, &[], &[], 0.0);
1562 assert_eq!(audio.abandoned.len(), 1);
1563 fake.ready.push("music/title.ogg".into());
1564 audio.play(&mut fake, &[], &[], 0.0);
1565 assert!(audio.abandoned.is_empty(), "{:?}", audio.abandoned);
1566 assert_eq!(fake.calls(), ["load music/title.ogg"], "never played");
1567 }
1568
1569 #[test]
1570 fn a_missing_track_is_a_warning_and_silence() {
1571 let mut fake = Fake::default();
1572 fake.broken.push("music/lost.ogg".into());
1573 let mut audio = audio(&mut fake);
1574 audio.take_warnings();
1575 audio.play(
1576 &mut fake,
1577 &[],
1578 &[cmd(load, "lost"), cmd(start, "lost")],
1579 0.0,
1580 );
1581 assert_eq!(
1582 audio.take_warnings(),
1583 ["music \"lost\": music/lost.ogg: no such file"]
1584 );
1585 audio.play(&mut fake, &[], &[gain("lost", 0.5), cmd(stop, "lost")], 0.0);
1586 assert_eq!(fake.calls(), ["load music/lost.ogg"]);
1587 }
1588
1589 #[test]
1590 fn loading_again_replaces_the_track() {
1591 let mut fake = Fake::default();
1592 fake.ready.push("music/title.ogg".into());
1593 let mut audio = audio(&mut fake);
1594 audio.play(
1595 &mut fake,
1596 &[],
1597 &[cmd(load, "title"), cmd(start, "title")],
1598 0.0,
1599 );
1600 audio.play(&mut fake, &[], &[], 0.0);
1601 audio.play(&mut fake, &[], &[cmd(load, "title")], 0.0);
1602 assert_eq!(
1603 fake.calls(),
1604 [
1605 "load music/title.ogg",
1606 "play music/title.ogg 0.80 looped",
1607 "stop music/title.ogg",
1608 "load music/title.ogg",
1609 ]
1610 );
1611 }
1612
1613 #[test]
1614 fn a_ready_track_not_started_is_freed_silently() {
1615 let mut fake = Fake::default();
1616 fake.ready.push("music/title.ogg".into());
1617 let mut audio = audio(&mut fake);
1618 audio.play(&mut fake, &[], &[cmd(load, "title")], 0.0);
1619 audio.play(
1620 &mut fake,
1621 &[],
1622 &[gain("title", 0.5), cmd(stop, "title")],
1623 0.0,
1624 );
1625 assert_eq!(fake.calls(), ["load music/title.ogg"]);
1626 assert!(audio.tracks.is_empty());
1627 audio.play(
1629 &mut fake,
1630 &[],
1631 &[cmd(start, "title"), gain("title", 1.0)],
1632 0.0,
1633 );
1634 let got = fake.calls();
1635 assert!(got.is_empty(), "{got:?}");
1636 }
1637
1638 #[test]
1639 fn stop_all_silences_every_track() {
1640 let mut fake = Fake::default();
1641 fake.ready.push("music/title.ogg".into());
1642 let mut audio = audio(&mut fake);
1643 let cmds = [cmd(load, "title"), cmd(start, "title"), cmd(load, "sting")];
1644 audio.play(&mut fake, &[], &cmds, 0.0);
1645 audio.stop_all(&mut fake);
1646 assert!(audio.tracks.is_empty());
1647 assert_eq!(
1648 fake.calls(),
1649 [
1650 "load music/title.ogg",
1651 "load music/sting.ogg",
1652 "play music/title.ogg 0.80 looped",
1653 "stop music/title.ogg",
1654 ]
1655 );
1656 assert_eq!(audio.abandoned.len(), 1);
1657 }
1658
1659 #[test]
1662 fn the_music_playing_is_reported_from_its_real_start() {
1663 let mut fake = Fake::default();
1664 let mut audio = audio(&mut fake);
1665 assert_eq!(audio.music_playing(10.0), None);
1666 let cmds = [cmd(load, "title"), cmd(start, "title")];
1667 audio.play(&mut fake, &[], &cmds, 10.0);
1668 audio.play(&mut fake, &[], &[], 11.0);
1669 assert_eq!(audio.music_playing(11.5), None, "still loading");
1670 fake.ready.push("music/title.ogg".into());
1671 assert_eq!(audio.music_playing(12.0), None, "loaded, not yet started");
1672 audio.play(&mut fake, &[], &[], 12.5);
1673 assert_eq!(audio.music_playing(12.5), Some(("title", 0.0)));
1674 assert_eq!(audio.music_playing(14.0), Some(("title", 1.5)));
1675 audio.play(&mut fake, &[], &[gain("title", 0.5)], 15.0);
1678 assert_eq!(audio.music_playing(100.0), Some(("title", 87.5)));
1679 assert_eq!(audio.music_playing(12.0), Some(("title", 0.0)));
1681 audio.play(&mut fake, &[], &[cmd(stop, "title")], 101.0);
1682 assert_eq!(audio.music_playing(101.0), None);
1683 }
1684
1685 #[test]
1686 fn a_loaded_track_is_reported_from_its_start_command() {
1687 let mut fake = Fake::default();
1688 fake.ready.push("music/sting.ogg".into());
1689 let mut audio = audio(&mut fake);
1690 audio.play(&mut fake, &[], &[cmd(load, "sting")], 1.0);
1691 assert_eq!(audio.music_playing(2.0), None, "ready, not asked to play");
1692 audio.play(&mut fake, &[], &[cmd(start, "sting")], 3.0);
1693 assert_eq!(audio.music_playing(3.25), Some(("sting", 0.25)));
1694 audio.play(&mut fake, &[], &[cmd(load, "sting")], 4.0);
1696 assert_eq!(audio.music_playing(4.5), None);
1697 }
1698
1699 #[test]
1700 fn a_track_that_fails_to_load_is_never_reported() {
1701 let mut fake = Fake::default();
1702 fake.broken.push("music/lost.ogg".into());
1703 let mut audio = audio(&mut fake);
1704 let cmds = [cmd(load, "lost"), cmd(start, "lost")];
1705 audio.play(&mut fake, &[], &cmds, 1.0);
1706 audio.play(&mut fake, &[], &[], 2.0);
1707 assert_eq!(audio.music_playing(3.0), None);
1708 }
1709
1710 #[test]
1713 fn after_a_switch_the_report_follows_the_new_track_once_it_starts() {
1714 let mut fake = Fake::default();
1715 fake.ready.push("music/title.ogg".into());
1716 let mut audio = audio(&mut fake);
1717 let cmds = [cmd(load, "title"), cmd(start, "title")];
1718 audio.play(&mut fake, &[], &cmds, 1.0);
1719 let fading = [cmd(load, "sting"), gain("title", 0.5)];
1720 audio.play(&mut fake, &[], &fading, 5.0);
1721 assert_eq!(audio.music_playing(5.25), Some(("title", 4.25)));
1722 let switch = [cmd(stop, "title"), cmd(start, "sting")];
1724 audio.play(&mut fake, &[], &switch, 5.5);
1725 assert_eq!(audio.music_playing(6.0), None);
1726 fake.ready.push("music/sting.ogg".into());
1727 audio.play(&mut fake, &[], &[], 7.0);
1728 assert_eq!(audio.music_playing(7.5), Some(("sting", 0.5)));
1729 }
1730
1731 #[test]
1734 fn of_two_tracks_the_one_started_last_is_reported() {
1735 for (first, second) in [("sting", "title"), ("title", "sting")] {
1736 let mut fake = Fake::default();
1737 fake.ready.push("music/title.ogg".into());
1738 fake.ready.push("music/sting.ogg".into());
1739 let mut audio = audio(&mut fake);
1740 let cmds = [cmd(load, first), cmd(load, second)];
1741 audio.play(&mut fake, &[], &cmds, 0.0);
1742 audio.play(&mut fake, &[], &[cmd(start, first)], 1.0);
1743 audio.play(&mut fake, &[], &[cmd(start, second)], 2.0);
1744 assert_eq!(audio.music_playing(2.5), Some((second, 0.5)));
1745 }
1746 }
1747
1748 #[test]
1750 fn a_start_at_gain_zero_plays_at_volume_zero() {
1751 let mut fake = Fake::default();
1752 fake.ready.push("music/sting.ogg".into());
1753 let mut audio = audio(&mut fake);
1754 let silent = MusicCommand::start("sting", 0.0);
1755 audio.play(&mut fake, &[], &[cmd(load, "sting"), silent], 0.0);
1756 audio.play(&mut fake, &[], &[gain("sting", 0.5)], 0.0);
1757 assert_eq!(
1758 fake.calls(),
1759 [
1760 "load music/sting.ogg",
1761 "play music/sting.ogg 0.00",
1762 "volume music/sting.ogg 0.50"
1763 ]
1764 );
1765 let silent = MusicCommand::start("title", 0.0);
1767 audio.play(&mut fake, &[], &[cmd(load, "title"), silent], 0.0);
1768 fake.ready.push("music/title.ogg".into());
1769 audio.play(&mut fake, &[], &[], 0.0);
1770 assert_eq!(
1771 fake.calls(),
1772 ["load music/title.ogg", "play music/title.ogg 0.00 looped"]
1773 );
1774 }
1775
1776 #[test]
1780 fn two_tracks_play_at_once_in_a_crossfade() {
1781 let mut fake = Fake::default();
1782 fake.ready.push("music/title.ogg".into());
1783 let mut audio = audio(&mut fake);
1784 let cmds = [cmd(load, "title"), cmd(start, "title")];
1785 audio.play(&mut fake, &[], &cmds, 1.0);
1786 let crossfade = [cmd(load, "sting"), MusicCommand::start("sting", 0.0)];
1787 audio.play(&mut fake, &[], &crossfade, 2.0);
1788 assert_eq!(audio.music_playing(2.5), Some(("title", 1.5)));
1789 fake.ready.push("music/sting.ogg".into());
1790 audio.play(&mut fake, &[], &[], 3.0);
1791 assert_eq!(audio.music_playing(3.5), Some(("sting", 0.5)));
1792 let halfway = [gain("title", 0.5), gain("sting", 0.5)];
1793 audio.play(&mut fake, &[], &halfway, 3.5);
1794 audio.play(
1795 &mut fake,
1796 &[],
1797 &[cmd(stop, "title"), gain("sting", 1.0)],
1798 4.0,
1799 );
1800 assert_eq!(
1801 fake.calls(),
1802 [
1803 "load music/title.ogg",
1804 "play music/title.ogg 0.80 looped",
1805 "load music/sting.ogg",
1806 "play music/sting.ogg 0.00",
1807 "volume music/title.ogg 0.40",
1808 "volume music/sting.ogg 0.50",
1809 "stop music/title.ogg",
1810 "volume music/sting.ogg 1.00",
1811 ]
1812 );
1813 assert_eq!(audio.music_playing(4.5), Some(("sting", 1.5)));
1814 }
1815
1816 #[test]
1817 fn percent_is_a_factor() {
1818 assert!((percent(100) - 1.0).abs() < f32::EPSILON);
1819 assert!((percent(60) - 0.6).abs() < f32::EPSILON);
1820 assert!(percent(0).abs() < f32::EPSILON);
1821 }
1822
1823 #[test]
1824 fn the_rng_is_deterministic_per_seed() {
1825 let draws = |seed| {
1826 let mut rng = VariantRng::new(seed);
1827 (0..8).map(|_| rng.next_u64()).collect::<Vec<_>>()
1828 };
1829 assert_eq!(draws(1), draws(1));
1830 assert_ne!(draws(1), draws(2));
1831 assert_eq!(VariantRng::new(0).next_u64(), 0xE220_A839_7B1D_CDAF);
1833 assert_eq!(VariantRng::new(5).below(0), 0);
1834 assert_eq!(VariantRng::new(5).below(1), 0);
1835 }
1836
1837 #[derive(Default)]
1840 struct Required {
1841 volumes: Vec<(u32, f32)>,
1842 plays: u32,
1843 }
1844
1845 impl Backend for Required {
1846 type Sound = u8;
1847 type Loading = u8;
1848 type Playing = u32;
1849
1850 fn load_sound(&mut self, _: &[u8]) -> impl Future<Output = Result<u8, String>> {
1851 core::future::ready(Ok(0))
1852 }
1853
1854 fn load_music(&mut self, _: &str) -> u8 {
1855 0
1856 }
1857
1858 fn poll_music(&mut self, _: &mut u8) -> Option<Result<u8, String>> {
1859 Some(Ok(0))
1860 }
1861
1862 fn length(&self, _: &u8) -> Option<f64> {
1863 None
1864 }
1865
1866 fn play(&mut self, _: &u8, _: f32, _: bool) -> u32 {
1867 self.plays += 1;
1868 self.plays
1869 }
1870
1871 fn set_volume(&mut self, _: &u8, _: f32) {}
1872
1873 fn stop(&mut self, _: &u8) {}
1874
1875 fn set_playing_volume(&mut self, playing: &u32, volume: f32) {
1876 self.volumes.push((*playing, volume));
1877 }
1878
1879 fn stop_playing(&mut self, _: u32) {}
1880 }
1881
1882 fn loaded<B: Backend>(backend: &mut B) -> Player<B> {
1884 let (music, voice) = ("music".to_owned(), "voice".to_owned());
1885 let files = files();
1886 let mut player = block_on(Player::load(backend, manifest(), &files, music, voice, 1));
1887 player.take_warnings();
1888 player
1889 }
1890
1891 fn handle() -> SoundId {
1893 shuyi_ui::audio::AudioQueue::<Take>::default().start_sound("rain")
1894 }
1895
1896 fn required() -> (Required, Player<Required>) {
1897 let mut backend = Required::default();
1898 let player = loaded(&mut backend);
1899 (backend, player)
1900 }
1901
1902 #[test]
1903 fn the_provided_methods_default_to_unsupported() {
1904 let mut backend = Required::default();
1905 assert_eq!(RATE_ONE, 65_536);
1906 assert_eq!(backend.pause(&1), Err(Unsupported));
1907 assert_eq!(backend.resume(&1), Err(Unsupported));
1908 assert_eq!(backend.set_rate(&0, RATE_ONE), Err(Unsupported));
1909 backend.warn("said nowhere");
1910 assert_eq!(
1911 Unsupported.to_string(),
1912 "this backend doesn't have this feature"
1913 );
1914 assert_eq!(
1915 block_on(backend.load_sound_file("nope", &files())),
1916 Err("not in the asset bundle".to_owned())
1917 );
1918 let samples = SharedSamples::new(added(4));
1919 assert_eq!(backend.add_sound(&samples), Err(Unsupported.to_string()));
1920 backend.remove_sound(0);
1921 }
1922
1923 #[test]
1924 fn a_backend_that_cant_add_a_sound_warns_and_plays_nothing() {
1925 let (mut backend, mut player) = required();
1926 let mut queue = shuyi_ui::audio::AudioQueue::<Take>::default();
1927 assert_eq!(queue.add_sound("tone", added(4)), Ok(()));
1928 queue.play_sound("tone");
1929 player.hear(&mut backend, &queue.take(), &[], 0.0);
1930 assert_eq!(backend.plays, 0);
1931 assert_eq!(
1932 player.take_warnings(),
1933 [
1934 "sound \"tone\": can't be added: this backend doesn't have this feature",
1935 "unknown sound cue \"tone\""
1936 ]
1937 );
1938 }
1939
1940 #[test]
1943 fn pausing_without_a_pause_warns_once_and_mutes_until_the_resume() {
1944 let (mut backend, mut player) = required();
1945 let start = SoundRequest::start(handle(), "rain", 1.0);
1946 player.play(&mut backend, &[start], &[], 0.0);
1947 for _ in 0..2 {
1948 player.play(&mut backend, &[SoundRequest::PauseSounds], &[], 0.0);
1949 player.play(&mut backend, &[SoundRequest::ResumeSounds], &[], 0.0);
1950 }
1951 assert_eq!(
1952 player.take_warnings(),
1953 ["this backend can't pause sounds: they are muted instead"]
1954 );
1955 assert_eq!(
1956 backend.volumes,
1957 [(1, 0.0), (1, 0.5), (1, 0.0), (1, 0.5)],
1958 "muted until each resume"
1959 );
1960 player.suspend(&mut backend);
1961 player.resume(&mut backend);
1962 assert_eq!(player.take_warnings(), [] as [String; 0]);
1963 }
1964
1965 #[test]
1966 fn a_rate_without_a_rate_warns_once_and_plays_as_recorded() {
1967 let (mut backend, mut player) = required();
1968 player.set_rate(&mut backend, "beep", 2 * RATE_ONE);
1969 player.set_rate(&mut backend, "beep", RATE_ONE);
1970 player.set_rate(&mut backend, "nope", RATE_ONE);
1971 assert_eq!(
1972 player.take_warnings(),
1973 ["this backend can't change a sound's rate: it plays as recorded"]
1974 );
1975 }
1976
1977 #[test]
1980 fn the_device_is_the_player_once_the_game_has_loaded() {
1981 use crate::audio::Audio as _;
1982 let mut device = PlayerDevice::new(Fake::default());
1983 let requests = [SoundRequest::Sound {
1984 cue: "nope".to_owned(),
1985 volume: 1.0,
1986 }];
1987 let frame = AudioFrame {
1988 requests: &requests,
1989 music: &[],
1990 music_volume: 0.5,
1991 sound_volume: 0.5,
1992 voice_volume: 0.5,
1993 };
1994 device.play(&frame, Millis(0));
1995 device.suspend();
1996 device.resume();
1997 device.stop_all();
1998 assert_eq!(device.music_playing(Millis(0)), None);
1999 assert!(format!("{device:?}").contains("loaded: false"));
2000 let player = audio(device.backend_mut());
2001 device.set_player(player);
2002 device.play(&frame, Millis(0));
2003 assert!(format!("{device:?}").contains("loaded: true"));
2004 let said = device.backend_mut().calls();
2005 let nope = "warn unknown sound cue \"nope\"".to_owned();
2006 assert!(said.contains(&nope), "{said:?}");
2007 let music = [cmd(load, "title"), cmd(start, "title")];
2008 device.backend_mut().ready.push("music/title.ogg".into());
2009 let frame = AudioFrame {
2010 requests: &[],
2011 music: &music,
2012 ..frame
2013 };
2014 device.play(&frame, Millis(1000));
2015 assert_eq!(device.music_playing(Millis(2000)), Some(("title", 1.0)));
2016 assert_eq!(device.voice_playing(Millis(2000)), None);
2017 let say = [SoundRequest::Voice {
2018 clip: "a.ogg".into(),
2019 }];
2020 device.play(
2021 &AudioFrame {
2022 requests: &say,
2023 ..frame
2024 },
2025 Millis(2000),
2026 );
2027 assert_eq!(device.voice_playing(Millis(2000)), Some("a.ogg"));
2028 device.backend_mut().calls();
2029 device.suspend();
2030 let said = device.backend_mut().calls();
2031 let muted = "volume music/title.ogg 0.00".to_owned();
2032 assert!(said.contains(&muted), "{said:?}");
2033 let warned = "warn this backend can't pause sounds: they are muted instead".to_owned();
2034 assert!(said.contains(&warned), "{said:?}");
2035 device.resume();
2036 let said = device.backend_mut().calls();
2037 let back = "volume music/title.ogg 0.40".to_owned();
2038 assert!(said.contains(&back), "{said:?}");
2039 device.stop_all();
2040 assert_eq!(device.music_playing(Millis(2000)), None);
2041 }
2042
2043 #[test]
2046 fn the_same_sound_volume_again_leaves_a_started_sound_alone() {
2047 let mut fake = Fake::default();
2048 let mut audio = audio(&mut fake);
2049 let start_rain = SoundRequest::start(handle(), "rain", 1.0);
2050 audio.play(&mut fake, &[start_rain], &[], 0.0);
2051 fake.calls();
2052 audio.set_volumes(&mut fake, 1.0, 1.0);
2053 assert_eq!(fake.calls(), [] as [String; 0]);
2054 audio.set_volumes(&mut fake, 1.0, 1.0 - f32::EPSILON);
2055 assert_eq!(fake.calls(), [] as [String; 0]);
2056 }
2057
2058 #[test]
2061 fn a_ready_track_starts_at_its_gain_and_a_fade_keeps_the_music_volume() {
2062 let mut fake = Fake::default();
2063 fake.ready.push("music/title.ogg".into());
2064 let mut audio = audio(&mut fake);
2065 audio.set_volumes(&mut fake, 0.5, 1.0);
2066 audio.play(&mut fake, &[], &[cmd(load, "title")], 0.0);
2067 fake.calls();
2068 let start_half = MusicCommand::start("title", 0.5);
2069 audio.play(&mut fake, &[], &[start_half], 0.0);
2070 assert_eq!(fake.calls(), ["play music/title.ogg 0.20 looped"]);
2071 audio.play(&mut fake, &[], &[gain("title", 0.25)], 0.0);
2072 assert_eq!(fake.calls(), ["volume music/title.ogg 0.10"]);
2073 }
2074
2075 #[derive(Default)]
2077 struct Pausing {
2078 inner: Fake,
2079 paused: Vec<u32>,
2080 }
2081
2082 impl Backend for Pausing {
2083 type Sound = String;
2084 type Loading = String;
2085 type Playing = u32;
2086
2087 fn load_sound(&mut self, bytes: &[u8]) -> impl Future<Output = Result<String, String>> {
2088 self.inner.load_sound(bytes)
2089 }
2090
2091 fn load_music(&mut self, path: &str) -> String {
2092 self.inner.load_music(path)
2093 }
2094
2095 fn poll_music(&mut self, loading: &mut String) -> Option<Result<String, String>> {
2096 self.inner.poll_music(loading)
2097 }
2098
2099 fn length(&self, sound: &String) -> Option<f64> {
2100 self.inner.length(sound)
2101 }
2102
2103 fn play(&mut self, sound: &String, volume: f32, looped: bool) -> u32 {
2104 self.inner.play(sound, volume, looped)
2105 }
2106
2107 fn set_volume(&mut self, sound: &String, volume: f32) {
2108 self.inner.set_volume(sound, volume);
2109 }
2110
2111 fn stop(&mut self, sound: &String) {
2112 self.inner.stop(sound);
2113 }
2114
2115 fn set_playing_volume(&mut self, playing: &u32, volume: f32) {
2116 self.inner.set_playing_volume(playing, volume);
2117 }
2118
2119 fn stop_playing(&mut self, playing: u32) {
2120 self.inner.stop_playing(playing);
2121 }
2122
2123 fn pause(&mut self, playing: &u32) -> Result<(), Unsupported> {
2124 self.paused.push(*playing);
2125 Ok(())
2126 }
2127
2128 fn resume(&mut self, playing: &u32) -> Result<(), Unsupported> {
2129 self.paused.retain(|paused| paused != playing);
2130 Ok(())
2131 }
2132 }
2133
2134 fn pausing() -> (Pausing, Player<Pausing>) {
2135 let mut backend = Pausing::default();
2136 backend.inner.ready.push("music/title.ogg".into());
2137 let player = loaded(&mut backend);
2138 (backend, player)
2139 }
2140
2141 #[test]
2142 fn a_backend_that_pauses_holds_its_playbacks_and_their_clock() {
2143 let (mut backend, mut player) = pausing();
2144 let start_rain = SoundRequest::start(handle(), "rain", 1.0);
2145 let music = [cmd(load, "title"), cmd(start, "title")];
2146 player.play(&mut backend, &[start_rain], &music, 1.0);
2147 backend.inner.calls();
2148 player.play(&mut backend, &[], &[], 2.0);
2149 player.suspend(&mut backend);
2150 assert_eq!(backend.paused, [1, 2], "the sound and the track");
2151 assert_eq!(backend.inner.calls(), [] as [String; 0]);
2153 assert_eq!(player.music_playing(50.0), Some(("title", 1.0)));
2155 player.resume(&mut backend);
2156 assert_eq!(backend.paused, [] as [u32; 0]);
2157 assert_eq!(player.music_playing(50.0), Some(("title", 1.0)));
2158 player.play(&mut backend, &[], &[], 50.0);
2159 assert_eq!(player.music_playing(51.5), Some(("title", 2.5)));
2160 assert_eq!(player.take_warnings(), [] as [String; 0]);
2161 }
2162
2163 #[test]
2166 fn a_menus_pause_and_a_suspend_hold_one_playback_once() {
2167 let (mut backend, mut player) = pausing();
2168 let start_rain = SoundRequest::start(handle(), "rain", 1.0);
2169 player.play(&mut backend, &[start_rain], &[], 0.0);
2170 player.play(&mut backend, &[SoundRequest::PauseSounds], &[], 0.0);
2171 assert_eq!(backend.paused, [1]);
2172 player.suspend(&mut backend);
2173 assert_eq!(backend.paused, [1], "not paused twice");
2174 player.play(&mut backend, &[SoundRequest::ResumeSounds], &[], 0.0);
2175 assert_eq!(backend.paused, [1], "still suspended");
2176 player.resume(&mut backend);
2177 assert_eq!(backend.paused, [] as [u32; 0]);
2178 player.suspend(&mut backend);
2180 assert_eq!(backend.paused, [1]);
2181 player.play(&mut backend, &[SoundRequest::PauseSounds], &[], 0.0);
2182 player.resume(&mut backend);
2183 assert_eq!(backend.paused, [1], "the menu holds it still");
2184 player.play(&mut backend, &[SoundRequest::ResumeSounds], &[], 0.0);
2185 assert_eq!(backend.paused, [] as [u32; 0]);
2186 }
2187
2188 proptest! {
2189 #[test]
2191 fn every_variant_gets_picked(seed: u64, n in 1usize..=6) {
2192 let mut rng = VariantRng::new(seed);
2193 let mut seen = vec![false; n];
2194 for _ in 0..200 {
2195 let i = rng.below(n);
2196 prop_assert!(i < n);
2197 seen[i] = true;
2198 }
2199 prop_assert!(seen.iter().all(|&s| s), "{seen:?}");
2200 }
2201 }
2202}