Skip to content

Commit

Permalink
rewrite delay to work with buffers
Browse files Browse the repository at this point in the history
  • Loading branch information
tesselode committed Dec 24, 2024
1 parent 79ca3a7 commit 2dcf9b8
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 113 deletions.
2 changes: 1 addition & 1 deletion 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
136 changes: 49 additions & 87 deletions crates/kira/src/effect/delay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,27 @@ mod handle;
pub use builder::*;
pub use handle::*;

use std::time::Duration;

use crate::{
command::{read_commands_into_parameters, ValueChangeCommand},
command_writers_and_readers,
frame::{interpolate_frame, Frame},
frame::Frame,
info::Info,
tween::Parameter,
Decibels, Mix,
};

use super::Effect;

#[derive(Debug, Clone)]
enum DelayState {
Uninitialized {
buffer_length: f64,
},
Initialized {
buffer: Vec<Frame>,
buffer_length: f64,
write_position: usize,
},
}

struct Delay {
command_readers: CommandReaders,
delay_time: Parameter,
delay_time: Duration,
feedback: Parameter<Decibels>,
mix: Parameter<Mix>,
state: DelayState,
buffer: Vec<Frame>,
feedback_effects: Vec<Box<dyn Effect>>,
temp_buffer: Vec<Frame>,
}

impl Delay {
Expand All @@ -44,110 +35,81 @@ impl Delay {
fn new(builder: DelayBuilder, command_readers: CommandReaders) -> Self {
Self {
command_readers,
delay_time: Parameter::new(builder.delay_time, 0.5),
delay_time: builder.delay_time,
feedback: Parameter::new(builder.feedback, Decibels(-6.0)),
mix: Parameter::new(builder.mix, Mix(0.5)),
state: DelayState::Uninitialized {
buffer_length: builder.buffer_length,
},
buffer: Vec::with_capacity(0),
feedback_effects: builder.feedback_effects,
temp_buffer: vec![],
}
}
}

impl Effect for Delay {
fn init(&mut self, sample_rate: u32) {
if let DelayState::Uninitialized { buffer_length } = &self.state {
self.state = DelayState::Initialized {
buffer: vec![Frame::ZERO; (buffer_length * sample_rate as f64) as usize],
buffer_length: *buffer_length,
write_position: 0,
};
for effect in &mut self.feedback_effects {
effect.init(sample_rate);
}
} else {
panic!("The delay should be in the uninitialized state before init")
fn init(&mut self, sample_rate: u32, internal_buffer_size: usize) {
let delay_time_frames = (self.delay_time.as_secs_f64() * sample_rate as f64) as usize;
self.buffer = vec![Frame::ZERO; delay_time_frames];
self.temp_buffer = vec![Frame::ZERO; internal_buffer_size];
for effect in &mut self.feedback_effects {
effect.init(sample_rate, internal_buffer_size);
}
}

fn on_change_sample_rate(&mut self, sample_rate: u32) {
if let DelayState::Initialized {
buffer,
buffer_length,
write_position,
} = &mut self.state
{
*buffer = vec![Frame::ZERO; (*buffer_length * sample_rate as f64) as usize];
*write_position = 0;
for effect in &mut self.feedback_effects {
effect.on_change_sample_rate(sample_rate);
}
} else {
panic!("The delay should be initialized when the change sample rate callback is called")
let delay_time_frames = (self.delay_time.as_secs_f64() * sample_rate as f64) as usize;
self.buffer = vec![Frame::ZERO; delay_time_frames];
for effect in &mut self.feedback_effects {
effect.on_change_sample_rate(sample_rate);
}
}

fn on_start_processing(&mut self) {
read_commands_into_parameters!(self, delay_time, feedback, mix);
read_commands_into_parameters!(self, feedback, mix);
for effect in &mut self.feedback_effects {
effect.on_start_processing();
}
}

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 * 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;
while read_position < 0.0 {
read_position += buffer.len() as f32;
}
self.feedback.update(dt * input.len() as f64, info);
self.mix.update(dt * input.len() as f64, info);

for input in input.chunks_mut(self.buffer.len()) {
let num_frames = input.len();

// read an interpolated sample
let current_sample_index = read_position as usize;
let previous_sample_index = if current_sample_index == 0 {
buffer.len() - 2
} else {
current_sample_index - 1
};
let next_sample_index = (current_sample_index + 1) % buffer.len();
let next_sample_index_2 = (current_sample_index + 2) % buffer.len();
let fraction = read_position % 1.0;
let mut output = interpolate_frame(
buffer[previous_sample_index],
buffer[current_sample_index],
buffer[next_sample_index],
buffer[next_sample_index_2],
fraction,
);
// read from the beginning of the buffer and apply effects and feedback gain
self.temp_buffer[..input.len()].copy_from_slice(&self.buffer[..input.len()]);
for effect in &mut self.feedback_effects {
output = effect.process(output, dt, info);
effect.process(&mut self.temp_buffer[..input.len()], dt, info);
}
for (i, frame) in self.temp_buffer[..input.len()].iter_mut().enumerate() {
let time_in_chunk = (i + 1) as f64 / num_frames as f64;
let feedback = self.feedback.interpolated_value(time_in_chunk);
*frame *= feedback.as_amplitude();
}

// write output audio to the buffer
*write_position += 1;
*write_position %= buffer.len();
buffer[*write_position] = input + output * self.feedback.value().as_amplitude();
// write input + read buffer to the end of the buffer
self.buffer.copy_within(input.len().., 0);
let write_range = self.buffer.len() - input.len()..;
for ((out, input), read) in self.buffer[write_range]
.iter_mut()
.zip(input.iter())
.zip(&mut self.temp_buffer[..input.len()])
{
*out = *input + *read;
}

let mix = self.mix.value().0;
output * mix.sqrt() + input * (1.0 - mix).sqrt()
} else {
panic!("The delay should be initialized by the first process call")
// output mix of input and read buffer
for (i, frame) in input.iter_mut().enumerate() {
let time_in_chunk = (i + 1) as f64 / num_frames as f64;
let mix = self.mix.interpolated_value(time_in_chunk);
*frame = self.temp_buffer[i] * mix.0.sqrt() + *frame * (1.0 - mix.0).sqrt()
}
}
}
}

command_writers_and_readers! {
set_delay_time: ValueChangeCommand<f64>,
set_feedback: ValueChangeCommand<Decibels>,
set_mix: ValueChangeCommand<Mix>,
}
38 changes: 16 additions & 22 deletions crates/kira/src/effect/delay/builder.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::time::Duration;

use crate::{
effect::{Effect, EffectBuilder},
tween::Value,
Expand All @@ -8,13 +10,10 @@ use super::{command_writers_and_readers, Delay, DelayHandle};

/// Configures a delay effect.
pub struct DelayBuilder {
/// The delay time (in seconds).
pub(super) delay_time: Value<f64>,
/// The amount of time the input audio is delayed by.
pub(super) delay_time: Duration,
/// The amount of feedback.
pub(super) feedback: Value<Decibels>,
/// The amount of audio the delay can store (in seconds).
/// This affects the maximum delay time.
pub(super) buffer_length: f64,
/// Effects that should be applied in the feedback loop.
pub(super) feedback_effects: Vec<Box<dyn Effect>>,
/// How much dry (unprocessed) signal should be blended
Expand All @@ -31,13 +30,10 @@ impl DelayBuilder {
Self::default()
}

/// Sets the delay time (in seconds).
/// Sets the amount of time the input audio is delayed by.
#[must_use = "This method consumes self and returns a modified DelayBuilder, so the return value should be used"]
pub fn delay_time(self, delay_time: impl Into<Value<f64>>) -> Self {
Self {
delay_time: delay_time.into(),
..self
}
pub fn delay_time(self, delay_time: Duration) -> Self {
Self { delay_time, ..self }
}

/// Sets the amount of feedback.
Expand All @@ -49,22 +45,21 @@ impl DelayBuilder {
}
}

/// Sets the amount of audio the delay can store.
#[must_use = "This method consumes self and returns a modified DelayBuilder, so the return value should be used"]
pub fn buffer_length(self, buffer_length: f64) -> Self {
Self {
buffer_length,
..self
}
}

/// Adds an effect to the feedback loop.
pub fn add_feedback_effect<B: EffectBuilder>(&mut self, builder: B) -> B::Handle {
let (effect, handle) = builder.build();
self.feedback_effects.push(effect);
handle
}

/// Adds an effect to the feedback loop and returns the [`DelayBuilder`].
///
/// If you need a handle to the newly added effect, use [`DelayBuilder::add_feedback_effect`].
pub fn with_feedback_effect<B: EffectBuilder>(mut self, builder: B) -> Self {
self.add_feedback_effect(builder);
self
}

/// Sets how much dry (unprocessed) signal should be blended
/// with the wet (processed) signal. `0.0` means only the dry
/// signal will be heard. `1.0` means only the wet signal will
Expand All @@ -81,9 +76,8 @@ impl DelayBuilder {
impl Default for DelayBuilder {
fn default() -> Self {
Self {
delay_time: Value::Fixed(0.5),
delay_time: Duration::from_millis(500),
feedback: Value::Fixed(Decibels(-6.0)),
buffer_length: 10.0,
feedback_effects: vec![],
mix: Value::Fixed(Mix(0.5)),
}
Expand Down
3 changes: 0 additions & 3 deletions crates/kira/src/effect/delay/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ pub struct DelayHandle {

impl DelayHandle {
handle_param_setters! {
/// Sets the delay time (in seconds).
delay_time: f64,

/// Sets the amount of feedback.
feedback: Decibels,

Expand Down

0 comments on commit 2dcf9b8

Please sign in to comment.