From 6a23dea9b9869de6f338099c5170566aa5ace52c Mon Sep 17 00:00:00 2001 From: Schmarni Date: Mon, 16 Dec 2024 03:19:07 +0100 Subject: [PATCH] refactor: impl audio Signed-off-by: Schmarni --- Cargo.toml | 2 +- src/nodes/audio.rs | 147 ++++++++++++++++++++++++++------------ src/nodes/drawable/mod.rs | 18 ----- 3 files changed, 104 insertions(+), 63 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8c40167..c59168f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ auto_link_exclude_list = [ ] [dependencies] -bevy = { version = "0.15", features = ["wayland"] } +bevy = { version = "0.15", features = ["wayland", "mp3", "wav"] } bevy_mod_xr = { git = "https://github.com/Schmarni-Dev/bevy_openxr", branch = "0.15rc" } bevy_mod_openxr = { git = "https://github.com/Schmarni-Dev/bevy_openxr", branch = "0.15rc" } bevy_xr_utils = { git = "https://github.com/Schmarni-Dev/bevy_openxr", branch = "0.15rc" } diff --git a/src/nodes/audio.rs b/src/nodes/audio.rs index 0faf870..369f748 100644 --- a/src/nodes/audio.rs +++ b/src/nodes/audio.rs @@ -5,29 +5,104 @@ use crate::core::error::Result; use crate::core::registry::Registry; use crate::core::resource::get_resource_file; use crate::nodes::spatial::{Spatial, Transform, SPATIAL_ASPECT_ALIAS_INFO}; +use bevy::app::{App, Plugin, PostUpdate, PreUpdate}; +use bevy::asset::AssetServer; +use bevy::audio::{AudioPlayer, AudioSink, AudioSinkPlayback, PlaybackSettings, Volume}; +use bevy::prelude::{Commands, Deref, Entity, Query, Res, Resource, Transform as BevyTransform}; use color_eyre::eyre::eyre; -use glam::{vec3, Vec4Swizzles}; use once_cell::sync::OnceCell; -use parking_lot::Mutex; use stardust_xr::values::ResourceID; +use tracing::error; -use std::ops::DerefMut; use std::sync::Arc; use std::{ffi::OsStr, path::PathBuf}; -use stereokit_rust::sound::{Sound as SkSound, SoundInst}; static SOUND_REGISTRY: Registry = Registry::new(); +pub struct StardustSoundPlugin; +impl Plugin for StardustSoundPlugin { + fn build(&self, app: &mut App) { + let (tx, rx) = crossbeam_channel::unbounded(); + SOUND_EVENT_SENDER.set(tx); + app.insert_resource(SoundEventReader(rx)); + let (tx, rx) = crossbeam_channel::unbounded(); + SPAWN_SOUND_SENDER.set(tx); + app.insert_resource(SpawnSoundReader(rx)); + app.add_systems(PostUpdate, update_sound_state); + app.add_systems(PreUpdate, spawn_sounds); + } +} + +fn spawn_sounds(reader: Res, mut cmds: Commands, asset_server: Res) { + for sound in reader.try_iter() { + let e = cmds + .spawn(( + BevyTransform::default(), + AudioPlayer::new(asset_server.load(sound.pending_audio_path.as_path())), + PlaybackSettings { + mode: bevy::audio::PlaybackMode::Once, + volume: Volume::new(sound.volume), + speed: 1.0, + paused: true, + spatial: true, + spatial_scale: None, + }, + )) + .id(); + sound.entity.set(e); + } +} + +fn update_sound_state( + mut query: Query<&mut AudioSink>, + mut transforms: Query<&mut BevyTransform>, + mut cmds: Commands, + reader: Res, +) { + for (entity, action) in reader.try_iter() { + match action { + SoundAction::Stop => { + let Ok(sink) = query.get_mut(entity) else { + error!("no audio sink to stop?"); + continue; + }; + sink.stop() + } + SoundAction::Play => { + // Idk let's hope this works? + cmds.entity(entity).remove::(); + } + } + } + for sound in SOUND_REGISTRY.get_valid_contents() { + let Some(entity) = sound.entity.get().copied() else { + continue; + }; + let Ok(mut transform) = transforms.get_mut(entity) else { + continue; + }; + *transform = BevyTransform::from_matrix(sound.space.global_transform()); + } +} + stardust_xr_server_codegen::codegen_audio_protocol!(); pub struct Sound { space: Arc, - volume: f32, pending_audio_path: PathBuf, - sk_sound: OnceCell, - instance: Mutex>, - stop: Mutex>, - play: Mutex>, + entity: OnceCell, +} +static SPAWN_SOUND_SENDER: OnceCell>> = OnceCell::new(); +#[derive(Resource, Deref)] +struct SpawnSoundReader(crossbeam_channel::Receiver>); +static SOUND_EVENT_SENDER: OnceCell> = + OnceCell::new(); +#[derive(Resource, Deref)] +struct SoundEventReader(crossbeam_channel::Receiver<(Entity, SoundAction)>); +pub enum SoundAction { + // Pause and Resume? + Stop, + Play, } impl Sound { pub fn add_to(node: &Arc, resource_id: ResourceID) -> Result> { @@ -41,33 +116,15 @@ impl Sound { space: node.get_aspect::().unwrap().clone(), volume: 1.0, pending_audio_path, - sk_sound: OnceCell::new(), - instance: Mutex::new(None), - stop: Mutex::new(None), - play: Mutex::new(None), + entity: OnceCell::new(), }; let sound_arc = SOUND_REGISTRY.add(sound); node.add_aspect_raw(sound_arc.clone()); + if let Some(sender) = SPAWN_SOUND_SENDER.get() { + sender.send(sound_arc.clone())?; + } Ok(sound_arc) } - - fn update(&self) { - let sound = self - .sk_sound - .get_or_init(|| SkSound::from_file(self.pending_audio_path.clone()).unwrap()); - if self.stop.lock().take().is_some() { - if let Some(instance) = self.instance.lock().take() { - instance.stop(); - } - } - if self.instance.lock().is_none() && self.play.lock().take().is_some() { - let instance = sound.play(vec3(0.0, 0.0, 0.0), Some(self.volume)); - self.instance.lock().replace(instance); - } - if let Some(instance) = self.instance.lock().deref_mut() { - instance.position(self.space.global_transform().w_axis.xyz()); - } - } } impl AspectIdentifier for Sound { impl_aspect_for_sound_aspect_id! {} @@ -78,34 +135,36 @@ impl Aspect for Sound { impl SoundAspect for Sound { fn play(node: Arc, _calling_client: Arc) -> Result<()> { let sound = node.get_aspect::().unwrap(); - sound.play.lock().replace(()); + if let Some((sender, entity)) = SOUND_EVENT_SENDER + .get() + .and_then(|s| Some((s, *sound.entity.get()?))) + { + sender.send((entity, SoundAction::Play))? + } Ok(()) } fn stop(node: Arc, _calling_client: Arc) -> Result<()> { let sound = node.get_aspect::().unwrap(); - sound.stop.lock().replace(()); + if let Some((sender, entity)) = SOUND_EVENT_SENDER + .get() + .and_then(|s| Some((s, *sound.entity.get()?))) + { + sender.send((entity, SoundAction::Stop))? + } Ok(()) } } impl Drop for Sound { fn drop(&mut self) { - if let Some(instance) = self.instance.lock().take() { - instance.stop(); - } - if let Some(sk_sound) = self.sk_sound.take() { + if let Some(sk_sound) = self.entity.take() { destroy_queue::add(sk_sound); } SOUND_REGISTRY.remove(self); } } -pub fn update() { - for sound in SOUND_REGISTRY.get_valid_contents() { - sound.update() - } -} - -impl InterfaceAspect for Interface { +struct AudioInterface; +impl InterfaceAspect for AudioInterface { #[doc = "Create a sound node. WAV and MP3 are supported."] fn create_sound( _node: Arc, diff --git a/src/nodes/drawable/mod.rs b/src/nodes/drawable/mod.rs index e845ebe..04c6434 100644 --- a/src/nodes/drawable/mod.rs +++ b/src/nodes/drawable/mod.rs @@ -18,24 +18,6 @@ use parking_lot::Mutex; use stardust_xr::values::ResourceID; use std::{ffi::OsStr, path::PathBuf, sync::Arc}; -// #[instrument(level = "debug", skip(sk))] -pub fn draw(token: &MainThreadToken) { - lines::draw_all(token); - model::draw_all(token); - text::draw_all(token); - - if let Some(skytex) = QUEUED_SKYTEX.lock().take() { - if let Ok(skytex) = SHCubemap::from_cubemap(skytex, true, 100) { - Renderer::skytex(skytex.tex); - } - } - if let Some(skylight) = QUEUED_SKYLIGHT.lock().take() { - if let Ok(skylight) = SHCubemap::from_cubemap(skylight, true, 100) { - Renderer::skylight(skylight.sh); - } - } -} - static QUEUED_SKYLIGHT: Mutex> = Mutex::new(None); static QUEUED_SKYTEX: Mutex> = Mutex::new(None);