diff --git a/.gitignore b/.gitignore index 83dc29a8..e129d0ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ Cargo.lock target .idea/ +.vscode/ diff --git a/Cargo.toml b/Cargo.toml index c5b901f0..f81a1b98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "crates/kira", "crates/benchmarks", + "crates/examples/doppler-effect", "crates/examples/dynamic-music", "crates/examples/ghost-noise", "crates/examples/metronome", diff --git a/crates/examples/assets/motor_loop.wav b/crates/examples/assets/motor_loop.wav new file mode 100644 index 00000000..df3ca703 Binary files /dev/null and b/crates/examples/assets/motor_loop.wav differ diff --git a/crates/examples/doppler-effect/Cargo.toml b/crates/examples/doppler-effect/Cargo.toml new file mode 100644 index 00000000..b0074199 --- /dev/null +++ b/crates/examples/doppler-effect/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "doppler-effect" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +glam = { version = "0.27", features = ["mint"] } +kira = { path = "../../kira" } +macroquad = { version = "0.4.13", default-features = false } diff --git a/crates/examples/doppler-effect/src/main.rs b/crates/examples/doppler-effect/src/main.rs new file mode 100644 index 00000000..ea733ac5 --- /dev/null +++ b/crates/examples/doppler-effect/src/main.rs @@ -0,0 +1,197 @@ +use kira::{ + backend::DefaultBackend, sound::static_sound::StaticSoundData, track::{SpatialTrackBuilder, SpatialTrackDistances}, AudioManager, AudioManagerSettings, Decibels, Easing, Mapping, Tween, Value +}; +use macroquad::prelude::*; + +const CAMERA_MAX_SPEED: f32 = 160.0; +const CAMERA_ACCEL: f32 = 1000.0; +const CAMERA_DRAG: f32 = 8.0; + +const LOOK_SPEED: f32 = 0.005; +const WORLD_UP: Vec3 = vec3(0.0, 1.0, 0.0); +const SPATIAL_TRACK_POSITION: Vec3 = vec3(0.0, 1.0, 6.0); +const OSCILLATION_AMPLITUDE: f32 = 40.0; +const OSCILLATION_SPEED: f32 = 4.0; + +fn conf() -> Conf { + Conf { + window_title: String::from("Macroquad"), + window_width: 1260, + window_height: 768, + fullscreen: false, + ..Default::default() + } +} + +#[macroquad::main(conf)] +async fn main() { + let mut camera_controller = CameraController::new(); + + let mut last_mouse_position: Vec2 = mouse_position().into(); + + let mut audio_manager = + AudioManager::::new(AudioManagerSettings::default()).unwrap(); + + let mut listener = audio_manager + .add_listener(camera_controller.position, camera_controller.orientation()) + .unwrap(); + + let mut spatial_track = audio_manager + .add_spatial_sub_track( + &listener, + SPATIAL_TRACK_POSITION, + SpatialTrackBuilder::new() + .distances(SpatialTrackDistances { + min_distance: 1.0, + max_distance: 400.0, + }) + // NOTE: Even though the doppler effect is enabled, the sound will not be affected by it + // until the listener and the spatial track have set the game loop delta time. See below! + .doppler_effect(true) + ) + .unwrap(); + + spatial_track + .play( + // motor_loop.wav: https://freesound.org/people/soundjoao/sounds/325809/ + StaticSoundData::from_file("crates/examples/assets/motor_loop.wav") + .unwrap() + .loop_region(0.0..), + ) + .unwrap(); + + let mut time = 0.0f32; + + loop { + let delta_time = get_frame_time(); + time += delta_time; + + if is_key_pressed(KeyCode::Escape) { + break; + } + + let mouse_position: Vec2 = mouse_position().into(); + let mouse_delta = mouse_position - last_mouse_position; + last_mouse_position = mouse_position; + camera_controller.update(delta_time, mouse_delta); + listener.set_position(camera_controller.position, Tween::default()); + listener.set_orientation(camera_controller.orientation(), Tween::default()); + + clear_background(LIGHTGRAY); + + // Going 3d! + set_camera(&camera_controller.camera()); + + draw_grid(20, 1., BLACK, GRAY); + + let source_position = Vec3::new( + SPATIAL_TRACK_POSITION.x, + SPATIAL_TRACK_POSITION.y, + SPATIAL_TRACK_POSITION.z + (time * OSCILLATION_SPEED).sin() * OSCILLATION_AMPLITUDE + ); + + spatial_track.set_position(source_position, Tween::default()); + + // need to set these every frame unless you're dealing with a fixed timestep + listener.set_game_loop_delta_time(delta_time as f64); + spatial_track.set_game_loop_delta_time(delta_time as f64); + + draw_cube_wires(source_position, vec3(2., 2., 2.), GREEN); + + // Back to screen space, render some text + set_default_camera(); + + draw_text( + &format!("FPS: {}", get_fps()), + 20.0, + 40.0, + 30.0, + BLACK, + ); + + next_frame().await + } +} + +struct CameraController { + position: Vec3, + yaw: f32, + pitch: f32, + velocity: Vec3, +} + +impl CameraController { + fn new() -> Self { + Self { + position: vec3(0.0, 1.0, 50.0), + yaw: 0.0, + pitch: 0.0, + velocity: Vec3::ZERO, + } + } + + fn update(&mut self, delta_time: f32, mouse_delta: Vec2) { + let forward = self.front(); + let right = self.right(); + + let mut desired_dir = Vec3::ZERO; + if is_key_down(KeyCode::W) { + desired_dir += forward; + } + if is_key_down(KeyCode::S) { + desired_dir -= forward; + } + if is_key_down(KeyCode::A) { + desired_dir -= right; + } + if is_key_down(KeyCode::D) { + desired_dir += right; + } + + if is_key_down(KeyCode::Left) { + self.yaw += 2.0 * delta_time; + } + if is_key_down(KeyCode::Right) { + self.yaw -= 2.0 * delta_time; + } + + if is_key_down(KeyCode::Up) { + self.pitch += 2.0 * delta_time; + } + if is_key_down(KeyCode::Down) { + self.pitch -= 2.0 * delta_time; + } + + let desired_dir = desired_dir.normalize_or_zero(); + self.velocity += desired_dir * CAMERA_ACCEL * delta_time; + self.velocity *= 1.0 - CAMERA_DRAG * delta_time; + if self.velocity.length() > CAMERA_MAX_SPEED { + self.velocity = self.velocity.normalize() * CAMERA_MAX_SPEED; + } + + self.position += self.velocity * delta_time; + self.yaw -= mouse_delta.x * LOOK_SPEED; + self.pitch = (self.pitch - mouse_delta.y * LOOK_SPEED).clamp(-1.5, 1.5); + } + + fn orientation(&self) -> Quat { + Quat::from_rotation_y(self.yaw) * Quat::from_rotation_x(self.pitch) + } + + fn camera(&self) -> Camera3D { + Camera3D { + position: self.position, + target: self.position + self.front(), + up: WORLD_UP, + ..Default::default() + } + } + + fn front(&self) -> Vec3 { + -self.orientation().mul_vec3(Vec3::Z).normalize() + } + + fn right(&self) -> Vec3 { + self.orientation().mul_vec3(Vec3::X).normalize() + } +} diff --git a/crates/examples/spatial-audio/src/main.rs b/crates/examples/spatial-audio/src/main.rs index 313a3323..0ef07f4a 100644 --- a/crates/examples/spatial-audio/src/main.rs +++ b/crates/examples/spatial-audio/src/main.rs @@ -79,9 +79,10 @@ async fn main() { let mouse_delta = mouse_position - last_mouse_position; last_mouse_position = mouse_position; camera_controller.update(delta_time, mouse_delta); + listener.set_position(camera_controller.position, Tween::default()); listener.set_orientation(camera_controller.orientation(), Tween::default()); - + clear_background(LIGHTGRAY); // Going 3d! diff --git a/crates/kira/src/command.rs b/crates/kira/src/command.rs index 377e0321..22b59fb9 100644 --- a/crates/kira/src/command.rs +++ b/crates/kira/src/command.rs @@ -163,7 +163,7 @@ macro_rules! read_commands_into_parameters { macro_rules! handle_param_setters { ($($(#[$m:meta])* $name:ident: $type:ty),*$(,)?) => { paste::paste! { - $( + $( $(#[$m])* pub fn [](&mut self, $name: impl Into<$crate::Value<$type>>, tween: $crate::tween::Tween) { self.command_writers.[].write($crate::command::ValueChangeCommand { diff --git a/crates/kira/src/effect/doppler/builder.rs b/crates/kira/src/effect/doppler/builder.rs new file mode 100644 index 00000000..db094847 --- /dev/null +++ b/crates/kira/src/effect/doppler/builder.rs @@ -0,0 +1,50 @@ +use crate::{ + effect::{Effect, EffectBuilder}, + Value, +}; + +use super::{command_writers_and_readers, Doppler, DopplerHandle, DEFAULT_SPEED}; + +/// Configures a doppler effect. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct DopplerBuilder { + /// The speed of sound in m/s. + pub speed: Value, +} + +impl DopplerBuilder { + /// Creates a new [`DopplerBuilder`] with the default settings. + #[must_use] + pub fn new() -> Self { + Self::default() + } + + /// Sets the speed of sound in m/s. + #[must_use = "This method consumes self and returns a modified DopplerBuilder, so the return value should be used"] + pub fn speed(self, speed: impl Into>) -> Self { + Self { + speed: speed.into(), + ..self + } + } +} + +impl Default for DopplerBuilder { + fn default() -> Self { + Self { + speed: Value::Fixed(DEFAULT_SPEED), + } + } +} + +impl EffectBuilder for DopplerBuilder { + type Handle = DopplerHandle; + + fn build(self) -> (Box, Self::Handle) { + let (command_writers, command_readers) = command_writers_and_readers(); + ( + Box::new(Doppler::new(self, command_readers)), + DopplerHandle { command_writers }, + ) + } +} diff --git a/crates/kira/src/effect/doppler/handle.rs b/crates/kira/src/effect/doppler/handle.rs new file mode 100644 index 00000000..7c77f3ec --- /dev/null +++ b/crates/kira/src/effect/doppler/handle.rs @@ -0,0 +1,16 @@ +use crate::command::handle_param_setters; + +use super::CommandWriters; + +/// Controls a reverb effect. +#[derive(Debug)] +pub struct DopplerHandle { + pub(super) command_writers: CommandWriters, +} + +impl DopplerHandle { + handle_param_setters! { + /// Sets the speed of sound in m/s. + speed: f64, + } +} diff --git a/crates/kira/src/info.rs b/crates/kira/src/info.rs index 5c7003aa..4ddcee5e 100644 --- a/crates/kira/src/info.rs +++ b/crates/kira/src/info.rs @@ -5,8 +5,10 @@ * like [`Sound`](crate::sound::Sound) or [`Effect`](crate::effect::Effect). */ +use std::time::Instant; + use atomic_arena::Arena; -use glam::{Quat, Vec3}; +use glam::{DVec3, Quat, Vec3}; use crate::{ clock::{Clock, ClockId, ClockTime, State as ClockState}, @@ -108,6 +110,7 @@ impl<'a> Info<'a> { orientation: listener.orientation.value().into(), previous_position: listener.position.previous_value().into(), previous_orientation: listener.orientation.previous_value().into(), + game_loop_delta_time: listener.game_loop_delta_time.value(), }) } InfoKind::Mock { listener_info, .. } => listener_info.get(listener_id.0).copied(), @@ -124,6 +127,12 @@ impl<'a> Info<'a> { }, ) } + + /// Gets information about the current spatial track + /// if there is one. + pub(crate) fn spatial_track_info(&self) -> Option<&SpatialTrackInfo> { + self.spatial_track_info.as_ref() + } } /// Information about the current state of a [clock](super::clock). @@ -164,6 +173,8 @@ pub struct ListenerInfo { pub previous_position: mint::Vector3, /// The rotation of the listener prior to the last update. pub previous_orientation: mint::Quaternion, + /// The delta time of the game loop. + pub game_loop_delta_time: f64, } impl ListenerInfo { @@ -182,6 +193,15 @@ impl ListenerInfo { let previous_orientation: Quat = self.previous_orientation.into(); previous_orientation.lerp(orientation, amount).into() } + + /// Returns the velocity of the listener. + pub fn velocity(&self) -> DVec3 { + let current: Vec3 = self.position.into(); + let current: DVec3 = current.into(); + let previous: Vec3 = self.previous_position.into(); + let previous: DVec3 = previous.into(); + (current - previous) / self.game_loop_delta_time + } } /// Generates a fake `Info` with arbitrary data. Useful for writing unit tests. @@ -288,6 +308,17 @@ enum InfoKind<'a> { #[derive(Debug, Clone, Copy, PartialEq)] pub(crate) struct SpatialTrackInfo { + pub previous_position: Vec3, pub position: Vec3, pub listener_id: ListenerId, + pub game_loop_delta_time: f64, +} + +impl SpatialTrackInfo { + /// Returns the velocity of the spatial track. Make sure you correctly set the game loop delta time. + pub fn velocity(&self) -> DVec3 { + let position: DVec3 = self.position.into(); + let previous_position: DVec3 = self.previous_position.into(); + (position - previous_position) / self.game_loop_delta_time + } } diff --git a/crates/kira/src/listener.rs b/crates/kira/src/listener.rs index 6312d8f4..8506295a 100644 --- a/crates/kira/src/listener.rs +++ b/crates/kira/src/listener.rs @@ -33,6 +33,7 @@ pub(crate) struct Listener { pub shared: Arc, pub position: Parameter, pub orientation: Parameter, + pub game_loop_delta_time: Parameter, pub command_readers: CommandReaders, } @@ -49,6 +50,7 @@ impl Listener { shared: shared.clone(), position: Parameter::new(position, Vec3::ZERO), orientation: Parameter::new(orientation, Quat::IDENTITY), + game_loop_delta_time: Parameter::new(Value::Fixed(0.0), 0.0), command_readers, }, ListenerHandle { @@ -60,12 +62,13 @@ impl Listener { } pub fn on_start_processing(&mut self) { - read_commands_into_parameters!(self, position, orientation); + read_commands_into_parameters!(self, position, orientation, game_loop_delta_time); } pub(crate) fn update(&mut self, dt: f64, info: &Info) { self.position.update(dt, info); self.orientation.update(dt, info); + self.game_loop_delta_time.update(dt, info); } } @@ -76,6 +79,7 @@ impl Default for Listener { shared: Arc::new(ListenerShared::new()), position: Parameter::new(Value::Fixed(Vec3::ZERO), Vec3::ZERO), orientation: Parameter::new(Value::Fixed(Quat::IDENTITY), Quat::IDENTITY), + game_loop_delta_time: Parameter::new(Value::Fixed(0.0), 0.0), command_readers, } } @@ -106,4 +110,5 @@ impl ListenerShared { command_writers_and_readers! { set_position: ValueChangeCommand, set_orientation: ValueChangeCommand, + set_game_loop_delta_time: ValueChangeCommand, } diff --git a/crates/kira/src/listener/handle.rs b/crates/kira/src/listener/handle.rs index b95e03a6..2ba32cf8 100644 --- a/crates/kira/src/listener/handle.rs +++ b/crates/kira/src/listener/handle.rs @@ -30,6 +30,15 @@ impl ListenerHandle { }) } + /// Sets the delta time of the game loop. Needed for things like doppler on spatial tracks. + pub fn set_game_loop_delta_time(&mut self, game_loop_delta_time: f64) { + let game_loop_delta_time: Value = game_loop_delta_time.into(); + self.command_writers.set_game_loop_delta_time.write(ValueChangeCommand { + target: game_loop_delta_time.to_(), + tween: Tween::default(), + }) + } + /// Sets the rotation of the listener. /// /// An unrotated listener should face in the negative Z direction with diff --git a/crates/kira/src/manager.rs b/crates/kira/src/manager.rs index df03e03b..beaf9353 100644 --- a/crates/kira/src/manager.rs +++ b/crates/kira/src/manager.rs @@ -139,6 +139,7 @@ impl AudioManager { self.internal_buffer_size, listener.into(), position.into().to_(), + Value::Fixed(0.0), // Initial game loop delta time is 0 ); track.init_effects(self.renderer_shared.sample_rate.load(Ordering::SeqCst)); self.resource_controllers diff --git a/crates/kira/src/track/sub.rs b/crates/kira/src/track/sub.rs index 77128fa4..c85138ed 100644 --- a/crates/kira/src/track/sub.rs +++ b/crates/kira/src/track/sub.rs @@ -143,8 +143,10 @@ impl Track { .spatial_data .as_ref() .map(|spatial_data| SpatialTrackInfo { + previous_position: spatial_data.position.previous_value(), position: spatial_data.position.value(), listener_id: spatial_data.listener_id, + game_loop_delta_time: spatial_data.game_loop_delta_time.value(), }) .or(parent_spatial_track_info); let info = Info::new( @@ -193,7 +195,18 @@ impl Track { // process sounds for (_, sound) in &mut self.sounds { - sound.process(&mut self.temp_buffer[..out.len()], dt, &info); + + // apply the playback rate computed by the doppler effect if applicable + if let Some(spatial_data) = &mut self.spatial_data { + if let Some(playback_multiplier) = spatial_data.current_doppler_scale { + sound.process(&mut self.temp_buffer[..out.len()], dt * playback_multiplier, &info); + } else { + sound.process(&mut self.temp_buffer[..out.len()], dt, &info); + } + } else { + sound.process(&mut self.temp_buffer[..out.len()], dt, &info); + } + for (summed_out, sound_out) in out.iter_mut().zip(self.temp_buffer.iter().copied()) { *summed_out += sound_out; } @@ -208,6 +221,10 @@ impl Track { // apply spatialization if let Some(spatial_data) = &mut self.spatial_data { spatial_data.position.update(dt * out.len() as f64, &info); + spatial_data.game_loop_delta_time.update(dt * out.len() as f64, &info); + if spatial_data.doppler_effect { + spatial_data.current_doppler_scale = spatial_data.compute_doppler_scale(&info); + } spatial_data .spatialization_strength .update(dt * out.len() as f64, &info); @@ -259,10 +276,12 @@ impl Track { if let Some(SpatialData { position, spatialization_strength, + game_loop_delta_time, .. }) = &mut self.spatial_data { position.read_command(&mut self.command_readers.set_position); + game_loop_delta_time.read_command(&mut self.command_readers.set_game_loop_delta_time); spatialization_strength .read_command(&mut self.command_readers.set_spatialization_strength); } @@ -278,6 +297,7 @@ impl Track { struct SpatialData { listener_id: ListenerId, position: Parameter, + game_loop_delta_time: Parameter, /// The distances from a listener at which the track is loudest and quietest. distances: SpatialTrackDistances, /// How the track's volume will change with distance. @@ -290,9 +310,56 @@ struct SpatialData { /// This value should be between `0.0` and `1.0`. `0.0` disables spatialization /// entirely. spatialization_strength: Parameter, + /// The actual playback rate computed by the doppler effect. + current_doppler_scale: Option, + /// Speed of sound in units per second + speed_of_sound: Parameter, + /// Doppler effect enabled. + doppler_effect: bool, } impl SpatialData { + fn compute_doppler_scale(&mut self, info: &Info) -> Option { + if let (Some(listener), Some(spatial)) = (info.listener_info(), info.spatial_track_info()) { + let emitter_position = Vec3::from(spatial.position); + let listener_position = Vec3::from(listener.position); + let direction_vector = listener_position - emitter_position; + + // Handle the case where objects are at the same position + if direction_vector.length_squared() < 1e-6 { + return Some(1.0); + } + + let direction = direction_vector.normalize(); + + let emitter_velocity = spatial.velocity(); + let listener_velocity = listener.velocity(); + + let v_emitter = emitter_velocity.dot(direction.into()); + let v_listener = -listener_velocity.dot(direction.into()); + + // "Sonic boom" + // It may sonic boom at the start of the emitter's life because the previous position + // is 0,0,0 not sure how to get around this, or if it even matters. + // But it would be kind of cool to emit an event when this happens. + let c = self.speed_of_sound.value(); + if v_emitter > c { + return Some(1.0); + } + + let scale = (c + v_listener) / (c - v_emitter); + + // Failsafe + if scale.is_nan() { + return Some(1.0); + } + + Some(scale) + } else { + None + } + } + fn spatialize( &self, input: Frame, @@ -369,6 +436,7 @@ fn listener_ear_directions(listener_orientation: Quat) -> (Vec3, Vec3) { command_writers_and_readers! { set_volume: ValueChangeCommand, set_position: ValueChangeCommand, + set_game_loop_delta_time: ValueChangeCommand, set_spatialization_strength: ValueChangeCommand, pause: Tween, resume: (StartTime, Tween), diff --git a/crates/kira/src/track/sub/handle.rs b/crates/kira/src/track/sub/handle.rs index 1d978236..3c7c86f9 100644 --- a/crates/kira/src/track/sub/handle.rs +++ b/crates/kira/src/track/sub/handle.rs @@ -71,6 +71,7 @@ impl TrackHandle { &mut self, listener: impl Into, position: impl Into>>, + game_loop_delta_time: impl Into>, builder: SpatialTrackBuilder, ) -> Result { let (mut track, handle) = builder.build( @@ -78,6 +79,7 @@ impl TrackHandle { self.internal_buffer_size, listener.into(), position.into().to_(), + game_loop_delta_time.into().to_(), ); track.init_effects(self.renderer_shared.sample_rate.load(Ordering::SeqCst)); self.sub_track_controller.insert(track)?; diff --git a/crates/kira/src/track/sub/spatial_builder.rs b/crates/kira/src/track/sub/spatial_builder.rs index 7806a2cf..15ccd182 100644 --- a/crates/kira/src/track/sub/spatial_builder.rs +++ b/crates/kira/src/track/sub/spatial_builder.rs @@ -42,6 +42,10 @@ pub struct SpatialTrackBuilder { /// This value should be between `0.0` and `1.0`. `0.0` disables spatialization /// entirely. pub(crate) spatialization_strength: Value, + /// The speed of sound in the scene, to make the doppler effect more obvious you can set this to a lower value. + pub(crate) speed_of_sound: Value, + /// Doppler effect enabled. + pub(crate) doppler_effect: bool, } impl SpatialTrackBuilder { @@ -58,6 +62,8 @@ impl SpatialTrackBuilder { distances: SpatialTrackDistances::default(), attenuation_function: Some(Easing::Linear), spatialization_strength: Value::Fixed(0.75), + speed_of_sound: Value::Fixed(343.0), + doppler_effect: false, } } @@ -135,6 +141,22 @@ impl SpatialTrackBuilder { self } + /// Sets the speed of sound, used for the doppler effect. + #[must_use = "This method consumes self and returns a modified SpatialTrackBuilder, so the return value should be used"] + pub fn speed_of_sound(self, speed_of_sound: impl Into>) -> Self { + Self { + speed_of_sound: speed_of_sound.into(), + ..self + } + } + + /// Enables the doppler effect. + #[must_use = "This method consumes self and returns a modified SpatialTrackBuilder, so the return value should be used"] + pub fn doppler_effect(mut self, doppler_effect: bool) -> Self { + self.doppler_effect = doppler_effect; + self + } + /** Adds an effect to the track. @@ -283,6 +305,7 @@ impl SpatialTrackBuilder { internal_buffer_size: usize, listener_id: ListenerId, position: Value, + game_loop_delta_time: Value, ) -> (Track, SpatialTrackHandle) { let (command_writers, command_readers) = command_writers_and_readers(); let shared = Arc::new(TrackShared::new()); @@ -314,9 +337,13 @@ impl SpatialTrackBuilder { spatial_data: Some(SpatialData { listener_id, position: Parameter::new(position, Vec3::ZERO), + game_loop_delta_time: Parameter::new(game_loop_delta_time, 0.0), distances: self.distances, attenuation_function: self.attenuation_function, spatialization_strength: Parameter::new(self.spatialization_strength, 0.75), + current_doppler_scale: None, + speed_of_sound: Parameter::new(self.speed_of_sound, 343.0), + doppler_effect: self.doppler_effect, }), playback_state_manager: PlaybackStateManager::new(None), temp_buffer: vec![Frame::ZERO; internal_buffer_size], diff --git a/crates/kira/src/track/sub/spatial_handle.rs b/crates/kira/src/track/sub/spatial_handle.rs index c09d16e1..405dafc0 100644 --- a/crates/kira/src/track/sub/spatial_handle.rs +++ b/crates/kira/src/track/sub/spatial_handle.rs @@ -71,6 +71,7 @@ impl SpatialTrackHandle { &mut self, listener: impl Into, position: impl Into>>, + game_loop_delta_time: impl Into>, builder: SpatialTrackBuilder, ) -> Result { let (mut track, handle) = builder.build( @@ -78,6 +79,7 @@ impl SpatialTrackHandle { self.internal_buffer_size, listener.into(), position.into().to_(), + game_loop_delta_time.into().to_(), ); track.init_effects(self.renderer_shared.sample_rate.load(Ordering::SeqCst)); self.sub_track_controller.insert(track)?; @@ -101,6 +103,15 @@ impl SpatialTrackHandle { }) } + /// Sets the delta time of the game loop. Needed for things like the doppler effect. + pub fn set_game_loop_delta_time(&mut self, game_loop_delta_time: f64) { + let game_loop_delta_time: Value = game_loop_delta_time.into(); + self.command_writers.set_game_loop_delta_time.write(ValueChangeCommand { + target: game_loop_delta_time.to_(), + tween: Tween::default(), + }) + } + /// Sets how much the track's output should be panned left or right depending on its /// direction from the listener. ///