use crate::{native::Audio as NativeAudio, Result, Source, VolumeScale};
pub struct Audio {
tx: flume::Sender<Command>,
}
enum Command {
Play {
slice: std::sync::Arc<[u8]>,
volume: u8,
pitch: u8,
source: Option<Source>,
scale: VolumeScale,
oneshot_tx: oneshot::Sender<Result<()>>,
},
SetPitch {
pitch: u8,
source: Source,
oneshot_tx: oneshot::Sender<()>,
},
SetVolume {
volume: u8,
source: Source,
scale: VolumeScale,
oneshot_tx: oneshot::Sender<()>,
},
ClearSinks {
oneshot_tx: oneshot::Sender<()>,
},
Stop {
source: Source,
oneshot_tx: oneshot::Sender<()>,
},
Drop,
}
impl Audio {
pub fn new() -> Self {
Default::default()
}
pub fn play(
&self,
path: impl AsRef<camino::Utf8Path>,
filesystem: &impl luminol_filesystem::FileSystem,
volume: u8,
pitch: u8,
source: Option<Source>,
scale: VolumeScale,
) -> Result<()> {
let path = path.as_ref();
let slice: std::sync::Arc<[u8]> = filesystem.read(path)?.into();
self.play_from_slice(slice, volume, pitch, source, scale)
}
pub fn play_from_slice(
&self,
slice: impl AsRef<[u8]> + Send + Sync + 'static,
volume: u8,
pitch: u8,
source: Option<Source>,
scale: VolumeScale,
) -> Result<()> {
let (oneshot_tx, oneshot_rx) = oneshot::channel();
self.tx
.send(Command::Play {
slice: slice.as_ref().into(),
volume,
pitch,
source,
scale,
oneshot_tx,
})
.unwrap();
oneshot_rx.recv().unwrap()
}
pub fn set_pitch(&self, pitch: u8, source: Source) {
let (oneshot_tx, oneshot_rx) = oneshot::channel();
self.tx
.send(Command::SetPitch {
pitch,
source,
oneshot_tx,
})
.unwrap();
oneshot_rx.recv().unwrap()
}
pub fn set_volume(&self, volume: u8, source: Source, scale: VolumeScale) {
let (oneshot_tx, oneshot_rx) = oneshot::channel();
self.tx
.send(Command::SetVolume {
volume,
source,
scale,
oneshot_tx,
})
.unwrap();
oneshot_rx.recv().unwrap()
}
pub fn clear_sinks(&self) {
let (oneshot_tx, oneshot_rx) = oneshot::channel();
self.tx.send(Command::ClearSinks { oneshot_tx }).unwrap();
oneshot_rx.recv().unwrap()
}
pub fn stop(&self, source: Source) {
let (oneshot_tx, oneshot_rx) = oneshot::channel();
self.tx.send(Command::Stop { source, oneshot_tx }).unwrap();
oneshot_rx.recv().unwrap()
}
}
impl Default for Audio {
fn default() -> Self {
#[cfg(target_arch = "wasm32")]
if web_sys::window().is_none() {
panic!("in web builds, `Audio` can only be created on the main thread");
}
let (tx, rx) = flume::unbounded::<Command>();
let mut maybe_audio = None;
wasm_bindgen_futures::spawn_local(async move {
loop {
let Ok(command) = rx.recv_async().await else {
return;
};
let audio = if let Some(audio) = &maybe_audio {
audio
} else {
maybe_audio = Some(NativeAudio::default());
maybe_audio.as_ref().unwrap()
};
match command {
Command::Play {
slice,
volume,
pitch,
source,
scale,
oneshot_tx,
} => {
oneshot_tx
.send(audio.play_from_slice(slice, volume, pitch, source, scale))
.unwrap();
}
Command::SetPitch {
pitch,
source,
oneshot_tx,
} => {
audio.set_pitch(pitch, source);
oneshot_tx.send(()).unwrap();
}
Command::SetVolume {
volume,
source,
scale,
oneshot_tx,
} => {
audio.set_volume(volume, source, scale);
oneshot_tx.send(()).unwrap();
}
Command::ClearSinks { oneshot_tx } => {
audio.clear_sinks();
oneshot_tx.send(()).unwrap();
}
Command::Stop { source, oneshot_tx } => {
audio.stop(source);
oneshot_tx.send(()).unwrap();
}
Command::Drop => {
break;
}
}
}
});
Self { tx }
}
}
impl Drop for Audio {
fn drop(&mut self) {
self.tx.send(Command::Drop).unwrap();
}
}