1#![forbid(unsafe_code)]
29
30use std::collections::HashMap;
31#[cfg(not(target_arch = "wasm32"))]
32use std::path::Path;
33use std::sync::Arc;
34
35use anyhow::{Result, anyhow};
36use kira::sound::static_sound::{StaticSoundData, StaticSoundSettings};
37use kira::{AudioManager, AudioManagerSettings, Decibels, DefaultBackend, Frame, Tween};
38
39mod handle;
40use handle::Slot;
41pub use handle::SoundHandle;
42pub use kira;
43
44#[cfg(feature = "synth")]
45mod synth;
46#[cfg(feature = "synth")]
47pub use synth::Synth;
48#[cfg(feature = "synth")]
49pub use synthie;
50
51#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
52pub struct SoundId(pub u32);
53
54#[derive(Debug)]
56#[non_exhaustive]
57pub enum AudioError {
58 NotStarted,
62 UnknownSound(SoundId),
64 Backend(String),
66}
67
68impl std::fmt::Display for AudioError {
69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70 match self {
71 Self::NotStarted => f.write_str("audio not started yet (waiting for a user gesture)"),
72 Self::UnknownSound(id) => write!(f, "unknown SoundId {id:?}"),
73 Self::Backend(e) => write!(f, "kira play: {e}"),
74 }
75 }
76}
77
78impl std::error::Error for AudioError {}
79
80#[derive(Copy, Clone, Debug, PartialEq)]
83pub struct PlayOptions {
84 pub looped: bool,
86 pub volume: f32,
88 pub deferred: bool,
92}
93
94impl Default for PlayOptions {
95 fn default() -> Self {
96 Self {
97 looped: false,
98 volume: 1.0,
99 deferred: false,
100 }
101 }
102}
103
104impl PlayOptions {
105 pub fn looped() -> Self {
107 Self {
108 looped: true,
109 ..Self::default()
110 }
111 }
112
113 pub fn volume(self, volume: f32) -> Self {
115 Self { volume, ..self }
116 }
117
118 pub fn deferred(self) -> Self {
120 Self {
121 deferred: true,
122 ..self
123 }
124 }
125}
126
127pub struct Audio {
128 manager: Option<AudioManager<DefaultBackend>>,
129 sounds: HashMap<SoundId, StaticSoundData>,
130 next_id: u32,
131 master_volume: Decibels,
133 queued: Vec<(StaticSoundData, SoundHandle)>,
135}
136
137impl Audio {
138 pub fn new() -> Result<Self> {
141 let mut audio = Self {
142 manager: None,
143 sounds: HashMap::new(),
144 next_id: 1,
145 master_volume: Decibels::IDENTITY,
146 queued: Vec::new(),
147 };
148 if !cfg!(target_arch = "wasm32") {
149 audio.start()?;
150 }
151 Ok(audio)
152 }
153
154 pub fn start(&mut self) -> Result<()> {
158 if self.manager.is_some() {
159 return Ok(());
160 }
161 let mut manager = AudioManager::<DefaultBackend>::new(AudioManagerSettings::default())
162 .map_err(|e| anyhow!("kira AudioManager init: {e}"))?;
163 manager
164 .main_track()
165 .set_volume(self.master_volume, Tween::default());
166 self.manager = Some(manager);
167 for (data, handle) in std::mem::take(&mut self.queued) {
168 self.start_queued(data, &handle);
169 }
170 Ok(())
171 }
172
173 fn start_queued(&mut self, data: StaticSoundData, handle: &SoundHandle) {
174 let mut slot = handle.lock();
175 let Slot::Pending { volume, paused } = &*slot else {
176 return; };
178 let (volume, paused) = (*volume, *paused);
179 let (data, fade) = match volume {
182 Some((db, duration)) if duration.is_zero() => (data.volume(db), None),
183 Some((db, duration)) => (data, Some((db, duration))),
184 None => (data, None),
185 };
186 match self.play_now(data) {
187 Ok(mut kira) => {
188 if let Some((db, duration)) = fade {
189 kira.set_volume(db, handle::tween(duration));
190 }
191 if paused {
192 kira.pause(Tween::default());
193 }
194 *slot = Slot::Playing(kira);
195 }
196 Err(e) => {
197 log::warn!("queued sound failed to start: {e}");
198 *slot = Slot::Cancelled;
199 }
200 }
201 }
202
203 pub fn is_started(&self) -> bool {
206 self.manager.is_some()
207 }
208
209 #[cfg(not(target_arch = "wasm32"))]
211 pub fn load_sound(&mut self, path: impl AsRef<Path>) -> Result<SoundId> {
212 let data = StaticSoundData::from_file(path.as_ref())
213 .map_err(|e| anyhow!("kira load {}: {e}", path.as_ref().display()))?;
214 Ok(self.insert(data))
215 }
216
217 pub fn load_sound_bytes(&mut self, bytes: impl Into<Vec<u8>>) -> Result<SoundId> {
219 let data = StaticSoundData::from_cursor(std::io::Cursor::new(bytes.into()))
220 .map_err(|e| anyhow!("kira decode: {e}"))?;
221 Ok(self.insert(data))
222 }
223
224 fn insert(&mut self, data: StaticSoundData) -> SoundId {
225 let id = SoundId(self.next_id);
226 self.next_id += 1;
227 self.sounds.insert(id, data);
228 id
229 }
230
231 pub fn play(&mut self, id: SoundId) -> Result<SoundHandle, AudioError> {
239 self.play_with(id, PlayOptions::default())
240 }
241
242 pub fn play_with(
245 &mut self,
246 id: SoundId,
247 options: PlayOptions,
248 ) -> Result<SoundHandle, AudioError> {
249 let data = self
250 .sounds
251 .get(&id)
252 .ok_or(AudioError::UnknownSound(id))?
253 .clone();
254 self.play_data(data, options)
255 }
256
257 pub fn play_samples(
261 &mut self,
262 samples: &[f32],
263 sample_rate: u32,
264 ) -> Result<SoundHandle, AudioError> {
265 self.play_samples_with(samples, sample_rate, PlayOptions::default())
266 }
267
268 pub fn play_samples_with(
270 &mut self,
271 samples: &[f32],
272 sample_rate: u32,
273 options: PlayOptions,
274 ) -> Result<SoundHandle, AudioError> {
275 let frames: Arc<[Frame]> = samples
276 .iter()
277 .map(|&s| Frame { left: s, right: s })
278 .collect();
279 let data = StaticSoundData {
280 sample_rate,
281 frames,
282 settings: StaticSoundSettings::default(),
283 slice: None,
284 };
285 self.play_data(data, options)
286 }
287
288 fn play_data(
289 &mut self,
290 data: StaticSoundData,
291 options: PlayOptions,
292 ) -> Result<SoundHandle, AudioError> {
293 let mut data = data.volume(amplitude_to_db(options.volume));
294 if options.looped {
295 data = data.loop_region(..);
296 }
297 if self.manager.is_none() && options.deferred {
298 let handle = SoundHandle::pending();
299 self.queued.push((data, handle.clone()));
300 return Ok(handle);
301 }
302 self.play_now(data).map(SoundHandle::playing)
303 }
304
305 fn play_now(
306 &mut self,
307 data: StaticSoundData,
308 ) -> Result<kira::sound::static_sound::StaticSoundHandle, AudioError> {
309 self.manager
310 .as_mut()
311 .ok_or(AudioError::NotStarted)?
312 .play(data)
313 .map_err(|e| AudioError::Backend(e.to_string()))
314 }
315
316 pub fn set_master_volume(&mut self, volume: f32) {
319 self.master_volume = amplitude_to_db(volume);
320 if let Some(manager) = self.manager.as_mut() {
321 manager
322 .main_track()
323 .set_volume(self.master_volume, Tween::default());
324 }
325 }
326}
327
328pub(crate) fn amplitude_to_db(volume: f32) -> Decibels {
330 if volume <= 0.0 {
331 Decibels::SILENCE
332 } else {
333 Decibels::from(20.0 * volume.log10())
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340
341 #[test]
342 fn amplitude_to_db_maps_unity_and_silence() {
343 assert_eq!(amplitude_to_db(1.0), Decibels::IDENTITY);
344 assert_eq!(amplitude_to_db(0.0), Decibels::SILENCE);
345 assert!((amplitude_to_db(0.5).0 + 6.02).abs() < 0.01);
346 }
347
348 #[test]
349 fn play_options_builders() {
350 let o = PlayOptions::looped().volume(0.25).deferred();
351 assert!(o.looped && o.deferred);
352 assert_eq!(o.volume, 0.25);
353 assert_eq!(PlayOptions::default().volume, 1.0);
354 }
355}