veloren_voxygen/audio/
soundcache.rs

1//! Handles caching and retrieval of decoded `.ogg` sfx sound data, eliminating
2//! the need to decode files on each playback
3use common::assets::{self, AssetExt, Loader};
4use kira::{
5    Decibels, StartTime, Tween, Value,
6    sound::{
7        FromFileError, IntoOptionalRegion, PlaybackState, SoundData,
8        static_sound::{StaticSoundData, StaticSoundHandle},
9        streaming::{StreamingSoundData, StreamingSoundHandle},
10    },
11};
12use std::{
13    borrow::Cow,
14    io::{self, Cursor},
15    sync::Arc,
16};
17use tracing::warn;
18
19// Kira does not provide a generic interface over sound data and sound handles,
20// but we want to use both streaming and static sound data for music and sfx.
21//
22// To work around this, here's a small wrapper exposing the functionality for
23// both audio data types.
24
25pub enum AnySoundData {
26    Static(StaticSoundData),
27    Streaming(StreamingSoundData<FromFileError>),
28}
29
30#[derive(Debug)]
31pub enum AnySoundError {
32    Static(<StaticSoundData as SoundData>::Error),
33    Streaming(<StreamingSoundData<FromFileError> as SoundData>::Error),
34}
35
36impl SoundData for AnySoundData {
37    type Error = AnySoundError;
38    type Handle = AnySoundHandle;
39
40    fn into_sound(self) -> Result<(Box<dyn kira::sound::Sound>, Self::Handle), Self::Error> {
41        match self {
42            AnySoundData::Static(data) => <StaticSoundData as SoundData>::into_sound(data)
43                .map(|(sound, handle)| (sound, AnySoundHandle::Static(handle)))
44                .map_err(AnySoundError::Static),
45            AnySoundData::Streaming(data) => {
46                <StreamingSoundData<FromFileError> as SoundData>::into_sound(data)
47                    .map(|(sound, handle)| (sound, AnySoundHandle::Streaming(handle)))
48                    .map_err(AnySoundError::Streaming)
49            },
50        }
51    }
52}
53
54impl AnySoundData {
55    pub fn fade_in_tween(self, fade_in_tween: impl Into<Option<Tween>>) -> Self {
56        match self {
57            AnySoundData::Static(d) => AnySoundData::Static(d.fade_in_tween(fade_in_tween)),
58            AnySoundData::Streaming(d) => AnySoundData::Streaming(d.fade_in_tween(fade_in_tween)),
59        }
60    }
61
62    pub fn start_time(self, start_time: impl Into<StartTime>) -> Self {
63        match self {
64            AnySoundData::Static(d) => AnySoundData::Static(d.start_time(start_time)),
65            AnySoundData::Streaming(d) => AnySoundData::Streaming(d.start_time(start_time)),
66        }
67    }
68
69    pub fn volume(self, volume: impl Into<Value<Decibels>>) -> Self {
70        match self {
71            AnySoundData::Static(d) => AnySoundData::Static(d.volume(volume)),
72            AnySoundData::Streaming(d) => AnySoundData::Streaming(d.volume(volume)),
73        }
74    }
75
76    pub fn loop_region(self, loop_region: impl IntoOptionalRegion) -> Self {
77        match self {
78            AnySoundData::Static(d) => AnySoundData::Static(d.loop_region(loop_region)),
79            AnySoundData::Streaming(d) => AnySoundData::Streaming(d.loop_region(loop_region)),
80        }
81    }
82}
83
84#[derive(Debug)]
85pub enum AnySoundHandle {
86    Static(StaticSoundHandle),
87    Streaming(StreamingSoundHandle<FromFileError>),
88}
89
90impl AnySoundHandle {
91    pub fn state(&self) -> PlaybackState {
92        match self {
93            AnySoundHandle::Static(h) => h.state(),
94            AnySoundHandle::Streaming(h) => h.state(),
95        }
96    }
97
98    pub fn position(&self) -> f64 {
99        match self {
100            AnySoundHandle::Static(h) => h.position(),
101            AnySoundHandle::Streaming(h) => h.position(),
102        }
103    }
104
105    pub fn set_volume(&mut self, volume: impl Into<Value<Decibels>>, tween: Tween) {
106        match self {
107            AnySoundHandle::Static(h) => h.set_volume(volume, tween),
108            AnySoundHandle::Streaming(h) => h.set_volume(volume, tween),
109        }
110    }
111
112    pub fn stop(&mut self, tween: Tween) {
113        match self {
114            AnySoundHandle::Static(h) => h.stop(tween),
115            AnySoundHandle::Streaming(h) => h.stop(tween),
116        }
117    }
118
119    pub fn set_loop_region(&mut self, loop_region: impl IntoOptionalRegion) {
120        match self {
121            AnySoundHandle::Static(h) => h.set_loop_region(loop_region),
122            AnySoundHandle::Streaming(h) => h.set_loop_region(loop_region),
123        }
124    }
125}
126
127struct SoundLoader;
128#[derive(Clone)]
129struct OggSound(StaticSoundData);
130
131struct StreamedSoundLoader;
132#[derive(Clone)]
133struct StreamedOggSound(Arc<[u8]>);
134
135impl Loader<OggSound> for SoundLoader {
136    fn load(content: Cow<[u8]>, _: &str) -> Result<OggSound, assets::BoxedError> {
137        let source = StaticSoundData::from_cursor(io::Cursor::new(content.into_owned()))?;
138        Ok(OggSound(source))
139    }
140}
141
142impl assets::Asset for OggSound {
143    type Loader = SoundLoader;
144
145    const EXTENSION: &'static str = "ogg";
146}
147
148impl assets::Asset for StreamedOggSound {
149    type Loader = StreamedSoundLoader;
150
151    const EXTENSION: &'static str = "ogg";
152}
153
154impl Loader<StreamedOggSound> for StreamedSoundLoader {
155    fn load(
156        content: Cow<[u8]>,
157        _ext: &str,
158    ) -> Result<StreamedOggSound, common::assets::BoxedError> {
159        // Store the raw file contents to be streamed later
160        Ok(StreamedOggSound(Arc::from(content.to_vec())))
161    }
162}
163
164/// Wrapper for decoded audio data
165impl OggSound {
166    pub fn empty() -> OggSound {
167        SoundLoader::load(
168            Cow::Borrowed(include_bytes!("../../../assets/voxygen/audio/null.ogg")),
169            "ogg",
170        )
171        .unwrap()
172    }
173}
174
175pub fn load_ogg(specifier: &str, streamed: bool) -> AnySoundData {
176    if streamed {
177        match StreamedOggSound::load(specifier) {
178            Ok(handle) => StreamingSoundData::from_cursor(Cursor::new(handle.cloned().0))
179                .map_or_else(
180                    |error| {
181                        warn!(?error, "Error while creating streaming sound data");
182                        AnySoundData::Static(OggSound::empty().0)
183                    },
184                    AnySoundData::Streaming,
185                ),
186
187            Err(error) => {
188                warn!(?specifier, ?error, "Failed to load sound");
189                AnySoundData::Static(OggSound::empty().0)
190            },
191        }
192    } else {
193        AnySoundData::Static(
194            OggSound::load_or_insert_with(specifier, |error| {
195                warn!(?specifier, ?error, "Failed to load sound");
196                OggSound::empty()
197            })
198            .cloned()
199            .0,
200        )
201    }
202}