use super::spatial::SpatialNode; use super::{Aspect, AspectIdentifier, Node}; use crate::core::client::Client; use crate::core::entity_handle::EntityHandle; use crate::core::error::Result; use crate::core::registry::Registry; use crate::core::resource::get_resource_file; use crate::nodes::spatial::{SPATIAL_ASPECT_ALIAS_INFO, Spatial, Transform}; use bevy::audio::{PlaybackMode, Volume}; use bevy_mod_openxr::session::OxrSession; use bevy_mod_xr::session::{XrPreDestroySession, XrSessionCreated}; use bevy_mod_xr::spaces::XrSpace; use color_eyre::eyre::eyre; use parking_lot::Mutex; use stardust_xr::values::ResourceID; use bevy::prelude::*; use bevy::transform::components::Transform as BevyTransform; use std::sync::{Arc, OnceLock}; use std::{ffi::OsStr, path::PathBuf}; pub struct AudioNodePlugin; impl Plugin for AudioNodePlugin { fn build(&self, app: &mut App) { app.add_systems(Update, update_sound_event); app.add_systems(XrSessionCreated, spawn_hmd_audio_listener); app.add_systems(XrPreDestroySession, despawn_hmd_audio_listener); } } fn despawn_hmd_audio_listener(mut cmds: Commands, session: Res, res: Res) { cmds.remove_resource::(); cmds.entity(res.0).despawn(); _ = session.destroy_space(res.1); } fn spawn_hmd_audio_listener(mut cmds: Commands, session: Res) { let space = session .create_reference_space(openxr::ReferenceSpaceType::VIEW, BevyTransform::IDENTITY) .unwrap(); let listener = cmds .spawn(( Name::new("HMD audio listener"), space.0, SpatialListener::new(0.2), )) .id(); cmds.insert_resource(HmdListener(listener, space.0)); } #[derive(Resource)] struct HmdListener(Entity, XrSpace); fn update_sound_event( mut cmds: Commands, sinks: Query<&SpatialAudioSink>, asset_server: Res, ) { for sound in SOUND_REGISTRY.get_valid_contents() { if sound.entity.get().is_none() { let handle = asset_server.load(sound.pending_audio_path.as_path()); sound .entity .set( cmds.spawn(( Name::new("Audio Node"), SpatialNode(Arc::downgrade(&sound.spatial)), AudioPlayer::new(handle), PlaybackSettings { mode: PlaybackMode::Once, volume: Volume::Linear(sound.volume), speed: 1.0, paused: true, muted: false, spatial: true, spatial_scale: None, }, )) .id() .into(), ) .unwrap(); } if let Some(sink) = sound.entity.get().and_then(|e| sinks.get(e.0).ok()) { if sound.play.lock().take().is_some() { sink.play(); } if sound.stop.lock().take().is_some() { sink.stop(); } } } } static SOUND_REGISTRY: Registry = Registry::new(); stardust_xr_server_codegen::codegen_audio_protocol!(); pub struct Sound { spatial: Arc, volume: f32, pending_audio_path: PathBuf, entity: OnceLock, stop: Mutex>, play: Mutex>, } impl Sound { pub fn add_to(node: &Arc, resource_id: ResourceID) -> Result> { let pending_audio_path = get_resource_file( &resource_id, &*node.get_client().ok_or_else(|| eyre!("Client not found"))?, &[OsStr::new("wav"), OsStr::new("mp3")], ) .ok_or_else(|| eyre!("Resource not found"))?; let sound = Sound { spatial: node.get_aspect::().unwrap().clone(), volume: 1.0, pending_audio_path, entity: OnceLock::new(), stop: Mutex::new(None), play: Mutex::new(None), }; let sound_arc = SOUND_REGISTRY.add(sound); node.add_aspect_raw(sound_arc.clone()); Ok(sound_arc) } } impl AspectIdentifier for Sound { impl_aspect_for_sound_aspect_id! {} } impl Aspect for Sound { impl_aspect_for_sound_aspect! {} } impl SoundAspect for Sound { fn play(node: Arc, _calling_client: Arc) -> Result<()> { let sound = node.get_aspect::().unwrap(); sound.play.lock().replace(()); Ok(()) } fn stop(node: Arc, _calling_client: Arc) -> Result<()> { let sound = node.get_aspect::().unwrap(); sound.stop.lock().replace(()); Ok(()) } } impl Drop for Sound { fn drop(&mut self) { SOUND_REGISTRY.remove(self); } } impl InterfaceAspect for Interface { #[doc = "Create a sound node. WAV and MP3 are supported."] fn create_sound( _node: Arc, calling_client: Arc, id: u64, parent: Arc, transform: Transform, resource: ResourceID, ) -> Result<()> { let node = Node::from_id(&calling_client, id, true); let parent = parent.get_aspect::()?; let transform = transform.to_mat4(true, true, true); let node = node.add_to_scenegraph()?; Spatial::add_to(&node, Some(parent.clone()), transform, false); Sound::add_to(&node, resource)?; Ok(()) } }