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::sound::static_sound::{StaticSoundData, StaticSoundHandle, StaticSoundSettings};
14use kira::{AudioManager, AudioManagerSettings, Decibels, DefaultBackend, Frame, Tween};
15
16#[cfg(feature = "synth")]
17mod synth;
18#[cfg(feature = "synth")]
19pub use synth::Synth;
20#[cfg(feature = "synth")]
21pub use synthie;
22
23#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
24pub struct SoundId(pub u32);
25
26pub struct Audio {
27    manager: AudioManager<DefaultBackend>,
28    sounds: HashMap<SoundId, StaticSoundData>,
29    next_id: u32,
30}
31
32impl Audio {
33    pub fn new() -> Result<Self> {
34        let manager = AudioManager::<DefaultBackend>::new(AudioManagerSettings::default())
35            .map_err(|e| anyhow!("kira AudioManager init: {e}"))?;
36        Ok(Self {
37            manager,
38            sounds: HashMap::new(),
39            next_id: 1,
40        })
41    }
42
43    pub fn load_sound(&mut self, path: impl AsRef<Path>) -> Result<SoundId> {
44        let data = StaticSoundData::from_file(path.as_ref())
45            .map_err(|e| anyhow!("kira load {}: {e}", path.as_ref().display()))?;
46        let id = SoundId(self.next_id);
47        self.next_id += 1;
48        self.sounds.insert(id, data);
49        Ok(id)
50    }
51
52    pub fn play(&mut self, id: SoundId) -> Result<StaticSoundHandle> {
53        let data = self
54            .sounds
55            .get(&id)
56            .ok_or_else(|| anyhow!("unknown SoundId {id:?}"))?
57            .clone();
58        self.manager
59            .play(data)
60            .map_err(|e| anyhow!("kira play: {e}"))
61    }
62
63    /// Play mono PCM samples (`-1.0..=1.0`) directly, e.g. a buffer rendered by
64    /// [`Synth`]. The samples are duplicated to both channels.
65    pub fn play_samples(&mut self, samples: &[f32], sample_rate: u32) -> Result<StaticSoundHandle> {
66        let frames: Arc<[Frame]> = samples
67            .iter()
68            .map(|&s| Frame { left: s, right: s })
69            .collect();
70        let data = StaticSoundData {
71            sample_rate,
72            frames,
73            settings: StaticSoundSettings::default(),
74            slice: None,
75        };
76        self.manager
77            .play(data)
78            .map_err(|e| anyhow!("kira play: {e}"))
79    }
80
81    /// Master volume on the default main track, as a linear amplitude
82    /// (`1.0` is unity gain, `0.0` is silence). Converted to decibels for kira.
83    pub fn set_master_volume(&mut self, volume: f32) {
84        let gain = if volume <= 0.0 {
85            Decibels::SILENCE
86        } else {
87            Decibels::from(20.0 * volume.log10())
88        };
89        self.manager.main_track().set_volume(gain, Tween::default());
90    }
91}