1#![forbid(unsafe_code)]
38
39use std::collections::HashMap;
40#[cfg(not(target_arch = "wasm32"))]
41use std::path::Path;
42use std::sync::Arc;
43
44use anyhow::{Result, anyhow};
45use kira::sound::static_sound::{StaticSoundData, StaticSoundSettings};
46use kira::track::{TrackBuilder, TrackHandle};
47use kira::{AudioManager, AudioManagerSettings, Decibels, Frame, Tween};
48
49mod handle;
50use handle::Slot;
51pub use handle::SoundHandle;
52mod stream;
53pub use kira;
54pub use stream::{AudioStream, StereoStream, StreamHandle};
55use stream::{Source, StreamData};
56
57#[cfg(target_arch = "wasm32")]
58mod web;
59#[cfg(target_arch = "wasm32")]
60pub use web::DetachHandle;
61
62#[cfg(target_arch = "wasm32")]
63type Backend = web::WebBackend;
64#[cfg(not(target_arch = "wasm32"))]
65type Backend = kira::DefaultBackend;
66
67#[cfg(feature = "synth")]
68mod music;
69#[cfg(feature = "synth")]
70use music::PreparedSynth;
71#[cfg(feature = "synth")]
72pub use music::SynthHandle;
73#[cfg(feature = "synth")]
74pub mod synth;
75#[cfg(feature = "synth")]
76pub use synth::Synth;
77#[cfg(feature = "synth")]
78pub use synthie;
79
80#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
81pub struct SoundId(pub u32);
82
83#[derive(Debug)]
85#[non_exhaustive]
86pub enum AudioError {
87 NotStarted,
91 UnknownSound(SoundId),
93 Backend(String),
95}
96
97impl std::fmt::Display for AudioError {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 match self {
100 Self::NotStarted => f.write_str("audio not started yet (waiting for a user gesture)"),
101 Self::UnknownSound(id) => write!(f, "unknown SoundId {id:?}"),
102 Self::Backend(e) => write!(f, "kira play: {e}"),
103 }
104 }
105}
106
107impl std::error::Error for AudioError {}
108
109#[derive(Copy, Clone, Debug, PartialEq)]
112pub struct PlayOptions {
113 pub looped: bool,
115 pub volume: f32,
117 pub deferred: bool,
121}
122
123impl Default for PlayOptions {
124 fn default() -> Self {
125 Self {
126 looped: false,
127 volume: 1.0,
128 deferred: false,
129 }
130 }
131}
132
133impl PlayOptions {
134 pub fn looped() -> Self {
136 Self {
137 looped: true,
138 ..Self::default()
139 }
140 }
141
142 pub fn volume(self, volume: f32) -> Self {
144 Self { volume, ..self }
145 }
146
147 pub fn deferred(self) -> Self {
149 Self {
150 deferred: true,
151 ..self
152 }
153 }
154}
155
156struct Manager {
158 kira: AudioManager<Backend>,
159 bus: TrackHandle,
162}
163
164pub struct Audio {
165 manager: Option<Manager>,
166 suspended: bool,
168 #[cfg(target_arch = "wasm32")]
169 detach: DetachHandle,
170 sounds: HashMap<SoundId, StaticSoundData>,
171 next_id: u32,
172 master_volume: Decibels,
174 queued: Vec<(StaticSoundData, SoundHandle)>,
176 queued_streams: Vec<StreamData>,
178 #[cfg(feature = "synth")]
180 queued_synths: Vec<PreparedSynth>,
181}
182
183impl Audio {
184 pub fn new() -> Result<Self> {
187 let mut audio = Self {
188 manager: None,
189 suspended: false,
190 #[cfg(target_arch = "wasm32")]
191 detach: DetachHandle::default(),
192 sounds: HashMap::new(),
193 next_id: 1,
194 master_volume: Decibels::IDENTITY,
195 queued: Vec::new(),
196 queued_streams: Vec::new(),
197 #[cfg(feature = "synth")]
198 queued_synths: Vec::new(),
199 };
200 if !cfg!(target_arch = "wasm32") {
201 audio.start()?;
202 }
203 Ok(audio)
204 }
205
206 pub fn start(&mut self) -> Result<()> {
210 if self.manager.is_some() {
211 return Ok(());
212 }
213 #[cfg_attr(not(target_arch = "wasm32"), allow(unused_mut))]
214 let mut settings = AudioManagerSettings::<Backend>::default();
215 #[cfg(target_arch = "wasm32")]
216 {
217 settings.backend_settings = self.detach.clone();
218 }
219 let mut kira = AudioManager::<Backend>::new(settings)
220 .map_err(|e| anyhow!("kira AudioManager init: {e}"))?;
221 kira.main_track()
222 .set_volume(self.master_volume, Tween::default());
223 let mut bus = kira
224 .add_sub_track(TrackBuilder::new())
225 .map_err(|e| anyhow!("kira bus track: {e}"))?;
226 if self.suspended {
227 bus.pause(Tween::default());
228 }
229 self.manager = Some(Manager { kira, bus });
230 for (data, handle) in std::mem::take(&mut self.queued) {
231 self.start_queued(data, &handle);
232 }
233 for data in std::mem::take(&mut self.queued_streams) {
234 if !data.handle().is_finished() {
235 self.start_stream(data);
236 }
237 }
238 #[cfg(feature = "synth")]
239 for prepared in std::mem::take(&mut self.queued_synths) {
240 if !prepared.handle().is_finished() {
241 self.start_synth(prepared);
242 }
243 }
244 Ok(())
245 }
246
247 fn start_queued(&mut self, data: StaticSoundData, handle: &SoundHandle) {
248 let mut slot = handle.lock();
249 let Slot::Pending { volume, paused } = &*slot else {
250 return; };
252 let (volume, paused) = (*volume, *paused);
253 let (data, fade) = match volume {
256 Some((db, duration)) if duration.is_zero() => (data.volume(db), None),
257 Some((db, duration)) => (data, Some((db, duration))),
258 None => (data, None),
259 };
260 match self.play_now(data) {
261 Ok(mut kira) => {
262 if let Some((db, duration)) = fade {
263 kira.set_volume(db, handle::tween(duration));
264 }
265 if paused {
266 kira.pause(Tween::default());
267 }
268 *slot = Slot::Playing(kira);
269 }
270 Err(e) => {
271 log::warn!("queued sound failed to start: {e}");
272 *slot = Slot::Cancelled;
273 }
274 }
275 }
276
277 pub fn is_started(&self) -> bool {
280 self.manager.is_some()
281 }
282
283 #[cfg(not(target_arch = "wasm32"))]
285 pub fn load_sound(&mut self, path: impl AsRef<Path>) -> Result<SoundId> {
286 let data = StaticSoundData::from_file(path.as_ref())
287 .map_err(|e| anyhow!("kira load {}: {e}", path.as_ref().display()))?;
288 Ok(self.insert(data))
289 }
290
291 pub fn load_sound_bytes(&mut self, bytes: impl Into<Vec<u8>>) -> Result<SoundId> {
293 let data = StaticSoundData::from_cursor(std::io::Cursor::new(bytes.into()))
294 .map_err(|e| anyhow!("kira decode: {e}"))?;
295 Ok(self.insert(data))
296 }
297
298 fn insert(&mut self, data: StaticSoundData) -> SoundId {
299 let id = SoundId(self.next_id);
300 self.next_id += 1;
301 self.sounds.insert(id, data);
302 id
303 }
304
305 pub fn play(&mut self, id: SoundId) -> Result<SoundHandle, AudioError> {
313 self.play_with(id, PlayOptions::default())
314 }
315
316 pub fn play_with(
319 &mut self,
320 id: SoundId,
321 options: PlayOptions,
322 ) -> Result<SoundHandle, AudioError> {
323 let data = self
324 .sounds
325 .get(&id)
326 .ok_or(AudioError::UnknownSound(id))?
327 .clone();
328 self.play_data(data, options)
329 }
330
331 pub fn play_samples(
335 &mut self,
336 samples: &[f32],
337 sample_rate: u32,
338 ) -> Result<SoundHandle, AudioError> {
339 self.play_samples_with(samples, sample_rate, PlayOptions::default())
340 }
341
342 pub fn play_samples_with(
344 &mut self,
345 samples: &[f32],
346 sample_rate: u32,
347 options: PlayOptions,
348 ) -> Result<SoundHandle, AudioError> {
349 let frames: Arc<[Frame]> = samples
350 .iter()
351 .map(|&s| Frame { left: s, right: s })
352 .collect();
353 let data = StaticSoundData {
354 sample_rate,
355 frames,
356 settings: StaticSoundSettings::default(),
357 slice: None,
358 };
359 self.play_data(data, options)
360 }
361
362 fn play_data(
363 &mut self,
364 data: StaticSoundData,
365 options: PlayOptions,
366 ) -> Result<SoundHandle, AudioError> {
367 let mut data = data.volume(amplitude_to_db(options.volume));
368 if options.looped {
369 data = data.loop_region(..);
370 }
371 if self.manager.is_none() && options.deferred {
372 let handle = SoundHandle::pending();
373 self.queued.push((data, handle.clone()));
374 return Ok(handle);
375 }
376 self.play_now(data).map(SoundHandle::playing)
377 }
378
379 fn play_now(
380 &mut self,
381 data: StaticSoundData,
382 ) -> Result<kira::sound::static_sound::StaticSoundHandle, AudioError> {
383 self.manager
384 .as_mut()
385 .ok_or(AudioError::NotStarted)?
386 .bus
387 .play(data)
388 .map_err(|e| AudioError::Backend(e.to_string()))
389 }
390
391 pub fn play_stream(
396 &mut self,
397 stream: impl AudioStream,
398 sample_rate: u32,
399 ) -> Result<StreamHandle, AudioError> {
400 self.play_stream_with(stream, sample_rate, PlayOptions::default())
401 }
402
403 pub fn play_stream_with(
406 &mut self,
407 stream: impl AudioStream,
408 sample_rate: u32,
409 options: PlayOptions,
410 ) -> Result<StreamHandle, AudioError> {
411 self.start_or_queue(Source::mono(Box::new(stream)), sample_rate, options)
412 }
413
414 pub fn play_stereo_stream(
416 &mut self,
417 stream: impl StereoStream,
418 sample_rate: u32,
419 ) -> Result<StreamHandle, AudioError> {
420 self.play_stereo_stream_with(stream, sample_rate, PlayOptions::default())
421 }
422
423 pub fn play_stereo_stream_with(
425 &mut self,
426 stream: impl StereoStream,
427 sample_rate: u32,
428 options: PlayOptions,
429 ) -> Result<StreamHandle, AudioError> {
430 self.start_or_queue(Source::Stereo(Box::new(stream)), sample_rate, options)
431 }
432
433 fn start_or_queue(
434 &mut self,
435 source: Source,
436 sample_rate: u32,
437 options: PlayOptions,
438 ) -> Result<StreamHandle, AudioError> {
439 let pending = self.manager.is_none();
440 if pending && !options.deferred {
441 return Err(AudioError::NotStarted);
442 }
443 let (data, handle) = StreamData::new(source, sample_rate, options.volume, pending);
444 if pending {
445 self.queued_streams.push(data);
446 } else {
447 self.start_stream(data);
448 }
449 Ok(handle)
450 }
451
452 fn start_stream(&mut self, data: StreamData) {
453 let handle = data.handle().clone();
454 let Some(manager) = self.manager.as_mut() else {
455 return;
456 };
457 match manager.bus.play(data) {
458 Ok(()) => handle.mark_started(),
459 Err(e) => {
460 log::warn!("stream failed to start: {e}");
461 handle.stop();
462 }
463 }
464 }
465
466 #[cfg(feature = "synth")]
492 pub fn play_synth(
493 &mut self,
494 mut song: synth::Song,
495 options: PlayOptions,
496 ) -> Result<SynthHandle, AudioError> {
497 song.looped = options.looped;
498 let data =
499 synthie::kira::SynthSoundData::new().with_sequencer(synth::SongPlayer::new(song));
500 self.play_synth_sound(data, options)
501 }
502
503 #[cfg(feature = "synth")]
508 pub fn play_synth_sound<S, const N: usize>(
509 &mut self,
510 data: synthie::kira::SynthSoundData<S, N>,
511 options: PlayOptions,
512 ) -> Result<SynthHandle, AudioError>
513 where
514 S: synthie::prelude::Sequencer + Send + 'static,
515 {
516 let pending = self.manager.is_none();
517 if pending && !options.deferred {
518 return Err(AudioError::NotStarted);
519 }
520 let data = data.with_volume(amplitude_to_db(options.volume));
521 let prepared = PreparedSynth::new(data, pending);
522 let handle = prepared.handle().clone();
523 if pending {
524 self.queued_synths.push(prepared);
525 } else {
526 self.start_synth(prepared);
527 }
528 Ok(handle)
529 }
530
531 #[cfg(feature = "synth")]
532 fn start_synth(&mut self, prepared: PreparedSynth) {
533 let handle = prepared.handle().clone();
534 let Some(manager) = self.manager.as_mut() else {
535 return;
536 };
537 match manager.bus.play(prepared) {
538 Ok(()) => handle.mark_started(),
539 Err(e) => {
540 log::warn!("synth failed to start: {e}");
541 handle.stop();
542 }
543 }
544 }
545
546 pub fn set_master_volume(&mut self, volume: f32) {
549 self.master_volume = amplitude_to_db(volume);
550 if let Some(manager) = self.manager.as_mut() {
551 manager
552 .kira
553 .main_track()
554 .set_volume(self.master_volume, Tween::default());
555 }
556 }
557
558 pub fn suspend(&mut self) {
566 self.set_suspended(true);
567 }
568
569 pub fn resume(&mut self) {
571 self.set_suspended(false);
572 }
573
574 pub fn is_suspended(&self) -> bool {
576 self.suspended
577 }
578
579 fn set_suspended(&mut self, suspended: bool) {
580 if self.suspended == suspended {
581 return;
582 }
583 self.suspended = suspended;
584 if let Some(manager) = self.manager.as_mut() {
585 if suspended {
586 manager.bus.pause(Tween::default());
587 } else {
588 manager.bus.resume(Tween::default());
589 }
590 }
591 }
592
593 #[cfg(target_arch = "wasm32")]
596 pub fn detach_handle(&self) -> DetachHandle {
597 self.detach.clone()
598 }
599}
600
601pub(crate) fn amplitude_to_db(volume: f32) -> Decibels {
603 if volume <= 0.0 {
604 Decibels::SILENCE
605 } else {
606 Decibels::from(20.0 * volume.log10())
607 }
608}
609
610#[cfg(test)]
611mod tests {
612 use super::*;
613
614 #[test]
615 fn amplitude_to_db_maps_unity_and_silence() {
616 assert_eq!(amplitude_to_db(1.0), Decibels::IDENTITY);
617 assert_eq!(amplitude_to_db(0.0), Decibels::SILENCE);
618 assert!((amplitude_to_db(0.5).0 + 6.02).abs() < 0.01);
619 }
620
621 #[test]
622 fn play_options_builders() {
623 let o = PlayOptions::looped().volume(0.25).deferred();
624 assert!(o.looped && o.deferred);
625 assert_eq!(o.volume, 0.25);
626 assert_eq!(PlayOptions::default().volume, 1.0);
627 }
628
629 #[test]
630 fn suspend_before_start_is_remembered() {
631 let mut audio = Audio {
632 manager: None,
633 suspended: false,
634 #[cfg(target_arch = "wasm32")]
635 detach: DetachHandle::default(),
636 sounds: HashMap::new(),
637 next_id: 1,
638 master_volume: Decibels::IDENTITY,
639 queued: Vec::new(),
640 queued_streams: Vec::new(),
641 #[cfg(feature = "synth")]
642 queued_synths: Vec::new(),
643 };
644 assert!(!audio.is_suspended());
645 audio.suspend();
646 audio.suspend();
647 assert!(audio.is_suspended());
648 audio.resume();
649 assert!(!audio.is_suspended());
650 }
651}