commit 35435527673a0828dff46283bc95c43de351421c Author: Michael Hübner Date: Mon Jan 16 13:17:58 2023 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..28a77a8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "workbench.colorCustomizations": { + "activityBar.background": "#56084C", + "titleBar.activeBackground": "#780B6B", + "titleBar.activeForeground": "#FFFBFE" + } +} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a72d529 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "audio" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rg3d-sound = "0.26.0" +anyhow = { version = "1.0.68", features = ["backtrace"] } +utilities = { git = "https://gavania.de/hodasemi/utilities.git" } +assetpath = { git = "https://gavania.de/hodasemi/vulkan_lib.git" } \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0c6da96 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,5 @@ +mod shared; +mod sound; + +pub use shared::*; +pub use sound::*; diff --git a/src/shared.rs b/src/shared.rs new file mode 100644 index 0000000..3667707 --- /dev/null +++ b/src/shared.rs @@ -0,0 +1,165 @@ +use anyhow::Result; +use utilities::prelude::*; + +use super::{Music, Sound}; + +use std::collections::HashMap; +use std::sync::Arc; + +#[derive(Copy, Clone, Debug)] +pub enum SoundInterpretation { + Generic, + Spatial, +} + +#[derive(Debug)] +// pub(crate) struct VolumeHandler { +pub struct VolumeHandler { + sound_volumes: HashMap, +} + +impl VolumeHandler { + pub(crate) fn new(volume_info: HashMap) -> Self { + VolumeHandler { + sound_volumes: volume_info, + } + } + + pub(crate) fn set_volume( + &mut self, + sound_type: &str, + volume: f32, + audio_objects: &mut AudioObjects, + ) -> Result<()> { + // check map if sound type is already present + let changed = match self.sound_volumes.get_mut(sound_type) { + Some(stype) => { + // set volume + if *stype != volume { + *stype = volume; + + true + } else { + false + } + } + None => { + // insert volume for requested sound type + self.sound_volumes.insert(sound_type.to_string(), volume); + + true + } + }; + + if changed { + if sound_type == "music" { + audio_objects.apply_to_music(|music| music.set_volume(volume))?; + } else { + audio_objects.apply_to_sound(Some(sound_type), |sound| sound.set_volume(volume))?; + } + } + + Ok(()) + } + + pub(crate) fn volume(&self, sound_type: &str) -> f32 { + self.sound_volumes[sound_type] + } +} + +pub trait ClearQueueAudioObject: Send + Sync { + fn end_looping(&self) -> Result<()>; + fn is_playing(&self) -> Result; +} + +#[derive(Default)] +pub struct AudioObjects { + // sound handling + sounds: HashMap>>, + + // music handling + music: Vec>, + + clear_queue: Vec>, +} + +impl AudioObjects { + pub fn add_sound(&mut self, sound: Arc) { + match self.sounds.get_mut(sound.sound_type()) { + Some(sounds) => sounds.push(sound), + None => { + self.sounds + .insert(sound.sound_type().to_string(), vec![sound]); + } + } + } + + pub fn add_music(&mut self, music: Arc) { + self.music.push(music); + } + + pub fn apply_to_sound(&self, sound_type: Option<&str>, f: F) -> Result<()> + where + F: Fn(&Arc) -> Result<()>, + { + match sound_type { + Some(sound_type) => { + if let Some(sounds) = self.sounds.get(sound_type) { + for sound in sounds.iter() { + f(sound)?; + } + } + } + None => { + for sounds in self.sounds.values() { + for sound in sounds.iter() { + f(sound)?; + } + } + } + } + + Ok(()) + } + + pub fn apply_to_music(&self, f: F) -> Result<()> + where + F: Fn(&Arc) -> Result<()>, + { + for music in self.music.iter() { + f(music)?; + } + + Ok(()) + } + + pub fn clear(&mut self) { + self.sounds.clear(); + self.music.clear(); + } + + pub fn remove_sound(&mut self, sound: &Arc) -> Result<()> { + if let Some(sounds) = self.sounds.get_mut(sound.sound_type()) { + if let Some(old_sound) = erase_arc(sounds, sound) { + old_sound.end_looping()?; + self.clear_queue.push(old_sound); + } + } + + Ok(()) + } + + pub fn check_clear_queue(&mut self) -> Result<()> { + let mut new_queue = Vec::new(); + + for sound in self.clear_queue.iter() { + if sound.is_playing()? { + new_queue.push(sound.clone()); + } + } + + self.clear_queue = new_queue; + + Ok(()) + } +} diff --git a/src/sound.rs b/src/sound.rs new file mode 100644 index 0000000..2775f88 --- /dev/null +++ b/src/sound.rs @@ -0,0 +1,512 @@ +use anyhow::Result; + +use super::{AudioObjects, ClearQueueAudioObject, SoundInterpretation, VolumeHandler}; + +use rg3d_sound::{ + algebra::Vector3, + buffer::{DataSource, SoundBufferResource}, + context::SoundContext, + engine::SoundEngine, + futures::executor::block_on, + pool::Handle, + source::{ + generic::{GenericSource, GenericSourceBuilder}, + spatial::{SpatialSource, SpatialSourceBuilder}, + SoundSource, Status, + }, +}; + +use assetpath::AssetPath; + +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +#[inline] +fn act_on_generic_source( + ctx: &SoundContext, + handle: Handle, + interpretation: SoundInterpretation, + f: F, +) -> Result +where + F: FnOnce(&mut GenericSource) -> Result, +{ + let mut state = ctx.state(); + let source = state.source_mut(handle); + + let generic_source = match interpretation { + SoundInterpretation::Generic => match source { + SoundSource::Generic(generic_source) => generic_source, + SoundSource::Spatial(_) => { + return Err(anyhow::Error::msg( + "Audio Error: called generic on spatial source", + )) + } + }, + SoundInterpretation::Spatial => match source { + SoundSource::Spatial(spatial_source) => spatial_source.generic_mut(), + SoundSource::Generic(_) => { + return Err(anyhow::Error::msg( + "Audio Error: called spatial on generic source", + )) + } + }, + }; + + Ok(f(generic_source)?) +} + +#[inline] +fn act_on_spatial_source( + ctx: &SoundContext, + handle: Handle, + interpretation: SoundInterpretation, + f: F, +) -> Result +where + F: FnOnce(&mut SpatialSource) -> Result, +{ + let mut state = ctx.state(); + let source = state.source_mut(handle); + + let spatial_source = match interpretation { + SoundInterpretation::Generic => match source { + SoundSource::Generic(_) => { + return Err(anyhow::Error::msg( + "Audio Error: requested spatial source on generic", + )) + } + SoundSource::Spatial(_) => { + return Err(anyhow::Error::msg( + "Audio Error: called generic on spatial source", + )) + } + }, + SoundInterpretation::Spatial => match source { + SoundSource::Spatial(spatial_source) => spatial_source, + SoundSource::Generic(_) => { + return Err(anyhow::Error::msg( + "Audio Error: called spatial on generic source", + )) + } + }, + }; + + Ok(f(spatial_source)?) +} + +macro_rules! sound_ctor { + ($struct_name:ident) => { + pub struct $struct_name { + handle: Handle, + rg3d_context: SoundContext, + interpretation: SoundInterpretation, + sound_type: String, + path: AssetPath, + duration: Duration, + } + + impl $struct_name { + fn new( + handle: Handle, + rg3d_context: SoundContext, + interpretation: SoundInterpretation, + sound_type: String, + path: AssetPath, + ) -> Result> { + let duration = act_on_generic_source( + &rg3d_context, + handle, + interpretation, + |generic_source| Ok(generic_source.playback_time()), + )?; + + Ok(Arc::new($struct_name { + handle, + rg3d_context, + interpretation, + sound_type, + path, + duration, + })) + } + + pub fn play(&self, enable_looping: Option) -> Result<()> { + act_on_generic_source( + &self.rg3d_context, + self.handle, + self.interpretation, + |generic_source| { + generic_source.stop()?; + generic_source.play(); + if let Some(looping) = enable_looping { + generic_source.set_looping(looping); + } + + Ok(()) + }, + ) + } + + pub fn pause(&self) -> Result<()> { + act_on_generic_source( + &self.rg3d_context, + self.handle, + self.interpretation, + |generic_source| { + generic_source.pause(); + + Ok(()) + }, + ) + } + + pub fn stop_looping(&self) -> Result<()> { + act_on_generic_source( + &self.rg3d_context, + self.handle, + self.interpretation, + |generic_source| { + generic_source.set_looping(false); + + Ok(()) + }, + ) + } + + pub fn stop(&self) -> Result<()> { + act_on_generic_source( + &self.rg3d_context, + self.handle, + self.interpretation, + |generic_source| { + generic_source.stop()?; + + Ok(()) + }, + ) + } + + pub fn set_position(&self, position: impl Into<[f32; 3]>) -> Result<()> { + act_on_spatial_source( + &self.rg3d_context, + self.handle, + self.interpretation, + |spatial_source| { + let pos = position.into(); + spatial_source.set_position(Vector3::new(pos[0], pos[1], pos[2])); + + Ok(()) + }, + ) + } + + pub fn set_volume(&self, volume: f32) -> Result<()> { + act_on_generic_source( + &self.rg3d_context, + self.handle, + self.interpretation, + |generic_source| { + generic_source.set_gain(volume); + + Ok(()) + }, + ) + } + + pub fn file_path(&self) -> &AssetPath { + &self.path + } + + pub fn sound_type(&self) -> &str { + &self.sound_type + } + + pub fn duration(&self) -> Duration { + self.duration + } + + fn is_paused(&self) -> Result { + act_on_generic_source( + &self.rg3d_context, + self.handle, + self.interpretation, + |generic_source| Ok(generic_source.status() == Status::Paused), + ) + } + } + + impl ClearQueueAudioObject for $struct_name { + fn end_looping(&self) -> Result<()> { + self.stop_looping() + } + + fn is_playing(&self) -> Result { + act_on_generic_source( + &self.rg3d_context, + self.handle, + self.interpretation, + |generic_source| Ok(generic_source.status() == Status::Playing), + ) + } + } + }; +} + +sound_ctor!(Music); +sound_ctor!(Sound); + +enum SoundType { + Sound, + Music, +} + +pub struct SoundHandler { + volume_handler: VolumeHandler, + + rg3d_context: SoundContext, + _sound_engine: Arc>, + + data: HashMap, + + audio_objects: AudioObjects, +} + +impl SoundHandler { + pub fn new(volume_info: HashMap) -> Result { + let sound_engine = SoundEngine::new(); + let rg3d_context = SoundContext::new(); + + sound_engine + .lock() + .unwrap() + .add_context(rg3d_context.clone()); + + let master_volume = match volume_info.get("master") { + Some(v) => *v, + None => 1.0, + }; + + rg3d_context.state().set_master_gain(master_volume); + + Ok(SoundHandler { + volume_handler: VolumeHandler::new(volume_info), + + rg3d_context, + _sound_engine: sound_engine, + + data: HashMap::new(), + + audio_objects: AudioObjects::default(), + }) + } + + pub fn set_position(&self, position: impl Into<[f32; 3]>) -> Result<()> { + let pos = position.into(); + + self.rg3d_context + .state() + .listener_mut() + .set_position(Vector3::new(pos[0], pos[1], pos[2])); + + Ok(()) + } + + pub fn set_direction( + &self, + direction: impl Into<[f32; 3]>, + up: impl Into<[f32; 3]>, + ) -> Result<()> { + let dir = direction.into(); + let up = up.into(); + + self.rg3d_context.state().listener_mut().set_orientation_rh( + Vector3::new(dir[0], dir[1], dir[2]), + Vector3::new(up[0], up[1], up[2]), + ); + + Ok(()) + } + + pub fn load_sound( + &mut self, + path: AssetPath, + sound_type: &str, + interpretation: SoundInterpretation, + ) -> Result> { + let volume = self.volume_handler.volume(sound_type); + + let handle = self.source_handle(&path, volume, interpretation, SoundType::Sound)?; + + let sound = Sound::new( + handle, + self.rg3d_context.clone(), + interpretation, + sound_type.to_string(), + path, + )?; + + self.audio_objects.add_sound(sound.clone()); + + Ok(sound) + } + + pub fn load_music( + &mut self, + path: AssetPath, + interpretation: SoundInterpretation, + ) -> Result> { + let volume = self.volume_handler.volume("music"); + + let handle = self.source_handle(&path, volume, interpretation, SoundType::Music)?; + + let music = Music::new( + handle, + self.rg3d_context.clone(), + interpretation, + "music".to_string(), + path, + )?; + + self.audio_objects.add_music(music.clone()); + + Ok(music) + } + + pub fn set_volume(&mut self, sound_type: &str, volume: f32) -> Result<()> { + if sound_type == "master" { + self.rg3d_context.state().set_master_gain(volume); + + Ok(()) + } else { + self.volume_handler + .set_volume(sound_type, volume, &mut self.audio_objects) + } + } + + pub fn volume(&self, sound_type: &str) -> Result { + if sound_type == "master" { + Ok(self.rg3d_context.state().master_gain()) + } else { + Ok(self.volume_handler.volume(sound_type)) + } + } + + pub fn pause(&mut self) -> Result<()> { + // check if sounds are playing + self.audio_objects.apply_to_sound(None, |sound| { + if sound.is_playing()? { + sound.pause()? + } + + Ok(()) + })?; + + self.audio_objects.apply_to_music(|music| { + if music.is_playing()? { + music.pause()? + } + + Ok(()) + })?; + + Ok(()) + } + + pub fn resume(&self) -> Result<()> { + // check if sounds are paused + self.audio_objects.apply_to_sound(None, |sound| { + if sound.is_paused()? { + sound.play(None)? + } + + Ok(()) + })?; + + self.audio_objects.apply_to_music(|music| { + if music.is_paused()? { + music.play(None)? + } + + Ok(()) + })?; + + Ok(()) + } + + pub fn remove_sound(&mut self, sound: &Arc) -> Result<()> { + self.audio_objects.remove_sound(sound) + } + + fn source_handle( + &mut self, + path: &AssetPath, + volume: f32, + interpretation: SoundInterpretation, + sound_type: SoundType, + ) -> Result> { + let sound_buffer = match self.data.get(path) { + Some(buffer) => buffer.clone(), + None => { + let data_source = + block_on(DataSource::from_file(&path.full_path())).map_err(|_err| { + anyhow::Error::msg(format!( + "Audio Error: failed loading file {}", + path.full_path() + )) + })?; + + let buffer = match sound_type { + SoundType::Sound => { + SoundBufferResource::new_generic(data_source).map_err(|_| { + anyhow::Error::msg("Audio Error: failed creating generic sound buffer") + })? + } + SoundType::Music => { + SoundBufferResource::new_streaming(data_source).map_err(|_| { + anyhow::Error::msg( + "Audio Error: failed creating streaming sound buffer", + ) + })? + } + }; + + self.data.insert(path.clone(), buffer.clone()); + + buffer + } + }; + + let generic_source_builder = GenericSourceBuilder::new() + .with_buffer(sound_buffer) + .with_status(Status::Stopped) + .with_gain(volume); + + let source = match interpretation { + SoundInterpretation::Generic => generic_source_builder.build_source()?, + SoundInterpretation::Spatial => { + SpatialSourceBuilder::new(generic_source_builder.build()?).build_source() + } + }; + + let handle = self.rg3d_context.state().add_source(source); + + Ok(handle) + } + + pub fn check_clear_queue(&mut self) -> Result<()> { + self.audio_objects.check_clear_queue() + } + + pub fn clear(&mut self) { + self.audio_objects.clear(); + self.data.clear(); + } +} + +impl Drop for SoundHandler { + fn drop(&mut self) { + self.clear(); + } +}