Skip to content

Commit

Permalink
lerp between previous and current parameter values
Browse files Browse the repository at this point in the history
this prevents discontinuities in some kinds of modulation where that matters, especially volume modulation

todo:
- test thoroughly
- make sure i've chosen the right parameters to lerp in the effects
  • Loading branch information
tesselode committed Dec 23, 2024
1 parent cb54874 commit 77cbcf6
Show file tree
Hide file tree
Showing 14 changed files with 141 additions and 54 deletions.
12 changes: 8 additions & 4 deletions crates/kira/src/effect/compressor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 + 1) 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(),
Expand All @@ -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()
}
}
}
Expand Down
11 changes: 7 additions & 4 deletions crates/kira/src/effect/distortion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 + 1) 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 => {
Expand All @@ -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()
}
}
}
Expand Down
6 changes: 4 additions & 2 deletions crates/kira/src/effect/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 + 1) 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));
Expand All @@ -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()
}
}
Expand Down
6 changes: 4 additions & 2 deletions crates/kira/src/effect/panning_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 + 1) as f64 / num_frames as f64;
*frame = frame.panned(self.panning.interpolated_value(time_in_chunk))
}
}
}
Expand Down
9 changes: 6 additions & 3 deletions crates/kira/src/effect/reverb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 + 1) 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
Expand All @@ -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 {
Expand Down
6 changes: 4 additions & 2 deletions crates/kira/src/effect/volume_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 + 1) as f64 / num_frames as f64;
*frame *= self.volume.interpolated_value(time_in_chunk).as_amplitude();
}
}
}
Expand Down
26 changes: 25 additions & 1 deletion crates/kira/src/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -158,6 +160,28 @@ pub struct ListenerInfo {
pub position: mint::Vector3<f32>,
/// The rotation of the listener.
pub orientation: mint::Quaternion<f32>,
/// The position of the listener prior to the last update.
pub previous_position: mint::Vector3<f32>,
/// The rotation of the listener prior to the last update.
pub previous_orientation: mint::Quaternion<f32>,
}

impl ListenerInfo {
/// Returns the interpolated position between the previous and current
/// position of the listener.
pub fn interpolated_position(self, amount: f32) -> mint::Vector3<f32> {
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<f32> {
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.
Expand Down
4 changes: 4 additions & 0 deletions crates/kira/src/playback_state_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
21 changes: 13 additions & 8 deletions crates/kira/src/sound/static_sound/sound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 + 1) 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);
}
}

Expand Down
21 changes: 13 additions & 8 deletions crates/kira/src/sound/streaming/sound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 + 1) 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();
Expand All @@ -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);
}
}

Expand Down
7 changes: 5 additions & 2 deletions crates/kira/src/track/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 + 1) as f64 / num_frames as f64;
let volume = self.volume.interpolated_value(time_in_chunk).as_amplitude();
*frame *= volume;
}
}
}
7 changes: 5 additions & 2 deletions crates/kira/src/track/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 + 1) as f64 / num_frames as f64;
let volume = self.volume.interpolated_value(time_in_chunk).as_amplitude();
*frame *= volume;
}
}
}
Expand Down
32 changes: 21 additions & 11 deletions crates/kira/src/track/sub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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()],
Expand Down Expand Up @@ -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 + 1) 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 {
Expand Down
Loading

0 comments on commit 77cbcf6

Please sign in to comment.