veloren_voxygen/audio/
soundcache.rs1use common::assets::{AssetExt, BoxedError, FileAsset};
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
127#[derive(Clone)]
128struct OggSound(StaticSoundData);
129
130#[derive(Clone)]
131struct StreamedOggSound(Arc<[u8]>);
132
133impl FileAsset for OggSound {
134 const EXTENSION: &'static str = "ogg";
135
136 fn from_bytes(bytes: Cow<[u8]>) -> Result<Self, BoxedError> {
137 let source = StaticSoundData::from_cursor(io::Cursor::new(bytes.into_owned()))?;
138 Ok(OggSound(source))
139 }
140}
141
142impl FileAsset for StreamedOggSound {
143 const EXTENSION: &'static str = "ogg";
144
145 fn from_bytes(bytes: Cow<[u8]>) -> Result<Self, BoxedError> {
146 Ok(StreamedOggSound(Arc::from(bytes)))
148 }
149}
150
151impl OggSound {
153 pub fn empty() -> OggSound {
154 OggSound::from_bytes(Cow::Borrowed(include_bytes!(
155 "../../../assets/voxygen/audio/null.ogg"
156 )))
157 .unwrap()
158 }
159}
160
161pub fn load_ogg(specifier: &str, streamed: bool) -> AnySoundData {
162 if streamed {
163 match StreamedOggSound::load(specifier) {
164 Ok(handle) => StreamingSoundData::from_cursor(Cursor::new(handle.cloned().0))
165 .map_or_else(
166 |error| {
167 warn!(?error, "Error while creating streaming sound data");
168 AnySoundData::Static(OggSound::empty().0)
169 },
170 AnySoundData::Streaming,
171 ),
172
173 Err(error) => {
174 warn!(?specifier, ?error, "Failed to load sound");
175 AnySoundData::Static(OggSound::empty().0)
176 },
177 }
178 } else {
179 AnySoundData::Static(
180 OggSound::load_or_insert_with(specifier, |error| {
181 warn!(?specifier, ?error, "Failed to load sound");
182 OggSound::empty()
183 })
184 .cloned()
185 .0,
186 )
187 }
188}