1use std::{io::Cursor, sync::Arc};
2
3use anyhow::{Context as _, Result};
4use collections::HashMap;
5use gpui::{App, AssetSource, Global};
6use rodio::{Decoder, Source, source::Buffered};
7
8type Sound = Buffered<Decoder<Cursor<Vec<u8>>>>;
9
10pub struct SoundRegistry {
11 cache: Arc<parking_lot::Mutex<HashMap<String, Sound>>>,
12 assets: Box<dyn AssetSource>,
13}
14
15struct GlobalSoundRegistry(Arc<SoundRegistry>);
16
17impl Global for GlobalSoundRegistry {}
18
19impl SoundRegistry {
20 pub fn new(source: impl AssetSource) -> Arc<Self> {
21 Arc::new(Self {
22 cache: Default::default(),
23 assets: Box::new(source),
24 })
25 }
26
27 pub fn global(cx: &App) -> Arc<Self> {
28 cx.global::<GlobalSoundRegistry>().0.clone()
29 }
30
31 pub(crate) fn set_global(source: impl AssetSource, cx: &mut App) {
32 cx.set_global(GlobalSoundRegistry(SoundRegistry::new(source)));
33 }
34
35 pub fn get(&self, name: &str) -> Result<impl Source<Item = f32> + use<>> {
36 if let Some(wav) = self.cache.lock().get(name) {
37 return Ok(wav.clone());
38 }
39
40 let path = format!("sounds/{}.wav", name);
41 let bytes = self
42 .assets
43 .load(&path)?
44 .map(anyhow::Ok)
45 .with_context(|| format!("No asset available for path {path}"))??
46 .into_owned();
47 let cursor = Cursor::new(bytes);
48 let source = Decoder::new(cursor)?.buffered();
49
50 self.cache.lock().insert(name.to_string(), source.clone());
51
52 Ok(source)
53 }
54}