diff --git a/Cargo.lock b/Cargo.lock index 00528bde..6f4415ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -190,6 +190,12 @@ dependencies = [ "libloading", ] +[[package]] +name = "atomic-arena" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73e8ed45f88ed32e6827a96b62d8fd4086d72defc754c5c6bd08470c1aaf648e" + [[package]] name = "atomic-waker" version = "1.1.2" @@ -1573,15 +1579,16 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kira" -version = "0.9.6" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a9f9dff5e262540b628b00d5e1a772270a962d6869f8695bb24832ff3b394" +checksum = "4151aa099bbf4ed2d0349cd954dde58432b08c37d627563b6dfe1e641d0cd022" dependencies = [ + "atomic-arena", "cpal", "glam", "mint", "paste", - "ringbuf", + "rtrb", "send_wrapper", "symphonia", "triple_buffer", @@ -2964,15 +2971,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "ringbuf" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79abed428d1fd2a128201cec72c5f6938e2da607c6f3745f769fabea399d950a" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "ron" version = "0.8.1" @@ -2991,6 +2989,12 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" +[[package]] +name = "rtrb" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8388ea1a9e0ea807e442e8263a699e7edcb320ecbcd21b4fa8ff859acce3ba" + [[package]] name = "rustc-demangle" version = "0.1.24" diff --git a/Cargo.toml b/Cargo.toml index 6be8ae86..cad94ca7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ flate2 = { version = "1", default-features = false } glidesort = "0.1" hashbrown = "0.15" image = { version = "0.25", default-features = false } -kira = { version = "0.9", default-features = false } +kira = { version = "0.10", default-features = false } korangar_audio = { path = "korangar_audio" } korangar_debug = { path = "korangar_debug" } korangar_interface = { path = "korangar_interface" } diff --git a/korangar_audio/src/lib.rs b/korangar_audio/src/lib.rs index 78c92be3..9ca87f48 100644 --- a/korangar_audio/src/lib.rs +++ b/korangar_audio/src/lib.rs @@ -15,17 +15,15 @@ use std::time::{Duration, Instant}; use cgmath::{InnerSpace, Matrix3, Point3, Quaternion, Vector3}; use cpal::BufferSize; -use kira::manager::backend::cpal::{CpalBackend, CpalBackendSettings}; -use kira::manager::{AudioManager, AudioManagerSettings, Capacities}; +use kira::backend::cpal::{CpalBackend, CpalBackendSettings}; +use kira::listener::ListenerHandle; use kira::sound::static_sound::{StaticSoundData, StaticSoundHandle}; use kira::sound::streaming::{StreamingSoundData, StreamingSoundHandle}; use kira::sound::{FromFileError, PlaybackState}; -use kira::spatial::emitter::{EmitterDistances, EmitterHandle, EmitterSettings}; -use kira::spatial::listener::{ListenerHandle, ListenerSettings}; -use kira::spatial::scene::{SpatialSceneHandle, SpatialSceneSettings}; -use kira::track::{TrackBuilder, TrackHandle}; -use kira::tween::{Easing, Tween, Value}; -use kira::{Frame, Volume}; +use kira::track::{ + MainTrackBuilder, SendTrackBuilder, SendTrackHandle, SpatialTrackBuilder, SpatialTrackHandle, TrackBuilder, TrackHandle, +}; +use kira::{AudioManager, AudioManagerSettings, Capacities, Decibels, Easing, Frame, Mapping, Tween, Value}; #[cfg(feature = "debug")] use korangar_debug::logging::{print_debug, Colorize}; use korangar_util::collision::{KDTree, Sphere}; @@ -36,6 +34,10 @@ use rayon::spawn; create_generational_key!(SoundEffectKey, "The key for a cached sound effect"); create_simple_key!(AmbientKey, "The key for a ambient sound"); +// TODO: NHA Issue 1: On drop sound leaks and is very loud. +// TODO: NHA output_range has the wrong order +// TODO: NHA the range has the wrong + const MAX_QUEUE_TIME_SECONDS: f32 = 1.0; const MAX_CACHE_COUNT: u32 = 400; const MAX_CACHE_SIZE: usize = 50 * 104 * 1024; // 50 MiB @@ -70,6 +72,7 @@ struct AmbientSoundConfig { } struct PlayingAmbient { + key: AmbientKey, data: StaticSoundData, handle: StaticSoundHandle, cycle: f32, @@ -106,10 +109,10 @@ pub struct AudioEngine<F> { } struct EngineContext<F> { - active_emitters: HashMap<AmbientKey, EmitterHandle>, + active_spatial_tracks: HashMap<AmbientKey, SpatialTrackHandle>, spatial_listener: ListenerHandle, ambient_sound: SimpleSlab<AmbientKey, AmbientSoundConfig>, - spatial_sound_effect_track: TrackHandle, + spatial_sound_effect_track: SendTrackHandle, async_response_receiver: Receiver<AsyncLoadResult>, async_response_sender: Sender<AsyncLoadResult>, background_music_track: TrackHandle, @@ -127,7 +130,6 @@ struct EngineContext<F> { query_result: Vec<AmbientKey>, queued_background_music_track: Option<String>, queued_sound_effect: Vec<QueuedSoundEffect>, - scene: SpatialSceneHandle, scratchpad: Vec<AmbientKey>, sound_effect_paths: GenerationalSlab<SoundEffectKey, String>, sound_effect_track: TrackHandle, @@ -138,7 +140,8 @@ impl<F: FileLoader> AudioEngine<F> { pub fn new(game_file_loader: Arc<F>) -> AudioEngine<F> { let mut manager = AudioManager::<CpalBackend>::new(AudioManagerSettings { capacities: Capacities::default(), - main_track_builder: TrackBuilder::default(), + main_track_builder: MainTrackBuilder::default(), + internal_buffer_size: 128, backend_settings: CpalBackendSettings { device: None, // At sampling rate of 48 kHz 1200 frames take 25 ms. @@ -146,23 +149,17 @@ impl<F: FileLoader> AudioEngine<F> { }, }) .expect("Can't initialize audio backend"); - let mut scene = manager - .add_spatial_scene(SpatialSceneSettings::default()) - .expect("Can't create spatial scene"); let background_music_track = manager .add_sub_track(TrackBuilder::new()) .expect("Can't create background music track"); let sound_effect_track = manager.add_sub_track(TrackBuilder::new()).expect("Can't create sound effect track"); let spatial_sound_effect_track = manager - .add_sub_track(TrackBuilder::new()) + .add_send_track(SendTrackBuilder::new()) .expect("Can't create spatial sound effect track"); let position = Vector3::new(0.0, 0.0, 0.0); let orientation = Quaternion::new(0.0, 0.0, 0.0, 0.0); - let spatial_listener = scene - .add_listener(position, orientation, ListenerSettings { - track: spatial_sound_effect_track.id(), - }) - .expect("Can't create ambient listener"); + let spatial_listener = manager.add_listener(position, orientation).expect("Can't create spatial listener"); + let loading_sound_effect = HashSet::new(); let cache = SimpleCache::new( NonZeroU32::new(MAX_CACHE_COUNT).unwrap(), @@ -175,7 +172,7 @@ impl<F: FileLoader> AudioEngine<F> { let object_kdtree = KDTree::empty(); let engine_context = Mutex::new(EngineContext { - active_emitters: HashMap::default(), + active_spatial_tracks: HashMap::default(), spatial_listener, ambient_sound: SimpleSlab::default(), spatial_sound_effect_track, @@ -196,7 +193,6 @@ impl<F: FileLoader> AudioEngine<F> { query_result: Vec::default(), queued_background_music_track: None, queued_sound_effect: Vec::default(), - scene, scratchpad: Vec::default(), sound_effect_paths: GenerationalSlab::default(), sound_effect_track, @@ -206,11 +202,10 @@ impl<F: FileLoader> AudioEngine<F> { /// Mutes or unmutes the audio. pub fn mute(&self, enable: bool) { - let mut volume = Volume::Amplitude(1.0); - if enable { - volume = Volume::Amplitude(0.0); - } - self.set_main_volume(volume); + match enable { + true => self.set_main_volume(0.0), + false => self.set_main_volume(1.0), + }; } /// This function needs the full file path with the file extension. @@ -261,23 +256,32 @@ impl<F: FileLoader> AudioEngine<F> { } /// Sets the global volume. - pub fn set_main_volume(&self, volume: impl Into<Value<Volume>>) { - self.engine_context.lock().unwrap().set_main_volume(volume) + pub fn set_main_volume(&self, volume: f32) { + self.engine_context.lock().unwrap().set_main_volume(linear_to_decibel(volume)) } /// Sets the volume of the background music. - pub fn set_background_music_volume(&self, volume: impl Into<Value<Volume>>) { - self.engine_context.lock().unwrap().set_background_music_volume(volume) + pub fn set_background_music_volume(&self, volume: f32) { + self.engine_context + .lock() + .unwrap() + .set_background_music_volume(linear_to_decibel(volume)) } /// Sets the volume of sound effect. - pub fn set_sound_effect_volume(&self, volume: impl Into<Value<Volume>>) { - self.engine_context.lock().unwrap().set_sound_effect_volume(volume) + pub fn set_sound_effect_volume(&self, volume: f32) { + self.engine_context + .lock() + .unwrap() + .set_sound_effect_volume(linear_to_decibel(volume)) } /// Sets the volume of spatial sound effects. - pub fn set_spatial_sound_effect_volume(&self, volume: impl Into<Value<Volume>>) { - self.engine_context.lock().unwrap().set_spatial_sound_effect_volume(volume) + pub fn set_spatial_sound_effect_volume(&self, volume: f32) { + self.engine_context + .lock() + .unwrap() + .set_spatial_sound_effect_volume(linear_to_decibel(volume)) } /// Plays the background music track. Fades out the currently playing @@ -332,7 +336,7 @@ impl<F: FileLoader> AudioEngine<F> { .add_ambient_sound(sound_effect_key, position, range, volume, cycle) } - /// Removes all ambient sound emitters from the spatial scene. + /// Removes all ambient-sound tracks. pub fn clear_ambient_sound(&self) { self.engine_context.lock().unwrap().clear_ambient_sound() } @@ -350,28 +354,28 @@ impl<F: FileLoader> AudioEngine<F> { } impl<F: FileLoader> EngineContext<F> { - fn set_main_volume(&mut self, volume: impl Into<Value<Volume>>) { + fn set_main_volume(&mut self, volume: Decibels) { self.manager.main_track().set_volume(volume, Tween { duration: Duration::from_millis(500), ..Default::default() }); } - fn set_background_music_volume(&mut self, volume: impl Into<Value<Volume>>) { + fn set_background_music_volume(&mut self, volume: Decibels) { self.background_music_track.set_volume(volume, Tween { duration: Duration::from_millis(500), ..Default::default() }); } - fn set_sound_effect_volume(&mut self, volume: impl Into<Value<Volume>>) { + fn set_sound_effect_volume(&mut self, volume: Decibels) { self.sound_effect_track.set_volume(volume, Tween { duration: Duration::from_millis(500), ..Default::default() }); } - fn set_spatial_sound_effect_volume(&mut self, volume: impl Into<Value<Volume>>) { + fn set_spatial_sound_effect_volume(&mut self, volume: Decibels) { self.spatial_sound_effect_track.set_volume(volume, Tween { duration: Duration::from_millis(500), ..Default::default() @@ -418,8 +422,7 @@ impl<F: FileLoader> EngineContext<F> { .get(&sound_effect_key) .map(|cached_sound_effect| cached_sound_effect.0.clone()) { - let data = data.output_destination(&self.sound_effect_track); - if let Err(_error) = self.manager.play(data.clone()) { + if let Err(_error) = self.sound_effect_track.play(data.clone()) { #[cfg(feature = "debug")] print_debug!("[{}] can't play sound effect: {:?}", "error".red(), _error); } @@ -446,28 +449,25 @@ impl<F: FileLoader> EngineContext<F> { .get(&sound_effect_key) .map(|cached_sound_effect| cached_sound_effect.0.clone()) { - let settings = EmitterSettings { - distances: EmitterDistances { - min_distance: 5.0, - max_distance: range, - }, - attenuation_function: Some(Easing::Linear), - enable_spatialization: true, - persist_until_sounds_finish: true, - }; - - match self.scene.add_emitter(position, settings) { - Ok(emitter_handle) => { - let data = adjust_ambient_sound(data, &emitter_handle, 1.0); - - if let Err(_error) = self.manager.play(data) { + let spatial_track = SpatialTrackBuilder::new().persist_until_sounds_finish(true).with_send( + &self.spatial_sound_effect_track, + Value::FromListenerDistance(Mapping { + input_range: (5.0, range as f64), + output_range: (Decibels::IDENTITY, Decibels::SILENCE), + easing: Easing::Linear, + }), + ); + + match self.manager.add_spatial_sub_track(&self.spatial_listener, position, spatial_track) { + Ok(mut spatial_track_handle) => { + if let Err(_error) = spatial_track_handle.play(data) { #[cfg(feature = "debug")] print_debug!("[{}] can't play sound effect: {:?}", "error".red(), _error); } } Err(_error) => { #[cfg(feature = "debug")] - print_debug!("[{}] can't add spatial sound emitter: {:?}", "error".red(), _error); + print_debug!("[{}] can't add spatial sound track: {:?}", "error".red(), _error); } }; } @@ -502,20 +502,21 @@ impl<F: FileLoader> EngineContext<F> { // Kira uses a RH coordinate system, so we need to convert our LH vectors. let position = sound_config.bounds.center(); let position = Vector3::new(position.x, position.y, -position.z); - let emitter_settings = EmitterSettings { - distances: EmitterDistances { - min_distance: 5.0, - max_distance: sound_config.bounds.radius(), - }, - attenuation_function: Some(Easing::Linear), - enable_spatialization: true, - persist_until_sounds_finish: true, - }; - let emitter_handle = match self.scene.add_emitter(position, emitter_settings) { - Ok(emitter_handle) => emitter_handle, + + let spatial_track = SpatialTrackBuilder::new().persist_until_sounds_finish(true).with_send( + &self.spatial_sound_effect_track, + Value::FromListenerDistance(Mapping { + input_range: (5.0, sound_config.bounds.radius() as f64), + output_range: (Decibels::IDENTITY, Decibels::SILENCE), + easing: Easing::Linear, + }), + ); + + let mut spatial_track_handle = match self.manager.add_spatial_sub_track(&self.spatial_listener, position, spatial_track) { + Ok(spatial_track_handle) => spatial_track_handle, Err(_error) => { #[cfg(feature = "debug")] - print_debug!("[{}] can't add ambient sound emitter: {:?}", "error".red(), _error); + print_debug!("[{}] can't add ambient sound track: {:?}", "error".red(), _error); continue; } }; @@ -526,11 +527,12 @@ impl<F: FileLoader> EngineContext<F> { .get(&sound_effect_key) .map(|cached_sound_effect| cached_sound_effect.0.clone()) { - let data = adjust_ambient_sound(data, &emitter_handle, sound_config.volume); - match self.manager.play(data.clone()) { + let data = data.volume(linear_to_decibel(sound_config.volume)); + match spatial_track_handle.play(data.clone()) { Ok(handle) => { if let Some(cycle) = sound_config.cycle { self.cycling_ambient.insert(ambient_key, PlayingAmbient { + key: ambient_key, data, handle, cycle, @@ -554,13 +556,13 @@ impl<F: FileLoader> EngineContext<F> { ); } - self.active_emitters.insert(ambient_key, emitter_handle); + self.active_spatial_tracks.insert(ambient_key, spatial_track_handle); } // Remove ambient sound that are out of reach. difference(&mut self.previous_query_result, &mut self.query_result, &mut self.scratchpad); for ambient_key in self.scratchpad.iter() { - let _ = self.active_emitters.remove(ambient_key); + let _ = self.active_spatial_tracks.remove(ambient_key); let _ = self.cycling_ambient.remove(ambient_key); } @@ -616,7 +618,7 @@ impl<F: FileLoader> EngineContext<F> { self.scratchpad.clear(); self.ambient_sound.clear(); - self.active_emitters.clear(); + self.active_spatial_tracks.clear(); self.cycling_ambient.clear(); } @@ -703,46 +705,44 @@ impl<F: FileLoader> EngineContext<F> { match queued.sound_type { QueuedSoundEffectType::Sound => { - if let Err(_error) = self.manager.play(data.output_destination(&self.sound_effect_track)) { + if let Err(_error) = self.sound_effect_track.play(data) { #[cfg(feature = "debug")] print_debug!("[{}] can't play sound effect: {:?}", "error".red(), _error); } } QueuedSoundEffectType::SpatialSound { position, range } => { - let settings = EmitterSettings { - distances: EmitterDistances { - min_distance: 5.0, - max_distance: range, - }, - attenuation_function: Some(Easing::Linear), - enable_spatialization: true, - persist_until_sounds_finish: true, - }; - - match self.scene.add_emitter(position, settings) { - Ok(emitter_handle) => { - let data = adjust_ambient_sound(data, &emitter_handle, 1.0); + let spatial_track = SpatialTrackBuilder::new().persist_until_sounds_finish(true).with_send( + &self.spatial_sound_effect_track, + Value::FromListenerDistance(Mapping { + input_range: (5.0, range as f64), + output_range: (Decibels::IDENTITY, Decibels::SILENCE), + easing: Easing::Linear, + }), + ); - if let Err(_error) = self.manager.play(data) { + match self.manager.add_spatial_sub_track(&self.spatial_listener, position, spatial_track) { + Ok(mut spatial_track_handle) => { + if let Err(_error) = spatial_track_handle.play(data) { #[cfg(feature = "debug")] print_debug!("[{}] can't play sound effect: {:?}", "error".red(), _error); } } Err(_error) => { #[cfg(feature = "debug")] - print_debug!("[{}] can't add spatial sound emitter: {:?}", "error".red(), _error); + print_debug!("[{}] can't add spatial sound track: {:?}", "error".red(), _error); } }; } QueuedSoundEffectType::AmbientSound { ambient_key } => { - if let Some(emitter_handle) = self.active_emitters.get(&ambient_key) + if let Some(spatial_track_handle) = self.active_spatial_tracks.get_mut(&ambient_key) && let Some(sound_config) = self.ambient_sound.get(ambient_key) { - let data = adjust_ambient_sound(data, emitter_handle, sound_config.volume); - match self.manager.play(data.clone()) { + let data = data.volume(sound_config.volume); + match spatial_track_handle.play(data.clone()) { Ok(handle) => { if let Some(cycle) = sound_config.cycle { self.cycling_ambient.insert(ambient_key, PlayingAmbient { + key: ambient_key, data, handle, cycle, @@ -770,15 +770,17 @@ impl<F: FileLoader> EngineContext<F> { for (_, playing) in self.cycling_ambient.iter_mut().filter(|(_, playing)| { playing.handle.state() != PlaybackState::Playing && now.duration_since(playing.last_start).as_secs_f32() >= playing.cycle }) { - playing.last_start = now; + if let Some(spatial_track) = self.active_spatial_tracks.get_mut(&playing.key) { + playing.last_start = now; - match self.manager.play(playing.data.clone()) { - Ok(handle) => { - playing.handle = handle; - } - Err(_error) => { - #[cfg(feature = "debug")] - print_debug!("[{}] can't play ambient sound effect: {:?}", "error".red(), _error); + match spatial_track.play(playing.data.clone()) { + Ok(handle) => { + playing.handle = handle; + } + Err(_error) => { + #[cfg(feature = "debug")] + print_debug!("[{}] can't play ambient sound effect: {:?}", "error".red(), _error); + } } } } @@ -806,9 +808,8 @@ impl<F: FileLoader> EngineContext<F> { // the music again. let duration = data.duration().as_secs_f64() - 0.05; let data = data.loop_region(..duration); - let data = data.output_destination(&self.background_music_track); - let handle = match self.manager.play(data) { + let handle = match self.background_music_track.play(data) { Ok(handle) => handle, Err(_error) => { #[cfg(feature = "debug")] @@ -824,12 +825,6 @@ impl<F: FileLoader> EngineContext<F> { } } -fn adjust_ambient_sound(mut data: StaticSoundData, emitter_handle: &EmitterHandle, volume: f32) -> StaticSoundData { - // Kira does the volume mapping from linear to logarithmic for us. - data.settings.volume = Volume::Amplitude(volume as f64).into(); - data.output_destination(emitter_handle) -} - fn queue_sound_effect_playback( game_file_loader: Arc<impl FileLoader>, async_response_sender: Sender<AsyncLoadResult>, @@ -966,6 +961,14 @@ fn difference<T: Ord + Copy>(vector_1: &mut [T], vector_2: &mut [T], result: &mu result.extend_from_slice(&vector_1[i..]); } +fn linear_to_decibel(linear: f32) -> Decibels { + if linear <= 0.0 { + Decibels::SILENCE + } else { + Decibels::from(20.0 * linear.log10()) + } +} + #[cfg(test)] mod tests { use crate::difference;