diff --git a/crates/kira/src/effect/compressor.rs b/crates/kira/src/effect/compressor.rs index 3ed77f04..daed5a61 100644 --- a/crates/kira/src/effect/compressor.rs +++ b/crates/kira/src/effect/compressor.rs @@ -83,7 +83,12 @@ impl Effect for Compressor { let attack_duration = self.attack_duration.value(); let release_duration = self.release_duration.value(); - for frame in input { + let num_frames = input.len(); + for (i, frame) in input.iter_mut().enumerate() { + let time_in_chunk = i as f64 / num_frames as f64; + let makeup_gain = self.makeup_gain.interpolated_value(time_in_chunk); + let mix = self.mix.interpolated_value(time_in_chunk); + let input_decibels = [ 20.0 * frame.left.abs().log10(), 20.0 * frame.right.abs().log10(), @@ -104,14 +109,13 @@ impl Effect for Compressor { .map(|envelope_follower| envelope_follower * ((1.0 / ratio) - 1.0)); let amplitude = gain_reduction.map(|gain_reduction| 10.0f32.powf(gain_reduction / 20.0)); - let makeup_gain_linear = 10.0f32.powf(self.makeup_gain.value().0 / 20.0); + let makeup_gain_linear = 10.0f32.powf(makeup_gain.0 / 20.0); let output = Frame { left: amplitude[0] * frame.left, right: amplitude[1] * frame.right, } * makeup_gain_linear; - let mix = self.mix.value().0; - *frame = output * mix.sqrt() + *frame * (1.0 - mix).sqrt() + *frame = output * mix.0.sqrt() + *frame * (1.0 - mix.0).sqrt() } } } diff --git a/crates/kira/src/effect/distortion.rs b/crates/kira/src/effect/distortion.rs index c221d7ec..64e4048a 100644 --- a/crates/kira/src/effect/distortion.rs +++ b/crates/kira/src/effect/distortion.rs @@ -58,9 +58,13 @@ impl Effect for Distortion { fn process(&mut self, input: &mut [Frame], dt: f64, info: &Info) { self.drive.update(dt * input.len() as f64, info); self.mix.update(dt * input.len() as f64, info); - let drive = self.drive.value().as_amplitude(); - for frame in input { + let num_frames = input.len(); + for (i, frame) in input.iter_mut().enumerate() { + let time_in_chunk = i as f64 / num_frames as f64; + let drive = self.drive.interpolated_value(time_in_chunk).as_amplitude(); + let mix = self.mix.interpolated_value(time_in_chunk); + let mut output = *frame * drive; output = match self.kind { DistortionKind::HardClip => { @@ -73,8 +77,7 @@ impl Effect for Distortion { }; output /= drive; - let mix = self.mix.value().0; - *frame = output * mix.sqrt() + *frame * (1.0 - mix).sqrt() + *frame = output * mix.0.sqrt() + *frame * (1.0 - mix.0).sqrt() } } } diff --git a/crates/kira/src/effect/filter.rs b/crates/kira/src/effect/filter.rs index 6f779796..fd53ac1f 100644 --- a/crates/kira/src/effect/filter.rs +++ b/crates/kira/src/effect/filter.rs @@ -80,7 +80,9 @@ impl Effect for Filter { let a2 = g * a1; let a3 = g * a2; - for frame in input { + let num_frames = input.len(); + for (i, frame) in input.iter_mut().enumerate() { + let time_in_chunk = i as f64 / num_frames as f64; let v3 = *frame - self.ic2eq; let v1 = (self.ic1eq * (a1 as f32)) + (v3 * (a2 as f32)); let v2 = self.ic2eq + (self.ic1eq * (a2 as f32)) + (v3 * (a3 as f32)); @@ -92,7 +94,7 @@ impl Effect for Filter { FilterMode::HighPass => *frame - v1 * (k as f32) - v2, FilterMode::Notch => *frame - v1 * (k as f32), }; - let mix = self.mix.value().0; + let mix = self.mix.interpolated_value(time_in_chunk).0; *frame = output * mix.sqrt() + *frame * (1.0 - mix).sqrt() } } diff --git a/crates/kira/src/effect/panning_control.rs b/crates/kira/src/effect/panning_control.rs index edf3a9cb..302a67b9 100644 --- a/crates/kira/src/effect/panning_control.rs +++ b/crates/kira/src/effect/panning_control.rs @@ -39,8 +39,10 @@ impl Effect for PanningControl { fn process(&mut self, input: &mut [Frame], dt: f64, info: &Info) { self.panning.update(dt * input.len() as f64, info); - for frame in input { - *frame = frame.panned(self.panning.value()) + let num_frames = input.len(); + for (i, frame) in input.iter_mut().enumerate() { + let time_in_chunk = i as f64 / num_frames as f64; + *frame = frame.panned(self.panning.interpolated_value(time_in_chunk)) } } } diff --git a/crates/kira/src/effect/reverb.rs b/crates/kira/src/effect/reverb.rs index 1c287df1..0217d570 100644 --- a/crates/kira/src/effect/reverb.rs +++ b/crates/kira/src/effect/reverb.rs @@ -151,9 +151,13 @@ impl Effect for Reverb { let feedback = self.feedback.value() as f32; let damping = self.damping.value() as f32; - let stereo_width = self.stereo_width.value() as f32; - for frame in input { + let num_frames = input.len(); + for (i, frame) in input.iter_mut().enumerate() { + let time_in_chunk = i as f64 / num_frames as f64; + let stereo_width = self.stereo_width.interpolated_value(time_in_chunk) as f32; + let mix = self.mix.interpolated_value(time_in_chunk).0; + let mut output = Frame::ZERO; let mono_input = (frame.left + frame.right) * GAIN; // accumulate comb filters in parallel @@ -172,7 +176,6 @@ impl Effect for Reverb { output.left * wet_1 + output.right * wet_2, output.right * wet_1 + output.left * wet_2, ); - let mix = self.mix.value().0; *frame = output * mix.sqrt() + *frame * (1.0 - mix).sqrt() } } else { diff --git a/crates/kira/src/effect/volume_control.rs b/crates/kira/src/effect/volume_control.rs index 70e32602..4a043a80 100644 --- a/crates/kira/src/effect/volume_control.rs +++ b/crates/kira/src/effect/volume_control.rs @@ -39,8 +39,10 @@ impl Effect for VolumeControl { fn process(&mut self, input: &mut [Frame], dt: f64, info: &Info) { self.volume.update(dt * input.len() as f64, info); - for frame in input { - *frame *= self.volume.value().as_amplitude(); + let num_frames = input.len(); + for (i, frame) in input.iter_mut().enumerate() { + let time_in_chunk = i as f64 / num_frames as f64; + *frame *= self.volume.interpolated_value(time_in_chunk).as_amplitude(); } } } diff --git a/crates/kira/src/info.rs b/crates/kira/src/info.rs index 03b4d156..06ed0da1 100644 --- a/crates/kira/src/info.rs +++ b/crates/kira/src/info.rs @@ -5,7 +5,7 @@ * like [`Sound`](crate::sound::Sound) or [`Effect`](crate::effect::Effect). */ -use glam::Vec3; +use glam::{Quat, Vec3}; use crate::{ arena::Arena, @@ -106,6 +106,8 @@ impl<'a> Info<'a> { listeners.get(listener_id.0).map(|listener| ListenerInfo { position: listener.position.value().into(), orientation: listener.orientation.value().into(), + previous_position: listener.position.previous_value().into(), + previous_orientation: listener.orientation.previous_value().into(), }) } InfoKind::Mock { listener_info, .. } => listener_info.get(listener_id.0).copied(), @@ -158,6 +160,28 @@ pub struct ListenerInfo { pub position: mint::Vector3, /// The rotation of the listener. pub orientation: mint::Quaternion, + /// The position of the listener prior to the last update. + pub previous_position: mint::Vector3, + /// The rotation of the listener prior to the last update. + pub previous_orientation: mint::Quaternion, +} + +impl ListenerInfo { + /// Returns the interpolated position between the previous and current + /// position of the listener. + pub fn interpolated_position(self, amount: f32) -> mint::Vector3 { + let position: Vec3 = self.position.into(); + let previous_position: Vec3 = self.previous_position.into(); + previous_position.lerp(position, amount).into() + } + + /// Returns the interpolated orientation between the previous and current + /// orientation of the listener. + pub fn interpolated_orientation(self, amount: f32) -> mint::Quaternion { + let orientation: Quat = self.orientation.into(); + let previous_orientation: Quat = self.previous_orientation.into(); + previous_orientation.lerp(orientation, amount).into() + } } /// Generates a fake `Info` with arbitrary data. Useful for writing unit tests. diff --git a/crates/kira/src/playback_state_manager.rs b/crates/kira/src/playback_state_manager.rs index fc284298..e6cc5466 100644 --- a/crates/kira/src/playback_state_manager.rs +++ b/crates/kira/src/playback_state_manager.rs @@ -31,6 +31,10 @@ impl PlaybackStateManager { self.volume_fade.value() } + pub fn interpolated_fade_volume(&self, amount: f64) -> Decibels { + self.volume_fade.interpolated_value(amount) + } + pub fn playback_state(&self) -> PlaybackState { match self.state { State::Playing => PlaybackState::Playing, diff --git a/crates/kira/src/sound/static_sound/sound.rs b/crates/kira/src/sound/static_sound/sound.rs index 3e60adde..a1c871bf 100644 --- a/crates/kira/src/sound/static_sound/sound.rs +++ b/crates/kira/src/sound/static_sound/sound.rs @@ -227,18 +227,23 @@ impl Sound for StaticSound { } // play back audio - for frame in out { - let out = self.resampler.get(self.fractional_position as f32); - self.fractional_position += - self.sample_rate as f64 * self.playback_rate.value().0.abs() * dt; + let num_frames = out.len(); + for (i, frame) in out.iter_mut().enumerate() { + let time_in_chunk = i as f64 / num_frames as f64; + let volume = self.volume.interpolated_value(time_in_chunk).as_amplitude(); + let fade_volume = self + .playback_state_manager + .interpolated_fade_volume(time_in_chunk) + .as_amplitude(); + let panning = self.panning.interpolated_value(time_in_chunk); + let playback_rate = self.playback_rate.interpolated_value(time_in_chunk); + let resampler_out = self.resampler.get(self.fractional_position as f32); + self.fractional_position += self.sample_rate as f64 * playback_rate.0.abs() * dt; while self.fractional_position >= 1.0 { self.fractional_position -= 1.0; self.update_position(); } - *frame = (out - * self.playback_state_manager.fade_volume().as_amplitude() - * self.volume.value().as_amplitude()) - .panned(self.panning.value()); + *frame = (resampler_out * fade_volume * volume).panned(panning); } } diff --git a/crates/kira/src/sound/streaming/sound.rs b/crates/kira/src/sound/streaming/sound.rs index b7998a51..d01ba493 100644 --- a/crates/kira/src/sound/streaming/sound.rs +++ b/crates/kira/src/sound/streaming/sound.rs @@ -241,17 +241,25 @@ impl Sound for StreamingSound { return; } - for frame in out { + let num_frames = out.len(); + for (i, frame) in out.iter_mut().enumerate() { + let time_in_chunk = i as f64 / num_frames as f64; + let volume = self.volume.interpolated_value(time_in_chunk).as_amplitude(); + let fade_volume = self + .playback_state_manager + .interpolated_fade_volume(time_in_chunk) + .as_amplitude(); + let panning = self.panning.interpolated_value(time_in_chunk); + let playback_rate = self.playback_rate.interpolated_value(time_in_chunk); let next_frames = self.next_frames(); - let out = interpolate_frame( + let interpolated_out = interpolate_frame( next_frames[0], next_frames[1], next_frames[2], next_frames[3], self.fractional_position as f32, ); - self.fractional_position += - self.sample_rate as f64 * self.playback_rate.value().0.max(0.0) * dt; + self.fractional_position += self.sample_rate as f64 * playback_rate.0.max(0.0) * dt; while self.fractional_position >= 1.0 { self.fractional_position -= 1.0; self.frame_consumer.pop().ok(); @@ -260,10 +268,7 @@ impl Sound for StreamingSound { self.playback_state_manager.mark_as_stopped(); self.update_shared_playback_state(); } - *frame = (out - * self.playback_state_manager.fade_volume().as_amplitude() - * self.volume.value().as_amplitude()) - .panned(self.panning.value()); + *frame = (interpolated_out * fade_volume * volume).panned(panning); } } diff --git a/crates/kira/src/track/main.rs b/crates/kira/src/track/main.rs index 6ba66557..626f6f39 100644 --- a/crates/kira/src/track/main.rs +++ b/crates/kira/src/track/main.rs @@ -59,8 +59,11 @@ impl MainTrack { for effect in &mut self.effects { effect.process(out, dt, info); } - for frame in out { - *frame *= self.volume.value().as_amplitude(); + let num_frames = out.len(); + for (i, frame) in out.iter_mut().enumerate() { + let time_in_chunk = i as f64 / num_frames as f64; + let volume = self.volume.interpolated_value(time_in_chunk).as_amplitude(); + *frame *= volume; } } } diff --git a/crates/kira/src/track/send.rs b/crates/kira/src/track/send.rs index 0c957f6a..50c5fbf2 100644 --- a/crates/kira/src/track/send.rs +++ b/crates/kira/src/track/send.rs @@ -76,8 +76,11 @@ impl SendTrack { for effect in &mut self.effects { effect.process(out, dt, info); } - for frame in out { - *frame *= self.volume.value().as_amplitude(); + let num_frames = out.len(); + for (i, frame) in out.iter_mut().enumerate() { + let time_in_chunk = i as f64 / num_frames as f64; + let volume = self.volume.interpolated_value(time_in_chunk).as_amplitude(); + *frame *= volume; } } } diff --git a/crates/kira/src/track/sub.rs b/crates/kira/src/track/sub.rs index 87d44cb4..ef927a8c 100644 --- a/crates/kira/src/track/sub.rs +++ b/crates/kira/src/track/sub.rs @@ -16,7 +16,7 @@ use crate::{ command::ValueChangeCommand, command_writers_and_readers, effect::Effect, - info::{Info, ListenerInfo, SpatialTrackInfo}, + info::{Info, SpatialTrackInfo}, listener::ListenerId, manager::backend::resources::{ clocks::Clocks, listeners::Listeners, modulators::Modulators, ResourceStorage, @@ -168,6 +168,7 @@ impl Track { return; } + let num_frames = out.len(); for (_, sub_track) in &mut self.sub_tracks { sub_track.process( &mut self.temp_buffer[..out.len()], @@ -195,19 +196,28 @@ impl Track { } if let Some(spatial_data) = &mut self.spatial_data { spatial_data.position.update(dt * out.len() as f64, &info); - if let Some(ListenerInfo { - position, - orientation, - }) = info.listener_info() - { - for frame in out.iter_mut() { - *frame = spatial_data.spatialize(*frame, position.into(), orientation.into()); + for (i, frame) in out.iter_mut().enumerate() { + let time_in_chunk = i as f32 / num_frames as f32; + if let Some(listener_info) = info.listener_info() { + let interpolated_position = listener_info.interpolated_position(time_in_chunk); + let interpolated_orientation = + listener_info.interpolated_orientation(time_in_chunk); + *frame = spatial_data.spatialize( + *frame, + interpolated_position.into(), + interpolated_orientation.into(), + ); } } } - for frame in out.iter_mut() { - *frame *= self.volume.value().as_amplitude() - * self.playback_state_manager.fade_volume().as_amplitude(); + for (i, frame) in out.iter_mut().enumerate() { + let time_in_chunk = i as f64 / num_frames as f64; + let volume = self.volume.interpolated_value(time_in_chunk).as_amplitude(); + let fade_volume = self + .playback_state_manager + .interpolated_fade_volume(time_in_chunk) + .as_amplitude(); + *frame *= volume * fade_volume; } for (send_track_id, SendTrackRoute { volume, .. }) in &self.sends { let Some(send_track) = send_tracks.get_mut(send_track_id.0) else { diff --git a/crates/kira/src/tween/parameter.rs b/crates/kira/src/tween/parameter.rs index 5891320b..32fb8493 100644 --- a/crates/kira/src/tween/parameter.rs +++ b/crates/kira/src/tween/parameter.rs @@ -27,6 +27,7 @@ use crate::{ pub struct Parameter { state: State, raw_value: T, + previous_raw_value: T, stagnant: bool, } @@ -37,15 +38,17 @@ impl Parameter { /// that doesn't exist. #[must_use] pub fn new(initial_value: Value, default_raw_value: T) -> Self { + let raw_value = match initial_value { + Value::Fixed(value) => value, + Value::FromModulator { .. } => default_raw_value, + Value::FromListenerDistance { .. } => default_raw_value, + }; Self { state: State::Idle { value: initial_value, }, - raw_value: match initial_value { - Value::Fixed(value) => value, - Value::FromModulator { .. } => default_raw_value, - Value::FromListenerDistance { .. } => default_raw_value, - }, + raw_value, + previous_raw_value: raw_value, stagnant: matches!(initial_value, Value::Fixed(_)), } } @@ -56,6 +59,19 @@ impl Parameter { self.raw_value } + /// Returns the previous actual value of the parameter. + #[must_use] + pub fn previous_value(&self) -> T { + self.previous_raw_value + } + + /// Returns the interpolated value between the previous and current + /// actual value of the parameter. + #[must_use] + pub fn interpolated_value(&self, amount: f64) -> T { + T::interpolate(self.previous_raw_value, self.raw_value, amount) + } + /// Starts a transition from the current value to the target value. pub fn set(&mut self, target: Value, tween: Tween) { self.stagnant = false; @@ -88,6 +104,7 @@ impl Parameter { } let just_finished_tween = self.update_tween(dt, info); if let Some(raw_value) = self.calculate_new_raw_value(info) { + self.previous_raw_value = self.raw_value; self.raw_value = raw_value; } just_finished_tween