bones_framework/audio/
audio_manager.rs

1//! Audio Manager resource and systems.
2
3use crate::prelude::*;
4use kira;
5use kira::{
6    manager::{
7        backend::{cpal::CpalBackend, mock::MockBackend, Backend},
8        AudioManager as KiraAudioManager,
9    },
10    sound::{static_sound::StaticSoundData, SoundData},
11};
12use std::io::Cursor;
13
14/// The audio manager resource which can be used to play sounds.
15#[derive(HasSchema, Deref, DerefMut)]
16#[schema(no_clone)]
17pub struct AudioManager(KiraAudioManager<CpalWithFallbackBackend>);
18
19impl Default for AudioManager {
20    fn default() -> Self {
21        Self(KiraAudioManager::<CpalWithFallbackBackend>::new(default()).unwrap())
22    }
23}
24
25/// Kira audio backend that will fall back to a dummy backend if setting up the Cpal backend
26/// fails with an error.
27#[allow(clippy::large_enum_variant)]
28pub enum CpalWithFallbackBackend {
29    /// This is a working Cpal backend.
30    Cpal(CpalBackend),
31    /// This is a dummy backend since Cpal didn't work.
32    Dummy(MockBackend),
33}
34
35impl Backend for CpalWithFallbackBackend {
36    type Settings = <CpalBackend as Backend>::Settings;
37    type Error = <CpalBackend as Backend>::Error;
38
39    fn setup(settings: Self::Settings) -> Result<(Self, u32), Self::Error> {
40        match CpalBackend::setup(settings) {
41            Ok((back, bit)) => Ok((Self::Cpal(back), bit)),
42            Err(e) => {
43                tracing::error!("Error starting audio backend, using dummy backend instead: {e}");
44                Ok(MockBackend::setup(default())
45                    .map(|(back, bit)| (Self::Dummy(back), bit))
46                    .unwrap())
47            }
48        }
49    }
50
51    fn start(&mut self, renderer: kira::manager::backend::Renderer) -> Result<(), Self::Error> {
52        match self {
53            CpalWithFallbackBackend::Cpal(cpal) => cpal.start(renderer),
54            CpalWithFallbackBackend::Dummy(dummy) => {
55                dummy.start(renderer).unwrap();
56                Ok(())
57            }
58        }
59    }
60}
61
62/// The audio source asset type, contains no data, but [`Handle<AudioSource>`] is still useful
63/// because it uniquely represents a sound/music that may be played outside of bones.
64#[derive(Clone, HasSchema, Debug, Deref, DerefMut)]
65#[schema(no_default)]
66#[type_data(asset_loader(["ogg", "mp3", "flac", "wav"], AudioLoader))]
67pub struct AudioSource(pub StaticSoundData);
68
69impl SoundData for &AudioSource {
70    type Error = <StaticSoundData as SoundData>::Error;
71    type Handle = <StaticSoundData as SoundData>::Handle;
72
73    fn into_sound(self) -> Result<(Box<dyn kira::sound::Sound>, Self::Handle), Self::Error> {
74        self.0.clone().into_sound()
75    }
76}
77
78/// The audio file asset loader.
79pub struct AudioLoader;
80impl AssetLoader for AudioLoader {
81    fn load(&self, _ctx: AssetLoadCtx, bytes: &[u8]) -> BoxedFuture<anyhow::Result<SchemaBox>> {
82        let bytes = bytes.to_vec();
83        Box::pin(async move {
84            let data = StaticSoundData::from_cursor(Cursor::new(bytes))?;
85            Ok(SchemaBox::new(AudioSource(data)))
86        })
87    }
88}