1#![forbid(unsafe_code)]
33
34use std::collections::HashMap;
35#[cfg(not(target_arch = "wasm32"))]
36use std::path::Path;
37use std::sync::Arc;
38
39use anyhow::{Result, anyhow};
40use kira::sound::static_sound::{StaticSoundData, StaticSoundSettings};
41use kira::{AudioManager, AudioManagerSettings, Decibels, DefaultBackend, Frame, Tween};
42
43mod handle;
44use handle::Slot;
45pub use handle::SoundHandle;
46mod stream;
47pub use kira;
48use stream::StreamData;
49pub use stream::{AudioStream, StreamHandle};
50
51#[cfg(feature = "synth")]
52mod music;
53#[cfg(feature = "synth")]
54use music::PreparedSynth;
55#[cfg(feature = "synth")]
56pub use music::SynthHandle;
57#[cfg(feature = "synth")]
58pub mod synth;
59#[cfg(feature = "synth")]
60pub use synth::Synth;
61#[cfg(feature = "synth")]
62pub use synthie;
63
64#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
65pub struct SoundId(pub u32);
66
67#[derive(Debug)]
69#[non_exhaustive]
70pub enum AudioError {
71 NotStarted,
75 UnknownSound(SoundId),
77 Backend(String),
79}
80
81impl std::fmt::Display for AudioError {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 match self {
84 Self::NotStarted => f.write_str("audio not started yet (waiting for a user gesture)"),
85 Self::UnknownSound(id) => write!(f, "unknown SoundId {id:?}"),
86 Self::Backend(e) => write!(f, "kira play: {e}"),
87 }
88 }
89}
90
91impl std::error::Error for AudioError {}
92
93#[derive(Copy, Clone, Debug, PartialEq)]
96pub struct PlayOptions {
97 pub looped: bool,
99 pub volume: f32,
101 pub deferred: bool,
105}
106
107impl Default for PlayOptions {
108 fn default() -> Self {
109 Self {
110 looped: false,
111 volume: 1.0,
112 deferred: false,
113 }
114 }
115}
116
117impl PlayOptions {
118 pub fn looped() -> Self {
120 Self {
121 looped: true,
122 ..Self::default()
123 }
124 }
125
126 pub fn volume(self, volume: f32) -> Self {
128 Self { volume, ..self }
129 }
130
131 pub fn deferred(self) -> Self {
133 Self {
134 deferred: true,
135 ..self
136 }
137 }
138}
139
140pub struct Audio {
141 manager: Option<AudioManager<DefaultBackend>>,
142 sounds: HashMap<SoundId, StaticSoundData>,
143 next_id: u32,
144 master_volume: Decibels,
146 queued: Vec<(StaticSoundData, SoundHandle)>,
148 queued_streams: Vec<StreamData>,
150 #[cfg(feature = "synth")]
152 queued_synths: Vec<PreparedSynth>,
153}
154
155impl Audio {
156 pub fn new() -> Result<Self> {
159 let mut audio = Self {
160 manager: None,
161 sounds: HashMap::new(),
162 next_id: 1,
163 master_volume: Decibels::IDENTITY,
164 queued: Vec::new(),
165 queued_streams: Vec::new(),
166 #[cfg(feature = "synth")]
167 queued_synths: Vec::new(),
168 };
169 if !cfg!(target_arch = "wasm32") {
170 audio.start()?;
171 }
172 Ok(audio)
173 }
174
175 pub fn start(&mut self) -> Result<()> {
179 if self.manager.is_some() {
180 return Ok(());
181 }
182 let mut manager = AudioManager::<DefaultBackend>::new(AudioManagerSettings::default())
183 .map_err(|e| anyhow!("kira AudioManager init: {e}"))?;
184 manager
185 .main_track()
186 .set_volume(self.master_volume, Tween::default());
187 self.manager = Some(manager);
188 for (data, handle) in std::mem::take(&mut self.queued) {
189 self.start_queued(data, &handle);
190 }
191 for data in std::mem::take(&mut self.queued_streams) {
192 if !data.handle().is_finished() {
193 self.start_stream(data);
194 }
195 }
196 #[cfg(feature = "synth")]
197 for prepared in std::mem::take(&mut self.queued_synths) {
198 if !prepared.handle().is_finished() {
199 self.start_synth(prepared);
200 }
201 }
202 Ok(())
203 }
204
205 fn start_queued(&mut self, data: StaticSoundData, handle: &SoundHandle) {
206 let mut slot = handle.lock();
207 let Slot::Pending { volume, paused } = &*slot else {
208 return; };
210 let (volume, paused) = (*volume, *paused);
211 let (data, fade) = match volume {
214 Some((db, duration)) if duration.is_zero() => (data.volume(db), None),
215 Some((db, duration)) => (data, Some((db, duration))),
216 None => (data, None),
217 };
218 match self.play_now(data) {
219 Ok(mut kira) => {
220 if let Some((db, duration)) = fade {
221 kira.set_volume(db, handle::tween(duration));
222 }
223 if paused {
224 kira.pause(Tween::default());
225 }
226 *slot = Slot::Playing(kira);
227 }
228 Err(e) => {
229 log::warn!("queued sound failed to start: {e}");
230 *slot = Slot::Cancelled;
231 }
232 }
233 }
234
235 pub fn is_started(&self) -> bool {
238 self.manager.is_some()
239 }
240
241 #[cfg(not(target_arch = "wasm32"))]
243 pub fn load_sound(&mut self, path: impl AsRef<Path>) -> Result<SoundId> {
244 let data = StaticSoundData::from_file(path.as_ref())
245 .map_err(|e| anyhow!("kira load {}: {e}", path.as_ref().display()))?;
246 Ok(self.insert(data))
247 }
248
249 pub fn load_sound_bytes(&mut self, bytes: impl Into<Vec<u8>>) -> Result<SoundId> {
251 let data = StaticSoundData::from_cursor(std::io::Cursor::new(bytes.into()))
252 .map_err(|e| anyhow!("kira decode: {e}"))?;
253 Ok(self.insert(data))
254 }
255
256 fn insert(&mut self, data: StaticSoundData) -> SoundId {
257 let id = SoundId(self.next_id);
258 self.next_id += 1;
259 self.sounds.insert(id, data);
260 id
261 }
262
263 pub fn play(&mut self, id: SoundId) -> Result<SoundHandle, AudioError> {
271 self.play_with(id, PlayOptions::default())
272 }
273
274 pub fn play_with(
277 &mut self,
278 id: SoundId,
279 options: PlayOptions,
280 ) -> Result<SoundHandle, AudioError> {
281 let data = self
282 .sounds
283 .get(&id)
284 .ok_or(AudioError::UnknownSound(id))?
285 .clone();
286 self.play_data(data, options)
287 }
288
289 pub fn play_samples(
293 &mut self,
294 samples: &[f32],
295 sample_rate: u32,
296 ) -> Result<SoundHandle, AudioError> {
297 self.play_samples_with(samples, sample_rate, PlayOptions::default())
298 }
299
300 pub fn play_samples_with(
302 &mut self,
303 samples: &[f32],
304 sample_rate: u32,
305 options: PlayOptions,
306 ) -> Result<SoundHandle, AudioError> {
307 let frames: Arc<[Frame]> = samples
308 .iter()
309 .map(|&s| Frame { left: s, right: s })
310 .collect();
311 let data = StaticSoundData {
312 sample_rate,
313 frames,
314 settings: StaticSoundSettings::default(),
315 slice: None,
316 };
317 self.play_data(data, options)
318 }
319
320 fn play_data(
321 &mut self,
322 data: StaticSoundData,
323 options: PlayOptions,
324 ) -> Result<SoundHandle, AudioError> {
325 let mut data = data.volume(amplitude_to_db(options.volume));
326 if options.looped {
327 data = data.loop_region(..);
328 }
329 if self.manager.is_none() && options.deferred {
330 let handle = SoundHandle::pending();
331 self.queued.push((data, handle.clone()));
332 return Ok(handle);
333 }
334 self.play_now(data).map(SoundHandle::playing)
335 }
336
337 fn play_now(
338 &mut self,
339 data: StaticSoundData,
340 ) -> Result<kira::sound::static_sound::StaticSoundHandle, AudioError> {
341 self.manager
342 .as_mut()
343 .ok_or(AudioError::NotStarted)?
344 .play(data)
345 .map_err(|e| AudioError::Backend(e.to_string()))
346 }
347
348 pub fn play_stream(
353 &mut self,
354 stream: impl AudioStream,
355 sample_rate: u32,
356 ) -> Result<StreamHandle, AudioError> {
357 self.play_stream_with(stream, sample_rate, PlayOptions::default())
358 }
359
360 pub fn play_stream_with(
363 &mut self,
364 stream: impl AudioStream,
365 sample_rate: u32,
366 options: PlayOptions,
367 ) -> Result<StreamHandle, AudioError> {
368 let pending = self.manager.is_none();
369 if pending && !options.deferred {
370 return Err(AudioError::NotStarted);
371 }
372 let (data, handle) =
373 StreamData::new(Box::new(stream), sample_rate, options.volume, pending);
374 if pending {
375 self.queued_streams.push(data);
376 } else {
377 self.start_stream(data);
378 }
379 Ok(handle)
380 }
381
382 fn start_stream(&mut self, data: StreamData) {
383 let handle = data.handle().clone();
384 let Some(manager) = self.manager.as_mut() else {
385 return;
386 };
387 match manager.play(data) {
388 Ok(()) => handle.mark_started(),
389 Err(e) => {
390 log::warn!("stream failed to start: {e}");
391 handle.stop();
392 }
393 }
394 }
395
396 #[cfg(feature = "synth")]
422 pub fn play_synth(
423 &mut self,
424 mut song: synth::Song,
425 options: PlayOptions,
426 ) -> Result<SynthHandle, AudioError> {
427 song.looped = options.looped;
428 let data =
429 synthie::kira::SynthSoundData::new().with_sequencer(synth::SongPlayer::new(song));
430 self.play_synth_sound(data, options)
431 }
432
433 #[cfg(feature = "synth")]
438 pub fn play_synth_sound<S, const N: usize>(
439 &mut self,
440 data: synthie::kira::SynthSoundData<S, N>,
441 options: PlayOptions,
442 ) -> Result<SynthHandle, AudioError>
443 where
444 S: synthie::prelude::Sequencer + Send + 'static,
445 {
446 let pending = self.manager.is_none();
447 if pending && !options.deferred {
448 return Err(AudioError::NotStarted);
449 }
450 let data = data.with_volume(amplitude_to_db(options.volume));
451 let prepared = PreparedSynth::new(data, pending);
452 let handle = prepared.handle().clone();
453 if pending {
454 self.queued_synths.push(prepared);
455 } else {
456 self.start_synth(prepared);
457 }
458 Ok(handle)
459 }
460
461 #[cfg(feature = "synth")]
462 fn start_synth(&mut self, prepared: PreparedSynth) {
463 let handle = prepared.handle().clone();
464 let Some(manager) = self.manager.as_mut() else {
465 return;
466 };
467 match manager.play(prepared) {
468 Ok(()) => handle.mark_started(),
469 Err(e) => {
470 log::warn!("synth failed to start: {e}");
471 handle.stop();
472 }
473 }
474 }
475
476 pub fn set_master_volume(&mut self, volume: f32) {
479 self.master_volume = amplitude_to_db(volume);
480 if let Some(manager) = self.manager.as_mut() {
481 manager
482 .main_track()
483 .set_volume(self.master_volume, Tween::default());
484 }
485 }
486}
487
488pub(crate) fn amplitude_to_db(volume: f32) -> Decibels {
490 if volume <= 0.0 {
491 Decibels::SILENCE
492 } else {
493 Decibels::from(20.0 * volume.log10())
494 }
495}
496
497#[cfg(test)]
498mod tests {
499 use super::*;
500
501 #[test]
502 fn amplitude_to_db_maps_unity_and_silence() {
503 assert_eq!(amplitude_to_db(1.0), Decibels::IDENTITY);
504 assert_eq!(amplitude_to_db(0.0), Decibels::SILENCE);
505 assert!((amplitude_to_db(0.5).0 + 6.02).abs() < 0.01);
506 }
507
508 #[test]
509 fn play_options_builders() {
510 let o = PlayOptions::looped().volume(0.25).deferred();
511 assert!(o.looped && o.deferred);
512 assert_eq!(o.volume, 0.25);
513 assert_eq!(PlayOptions::default().volume, 1.0);
514 }
515}