Skip to main content

game_toolkit_audio/
lib.rs

1//! Audio subsystem built on [`kira`].
2//!
3//! Minimal first-cut API: load static sound buffers from disk and play them on the main track.
4//! Mixer tracks, tweens, spatial audio and streaming are intentionally deferred.
5
6#![forbid(unsafe_code)]
7
8use std::collections::HashMap;
9use std::path::Path;
10use std::sync::Arc;
11
12use anyhow::{Result, anyhow};
13use kira::Frame;
14use kira::manager::backend::DefaultBackend;
15use kira::manager::{AudioManager, AudioManagerSettings};
16use kira::sound::static_sound::{StaticSoundData, StaticSoundHandle, StaticSoundSettings};
17use kira::tween::Tween;
18
19#[cfg(feature = "synth")]
20mod synth;
21#[cfg(feature = "synth")]
22pub use synth::Synth;
23#[cfg(feature = "synth")]
24pub use synthie;
25
26#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
27pub struct SoundId(pub u32);
28
29pub struct Audio {
30    manager: AudioManager<DefaultBackend>,
31    sounds: HashMap<SoundId, StaticSoundData>,
32    next_id: u32,
33}
34
35impl Audio {
36    pub fn new() -> Result<Self> {
37        let manager = AudioManager::<DefaultBackend>::new(AudioManagerSettings::default())
38            .map_err(|e| anyhow!("kira AudioManager init: {e}"))?;
39        Ok(Self {
40            manager,
41            sounds: HashMap::new(),
42            next_id: 1,
43        })
44    }
45
46    pub fn load_sound(&mut self, path: impl AsRef<Path>) -> Result<SoundId> {
47        let data = StaticSoundData::from_file(path.as_ref())
48            .map_err(|e| anyhow!("kira load {}: {e}", path.as_ref().display()))?;
49        let id = SoundId(self.next_id);
50        self.next_id += 1;
51        self.sounds.insert(id, data);
52        Ok(id)
53    }
54
55    pub fn play(&mut self, id: SoundId) -> Result<StaticSoundHandle> {
56        let data = self
57            .sounds
58            .get(&id)
59            .ok_or_else(|| anyhow!("unknown SoundId {id:?}"))?
60            .clone();
61        self.manager
62            .play(data)
63            .map_err(|e| anyhow!("kira play: {e}"))
64    }
65
66    /// Play mono PCM samples (`-1.0..=1.0`) directly, e.g. a buffer rendered by
67    /// [`Synth`](crate::Synth). The samples are duplicated to both channels.
68    pub fn play_samples(&mut self, samples: &[f32], sample_rate: u32) -> Result<StaticSoundHandle> {
69        let frames: Arc<[Frame]> = samples
70            .iter()
71            .map(|&s| Frame { left: s, right: s })
72            .collect();
73        let data = StaticSoundData {
74            sample_rate,
75            frames,
76            settings: StaticSoundSettings::default(),
77            slice: None,
78        };
79        self.manager
80            .play(data)
81            .map_err(|e| anyhow!("kira play: {e}"))
82    }
83
84    /// Master volume on the default main track. `1.0` is unity gain.
85    pub fn set_master_volume(&mut self, volume: f32) {
86        self.manager
87            .main_track()
88            .set_volume(volume as f64, Tween::default());
89    }
90}