Skip to content

Commit

Permalink
use buffered processing
Browse files Browse the repository at this point in the history
todo:
- re-enable delay effect
- figure out if clocks need more precision
  • Loading branch information
tesselode committed Dec 15, 2024
1 parent eb530c6 commit 4c29057
Show file tree
Hide file tree
Showing 31 changed files with 462 additions and 363 deletions.
12 changes: 10 additions & 2 deletions crates/benchmarks/benches/benchmarks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,13 @@ fn sounds(c: &mut Criterion) {
manager.play(sound_data.clone()).unwrap();
}
manager.backend_mut().on_start_processing();
b.iter(|| manager.backend_mut().process());
let mut num_iterations = 0;
b.iter(|| {
if num_iterations % 128 == 0 {
manager.backend_mut().process();
}
num_iterations += 1
});
});

// similar to "simple", but also periodically calls the
Expand Down Expand Up @@ -78,7 +84,9 @@ fn sounds(c: &mut Criterion) {
if num_iterations % 1000 == 0 {
manager.backend_mut().on_start_processing();
}
let _ = manager.backend_mut().process();
if num_iterations % 128 == 0 {
manager.backend_mut().process();
}
num_iterations += 1;
});
});
Expand Down
9 changes: 4 additions & 5 deletions crates/kira/src/effect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ and [`Effect`] traits.
*/

pub mod compressor;
pub mod delay;
// pub mod delay;
pub mod distortion;
pub mod eq_filter;
pub mod filter;
Expand Down Expand Up @@ -48,9 +48,8 @@ pub trait Effect: Send {
/// but not for every single audio sample.
fn on_start_processing(&mut self) {}

/// Transforms an input [`Frame`].
/// Transforms a slice of input [`Frame`]s.
///
/// `dt` is the time that's elapsed since the previous round of
/// processing (in seconds).
fn process(&mut self, input: Frame, dt: f64, info: &Info) -> Frame;
/// `dt` is the time between each frame (in seconds).
fn process(&mut self, input: &mut [Frame], dt: f64, info: &Info);
}
69 changes: 36 additions & 33 deletions crates/kira/src/effect/compressor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,46 +70,49 @@ impl Effect for Compressor {
);
}

fn process(&mut self, input: Frame, dt: f64, info: &Info) -> Frame {
self.threshold.update(dt, info);
self.ratio.update(dt, info);
self.attack_duration.update(dt, info);
self.release_duration.update(dt, info);
self.makeup_gain.update(dt, info);
self.mix.update(dt, info);
fn process(&mut self, input: &mut [Frame], dt: f64, info: &Info) {
self.threshold.update(dt * input.len() as f64, info);
self.ratio.update(dt * input.len() as f64, info);
self.attack_duration.update(dt * input.len() as f64, info);
self.release_duration.update(dt * input.len() as f64, info);
self.makeup_gain.update(dt * input.len() as f64, info);
self.mix.update(dt * input.len() as f64, info);

let threshold = self.threshold.value() as f32;
let ratio = self.ratio.value() as f32;
let attack_duration = self.attack_duration.value();
let release_duration = self.release_duration.value();

let input_decibels = [
20.0 * input.left.abs().log10(),
20.0 * input.right.abs().log10(),
];
let over_decibels = input_decibels.map(|input| (input - threshold).max(0.0));
for (i, envelope_follower) in self.envelope_follower.iter_mut().enumerate() {
let duration = if *envelope_follower > over_decibels[i] {
release_duration
} else {
attack_duration
};
let speed = (-1.0 / (duration.as_secs_f64() / dt)).exp();
*envelope_follower =
over_decibels[i] + speed as f32 * (*envelope_follower - over_decibels[i]);
}
let gain_reduction = self
.envelope_follower
.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 output = Frame {
left: amplitude[0] * input.left,
right: amplitude[1] * input.right,
} * makeup_gain_linear;
for frame in input {
let input_decibels = [
20.0 * frame.left.abs().log10(),
20.0 * frame.right.abs().log10(),
];
let over_decibels = input_decibels.map(|input| (input - threshold).max(0.0));
for (i, envelope_follower) in self.envelope_follower.iter_mut().enumerate() {
let duration = if *envelope_follower > over_decibels[i] {
release_duration
} else {
attack_duration
};
let speed = (-1.0 / (duration.as_secs_f64() / dt)).exp();
*envelope_follower =
over_decibels[i] + speed as f32 * (*envelope_follower - over_decibels[i]);
}
let gain_reduction = self
.envelope_follower
.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 output = Frame {
left: amplitude[0] * frame.left,
right: amplitude[1] * frame.right,
} * makeup_gain_linear;

let mix = self.mix.value().0;
output * mix.sqrt() + input * (1.0 - mix).sqrt()
let mix = self.mix.value().0;
*frame = output * mix.sqrt() + *frame * (1.0 - mix).sqrt()
}
}
}

Expand Down
8 changes: 4 additions & 4 deletions crates/kira/src/effect/delay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,16 +95,16 @@ impl Effect for Delay {
}
}

fn process(&mut self, input: Frame, dt: f64, info: &Info) -> Frame {
fn process(&mut self, input: &mut [Frame], dt: f64, info: &Info) {
if let DelayState::Initialized {
buffer,
write_position,
..
} = &mut self.state
{
self.delay_time.update(dt, info);
self.feedback.update(dt, info);
self.mix.update(dt, info);
self.delay_time.update(dt * input.len() as f64, info);
self.feedback.update(dt * input.len() as f64, info);
self.mix.update(dt * input.len() as f64, info);

// get the read position (in samples)
let mut read_position = *write_position as f32 - (self.delay_time.value() / dt) as f32;
Expand Down
35 changes: 19 additions & 16 deletions crates/kira/src/effect/distortion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,24 +55,27 @@ impl Effect for Distortion {
read_commands_into_parameters!(self, drive, mix);
}

fn process(&mut self, input: Frame, dt: f64, info: &Info) -> Frame {
self.drive.update(dt, info);
self.mix.update(dt, info);
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();
let mut output = input * drive;
output = match self.kind {
DistortionKind::HardClip => {
Frame::new(output.left.clamp(-1.0, 1.0), output.right.clamp(-1.0, 1.0))
}
DistortionKind::SoftClip => Frame::new(
output.left / (1.0 + output.left.abs()),
output.right / (1.0 + output.right.abs()),
),
};
output /= drive;

let mix = self.mix.value().0;
output * mix.sqrt() + input * (1.0 - mix).sqrt()
for frame in input {
let mut output = *frame * drive;
output = match self.kind {
DistortionKind::HardClip => {
Frame::new(output.left.clamp(-1.0, 1.0), output.right.clamp(-1.0, 1.0))
}
DistortionKind::SoftClip => Frame::new(
output.left / (1.0 + output.left.abs()),
output.right / (1.0 + output.right.abs()),
),
};
output /= drive;

let mix = self.mix.value().0;
*frame = output * mix.sqrt() + *frame * (1.0 - mix).sqrt()
}
}
}

Expand Down
23 changes: 13 additions & 10 deletions crates/kira/src/effect/eq_filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,10 @@ impl Effect for EqFilter {
read_commands_into_parameters!(self, frequency, gain, q);
}

fn process(&mut self, input: Frame, dt: f64, info: &Info) -> Frame {
self.frequency.update(dt, info);
self.gain.update(dt, info);
self.q.update(dt, info);
fn process(&mut self, input: &mut [Frame], dt: f64, info: &Info) {
self.frequency.update(dt * input.len() as f64, info);
self.gain.update(dt * input.len() as f64, info);
self.q.update(dt * input.len() as f64, info);
let Coefficients {
a1,
a2,
Expand All @@ -135,12 +135,15 @@ impl Effect for EqFilter {
m1,
m2,
} = self.calculate_coefficients(dt);
let v3 = input - 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);
self.ic1eq = v1 * 2.0 - self.ic1eq;
self.ic2eq = v2 * 2.0 - self.ic2eq;
input * (m0 as f32) + v1 * (m1 as f32) + v2 * (m2 as f32)

for frame in input {
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);
self.ic1eq = v1 * 2.0 - self.ic1eq;
self.ic2eq = v2 * 2.0 - self.ic2eq;
*frame = *frame * (m0 as f32) + v1 * (m1 as f32) + v2 * (m2 as f32)
}
}
}

Expand Down
37 changes: 20 additions & 17 deletions crates/kira/src/effect/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,29 +69,32 @@ impl Effect for Filter {
read_commands_into_parameters!(self, cutoff, resonance, mix);
}

fn process(&mut self, input: Frame, dt: f64, info: &Info) -> Frame {
self.cutoff.update(dt, info);
self.resonance.update(dt, info);
self.mix.update(dt, info);
fn process(&mut self, input: &mut [Frame], dt: f64, info: &Info) {
self.cutoff.update(dt * input.len() as f64, info);
self.resonance.update(dt * input.len() as f64, info);
self.mix.update(dt * input.len() as f64, info);
let sample_rate = 1.0 / dt;
let g = (PI * (self.cutoff.value() / sample_rate)).tan();
let k = 2.0 - (1.9 * self.resonance.value().clamp(0.0, 1.0));
let a1 = 1.0 / (1.0 + (g * (g + k)));
let a2 = g * a1;
let a3 = g * a2;
let v3 = input - 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));
self.ic1eq = (v1 * 2.0) - self.ic1eq;
self.ic2eq = (v2 * 2.0) - self.ic2eq;
let output = match self.mode {
FilterMode::LowPass => v2,
FilterMode::BandPass => v1,
FilterMode::HighPass => input - v1 * (k as f32) - v2,
FilterMode::Notch => input - v1 * (k as f32),
};
let mix = self.mix.value().0;
output * mix.sqrt() + input * (1.0 - mix).sqrt()

for frame in input {
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));
self.ic1eq = (v1 * 2.0) - self.ic1eq;
self.ic2eq = (v2 * 2.0) - self.ic2eq;
let output = match self.mode {
FilterMode::LowPass => v2,
FilterMode::BandPass => v1,
FilterMode::HighPass => *frame - v1 * (k as f32) - v2,
FilterMode::Notch => *frame - v1 * (k as f32),
};
let mix = self.mix.value().0;
*frame = output * mix.sqrt() + *frame * (1.0 - mix).sqrt()
}
}
}

Expand Down
8 changes: 5 additions & 3 deletions crates/kira/src/effect/panning_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ impl Effect for PanningControl {
read_commands_into_parameters!(self, panning);
}

fn process(&mut self, input: Frame, dt: f64, info: &Info) -> Frame {
self.panning.update(dt, info);
input.panned(self.panning.value())
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())
}
}
}

Expand Down
50 changes: 26 additions & 24 deletions crates/kira/src/effect/reverb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,41 +138,43 @@ impl Effect for Reverb {
read_commands_into_parameters!(self, feedback, damping, stereo_width, mix);
}

fn process(&mut self, input: Frame, dt: f64, info: &Info) -> Frame {
fn process(&mut self, input: &mut [Frame], dt: f64, info: &Info) {
if let ReverbState::Initialized {
comb_filters,
all_pass_filters,
} = &mut self.state
{
self.feedback.update(dt, info);
self.damping.update(dt, info);
self.stereo_width.update(dt, info);
self.mix.update(dt, info);
self.feedback.update(dt * input.len() as f64, info);
self.damping.update(dt * input.len() as f64, info);
self.stereo_width.update(dt * input.len() as f64, info);
self.mix.update(dt * input.len() as f64, info);

let feedback = self.feedback.value() as f32;
let damping = self.damping.value() as f32;
let stereo_width = self.stereo_width.value() as f32;

let mut output = Frame::ZERO;
let mono_input = (input.left + input.right) * GAIN;
// accumulate comb filters in parallel
for comb_filter in comb_filters {
output.left += comb_filter.0.process(mono_input, feedback, damping);
output.right += comb_filter.1.process(mono_input, feedback, damping);
for frame in input {
let mut output = Frame::ZERO;
let mono_input = (frame.left + frame.right) * GAIN;
// accumulate comb filters in parallel
for comb_filter in comb_filters.iter_mut() {
output.left += comb_filter.0.process(mono_input, feedback, damping);
output.right += comb_filter.1.process(mono_input, feedback, damping);
}
// feed through all-pass filters in series
for all_pass_filter in all_pass_filters.iter_mut() {
output.left = all_pass_filter.0.process(output.left);
output.right = all_pass_filter.1.process(output.right);
}
let wet_1 = stereo_width / 2.0 + 0.5;
let wet_2 = (1.0 - stereo_width) / 2.0;
let output = Frame::new(
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()
}
// feed through all-pass filters in series
for all_pass_filter in all_pass_filters {
output.left = all_pass_filter.0.process(output.left);
output.right = all_pass_filter.1.process(output.right);
}
let wet_1 = stereo_width / 2.0 + 0.5;
let wet_2 = (1.0 - stereo_width) / 2.0;
let output = Frame::new(
output.left * wet_1 + output.right * wet_2,
output.right * wet_1 + output.left * wet_2,
);
let mix = self.mix.value().0;
output * mix.sqrt() + input * (1.0 - mix).sqrt()
} else {
panic!("Reverb should be initialized before the first process call")
}
Expand Down
8 changes: 5 additions & 3 deletions crates/kira/src/effect/volume_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ impl Effect for VolumeControl {
read_commands_into_parameters!(self, volume);
}

fn process(&mut self, input: Frame, dt: f64, info: &Info) -> Frame {
self.volume.update(dt, info);
input * self.volume.value().as_amplitude()
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();
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions crates/kira/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@ and compile times for games.
#![warn(missing_docs)]
#![allow(clippy::tabs_in_doc_comments)]

const INTERNAL_BUFFER_SIZE: usize = 128;

mod arena;
pub mod clock;
pub mod command;
Expand Down
Loading

0 comments on commit 4c29057

Please sign in to comment.