1#![forbid(unsafe_code)]
31
32use std::collections::HashMap;
33#[cfg(not(target_arch = "wasm32"))]
34use std::path::Path;
35use std::sync::Arc;
36
37use anyhow::{Result, anyhow};
38use kira::sound::static_sound::{StaticSoundData, StaticSoundSettings};
39use kira::{AudioManager, AudioManagerSettings, Decibels, DefaultBackend, Frame, Tween};
40
41mod handle;
42use handle::Slot;
43pub use handle::SoundHandle;
44mod stream;
45pub use kira;
46use stream::StreamData;
47pub use stream::{AudioStream, StreamHandle};
48
49#[cfg(feature = "synth")]
50mod synth;
51#[cfg(feature = "synth")]
52pub use synth::Synth;
53#[cfg(feature = "synth")]
54pub use synthie;
55
56#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
57pub struct SoundId(pub u32);
58
59#[derive(Debug)]
61#[non_exhaustive]
62pub enum AudioError {
63 NotStarted,
67 UnknownSound(SoundId),
69 Backend(String),
71}
72
73impl std::fmt::Display for AudioError {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 match self {
76 Self::NotStarted => f.write_str("audio not started yet (waiting for a user gesture)"),
77 Self::UnknownSound(id) => write!(f, "unknown SoundId {id:?}"),
78 Self::Backend(e) => write!(f, "kira play: {e}"),
79 }
80 }
81}
82
83impl std::error::Error for AudioError {}
84
85#[derive(Copy, Clone, Debug, PartialEq)]
88pub struct PlayOptions {
89 pub looped: bool,
91 pub volume: f32,
93 pub deferred: bool,
97}
98
99impl Default for PlayOptions {
100 fn default() -> Self {
101 Self {
102 looped: false,
103 volume: 1.0,
104 deferred: false,
105 }
106 }
107}
108
109impl PlayOptions {
110 pub fn looped() -> Self {
112 Self {
113 looped: true,
114 ..Self::default()
115 }
116 }
117
118 pub fn volume(self, volume: f32) -> Self {
120 Self { volume, ..self }
121 }
122
123 pub fn deferred(self) -> Self {
125 Self {
126 deferred: true,
127 ..self
128 }
129 }
130}
131
132pub struct Audio {
133 manager: Option<AudioManager<DefaultBackend>>,
134 sounds: HashMap<SoundId, StaticSoundData>,
135 next_id: u32,
136 master_volume: Decibels,
138 queued: Vec<(StaticSoundData, SoundHandle)>,
140 queued_streams: Vec<StreamData>,
142}
143
144impl Audio {
145 pub fn new() -> Result<Self> {
148 let mut audio = Self {
149 manager: None,
150 sounds: HashMap::new(),
151 next_id: 1,
152 master_volume: Decibels::IDENTITY,
153 queued: Vec::new(),
154 queued_streams: Vec::new(),
155 };
156 if !cfg!(target_arch = "wasm32") {
157 audio.start()?;
158 }
159 Ok(audio)
160 }
161
162 pub fn start(&mut self) -> Result<()> {
166 if self.manager.is_some() {
167 return Ok(());
168 }
169 let mut manager = AudioManager::<DefaultBackend>::new(AudioManagerSettings::default())
170 .map_err(|e| anyhow!("kira AudioManager init: {e}"))?;
171 manager
172 .main_track()
173 .set_volume(self.master_volume, Tween::default());
174 self.manager = Some(manager);
175 for (data, handle) in std::mem::take(&mut self.queued) {
176 self.start_queued(data, &handle);
177 }
178 for data in std::mem::take(&mut self.queued_streams) {
179 if !data.handle().is_finished() {
180 self.start_stream(data);
181 }
182 }
183 Ok(())
184 }
185
186 fn start_queued(&mut self, data: StaticSoundData, handle: &SoundHandle) {
187 let mut slot = handle.lock();
188 let Slot::Pending { volume, paused } = &*slot else {
189 return; };
191 let (volume, paused) = (*volume, *paused);
192 let (data, fade) = match volume {
195 Some((db, duration)) if duration.is_zero() => (data.volume(db), None),
196 Some((db, duration)) => (data, Some((db, duration))),
197 None => (data, None),
198 };
199 match self.play_now(data) {
200 Ok(mut kira) => {
201 if let Some((db, duration)) = fade {
202 kira.set_volume(db, handle::tween(duration));
203 }
204 if paused {
205 kira.pause(Tween::default());
206 }
207 *slot = Slot::Playing(kira);
208 }
209 Err(e) => {
210 log::warn!("queued sound failed to start: {e}");
211 *slot = Slot::Cancelled;
212 }
213 }
214 }
215
216 pub fn is_started(&self) -> bool {
219 self.manager.is_some()
220 }
221
222 #[cfg(not(target_arch = "wasm32"))]
224 pub fn load_sound(&mut self, path: impl AsRef<Path>) -> Result<SoundId> {
225 let data = StaticSoundData::from_file(path.as_ref())
226 .map_err(|e| anyhow!("kira load {}: {e}", path.as_ref().display()))?;
227 Ok(self.insert(data))
228 }
229
230 pub fn load_sound_bytes(&mut self, bytes: impl Into<Vec<u8>>) -> Result<SoundId> {
232 let data = StaticSoundData::from_cursor(std::io::Cursor::new(bytes.into()))
233 .map_err(|e| anyhow!("kira decode: {e}"))?;
234 Ok(self.insert(data))
235 }
236
237 fn insert(&mut self, data: StaticSoundData) -> SoundId {
238 let id = SoundId(self.next_id);
239 self.next_id += 1;
240 self.sounds.insert(id, data);
241 id
242 }
243
244 pub fn play(&mut self, id: SoundId) -> Result<SoundHandle, AudioError> {
252 self.play_with(id, PlayOptions::default())
253 }
254
255 pub fn play_with(
258 &mut self,
259 id: SoundId,
260 options: PlayOptions,
261 ) -> Result<SoundHandle, AudioError> {
262 let data = self
263 .sounds
264 .get(&id)
265 .ok_or(AudioError::UnknownSound(id))?
266 .clone();
267 self.play_data(data, options)
268 }
269
270 pub fn play_samples(
274 &mut self,
275 samples: &[f32],
276 sample_rate: u32,
277 ) -> Result<SoundHandle, AudioError> {
278 self.play_samples_with(samples, sample_rate, PlayOptions::default())
279 }
280
281 pub fn play_samples_with(
283 &mut self,
284 samples: &[f32],
285 sample_rate: u32,
286 options: PlayOptions,
287 ) -> Result<SoundHandle, AudioError> {
288 let frames: Arc<[Frame]> = samples
289 .iter()
290 .map(|&s| Frame { left: s, right: s })
291 .collect();
292 let data = StaticSoundData {
293 sample_rate,
294 frames,
295 settings: StaticSoundSettings::default(),
296 slice: None,
297 };
298 self.play_data(data, options)
299 }
300
301 fn play_data(
302 &mut self,
303 data: StaticSoundData,
304 options: PlayOptions,
305 ) -> Result<SoundHandle, AudioError> {
306 let mut data = data.volume(amplitude_to_db(options.volume));
307 if options.looped {
308 data = data.loop_region(..);
309 }
310 if self.manager.is_none() && options.deferred {
311 let handle = SoundHandle::pending();
312 self.queued.push((data, handle.clone()));
313 return Ok(handle);
314 }
315 self.play_now(data).map(SoundHandle::playing)
316 }
317
318 fn play_now(
319 &mut self,
320 data: StaticSoundData,
321 ) -> Result<kira::sound::static_sound::StaticSoundHandle, AudioError> {
322 self.manager
323 .as_mut()
324 .ok_or(AudioError::NotStarted)?
325 .play(data)
326 .map_err(|e| AudioError::Backend(e.to_string()))
327 }
328
329 pub fn play_stream(
334 &mut self,
335 stream: impl AudioStream,
336 sample_rate: u32,
337 ) -> Result<StreamHandle, AudioError> {
338 self.play_stream_with(stream, sample_rate, PlayOptions::default())
339 }
340
341 pub fn play_stream_with(
344 &mut self,
345 stream: impl AudioStream,
346 sample_rate: u32,
347 options: PlayOptions,
348 ) -> Result<StreamHandle, AudioError> {
349 let pending = self.manager.is_none();
350 if pending && !options.deferred {
351 return Err(AudioError::NotStarted);
352 }
353 let (data, handle) =
354 StreamData::new(Box::new(stream), sample_rate, options.volume, pending);
355 if pending {
356 self.queued_streams.push(data);
357 } else {
358 self.start_stream(data);
359 }
360 Ok(handle)
361 }
362
363 fn start_stream(&mut self, data: StreamData) {
364 let handle = data.handle().clone();
365 let Some(manager) = self.manager.as_mut() else {
366 return;
367 };
368 match manager.play(data) {
369 Ok(()) => handle.mark_started(),
370 Err(e) => {
371 log::warn!("stream failed to start: {e}");
372 handle.stop();
373 }
374 }
375 }
376
377 pub fn set_master_volume(&mut self, volume: f32) {
380 self.master_volume = amplitude_to_db(volume);
381 if let Some(manager) = self.manager.as_mut() {
382 manager
383 .main_track()
384 .set_volume(self.master_volume, Tween::default());
385 }
386 }
387}
388
389pub(crate) fn amplitude_to_db(volume: f32) -> Decibels {
391 if volume <= 0.0 {
392 Decibels::SILENCE
393 } else {
394 Decibels::from(20.0 * volume.log10())
395 }
396}
397
398#[cfg(test)]
399mod tests {
400 use super::*;
401
402 #[test]
403 fn amplitude_to_db_maps_unity_and_silence() {
404 assert_eq!(amplitude_to_db(1.0), Decibels::IDENTITY);
405 assert_eq!(amplitude_to_db(0.0), Decibels::SILENCE);
406 assert!((amplitude_to_db(0.5).0 + 6.02).abs() < 0.01);
407 }
408
409 #[test]
410 fn play_options_builders() {
411 let o = PlayOptions::looped().volume(0.25).deferred();
412 assert!(o.looped && o.deferred);
413 assert_eq!(o.volume, 0.25);
414 assert_eq!(PlayOptions::default().volume, 1.0);
415 }
416}