veloren_voxygen/audio/
soundcache.rs1use 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
19pub 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 Ok(StreamedOggSound(Arc::from(content.to_vec())))
161 }
162}
163
164impl 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}