game_toolkit_audio/
lib.rs1#![forbid(unsafe_code)]
12
13use std::collections::HashMap;
14#[cfg(not(target_arch = "wasm32"))]
15use std::path::Path;
16use std::sync::Arc;
17
18use anyhow::{Result, anyhow};
19use kira::sound::static_sound::{StaticSoundData, StaticSoundHandle, StaticSoundSettings};
20use kira::{AudioManager, AudioManagerSettings, Decibels, DefaultBackend, Frame, Tween};
21
22#[cfg(feature = "synth")]
23mod synth;
24#[cfg(feature = "synth")]
25pub use synth::Synth;
26#[cfg(feature = "synth")]
27pub use synthie;
28
29#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
30pub struct SoundId(pub u32);
31
32pub struct Audio {
33 manager: Option<AudioManager<DefaultBackend>>,
34 sounds: HashMap<SoundId, StaticSoundData>,
35 next_id: u32,
36 master_volume: Decibels,
38}
39
40impl Audio {
41 pub fn new() -> Result<Self> {
44 let mut audio = Self {
45 manager: None,
46 sounds: HashMap::new(),
47 next_id: 1,
48 master_volume: Decibels::IDENTITY,
49 };
50 if !cfg!(target_arch = "wasm32") {
51 audio.start()?;
52 }
53 Ok(audio)
54 }
55
56 pub fn start(&mut self) -> Result<()> {
59 if self.manager.is_some() {
60 return Ok(());
61 }
62 let mut manager = AudioManager::<DefaultBackend>::new(AudioManagerSettings::default())
63 .map_err(|e| anyhow!("kira AudioManager init: {e}"))?;
64 manager
65 .main_track()
66 .set_volume(self.master_volume, Tween::default());
67 self.manager = Some(manager);
68 Ok(())
69 }
70
71 pub fn is_started(&self) -> bool {
73 self.manager.is_some()
74 }
75
76 #[cfg(not(target_arch = "wasm32"))]
78 pub fn load_sound(&mut self, path: impl AsRef<Path>) -> Result<SoundId> {
79 let data = StaticSoundData::from_file(path.as_ref())
80 .map_err(|e| anyhow!("kira load {}: {e}", path.as_ref().display()))?;
81 Ok(self.insert(data))
82 }
83
84 pub fn load_sound_bytes(&mut self, bytes: impl Into<Vec<u8>>) -> Result<SoundId> {
86 let data = StaticSoundData::from_cursor(std::io::Cursor::new(bytes.into()))
87 .map_err(|e| anyhow!("kira decode: {e}"))?;
88 Ok(self.insert(data))
89 }
90
91 fn insert(&mut self, data: StaticSoundData) -> SoundId {
92 let id = SoundId(self.next_id);
93 self.next_id += 1;
94 self.sounds.insert(id, data);
95 id
96 }
97
98 fn manager(&mut self) -> Result<&mut AudioManager<DefaultBackend>> {
99 self.manager
100 .as_mut()
101 .ok_or_else(|| anyhow!("audio not started yet (waiting for a user gesture)"))
102 }
103
104 pub fn play(&mut self, id: SoundId) -> Result<StaticSoundHandle> {
105 let data = self
106 .sounds
107 .get(&id)
108 .ok_or_else(|| anyhow!("unknown SoundId {id:?}"))?
109 .clone();
110 self.manager()?
111 .play(data)
112 .map_err(|e| anyhow!("kira play: {e}"))
113 }
114
115 pub fn play_samples(&mut self, samples: &[f32], sample_rate: u32) -> Result<StaticSoundHandle> {
118 let frames: Arc<[Frame]> = samples
119 .iter()
120 .map(|&s| Frame { left: s, right: s })
121 .collect();
122 let data = StaticSoundData {
123 sample_rate,
124 frames,
125 settings: StaticSoundSettings::default(),
126 slice: None,
127 };
128 self.manager()?
129 .play(data)
130 .map_err(|e| anyhow!("kira play: {e}"))
131 }
132
133 pub fn set_master_volume(&mut self, volume: f32) {
136 self.master_volume = if volume <= 0.0 {
137 Decibels::SILENCE
138 } else {
139 Decibels::from(20.0 * volume.log10())
140 };
141 if let Some(manager) = self.manager.as_mut() {
142 manager
143 .main_track()
144 .set_volume(self.master_volume, Tween::default());
145 }
146 }
147}