diff --git a/crates/kira/src/effect.rs b/crates/kira/src/effect.rs index 24d76c00..ed791594 100644 --- a/crates/kira/src/effect.rs +++ b/crates/kira/src/effect.rs @@ -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; diff --git a/crates/kira/src/effect/delay.rs b/crates/kira/src/effect/delay.rs index f6153079..afa2967f 100644 --- a/crates/kira/src/effect/delay.rs +++ b/crates/kira/src/effect/delay.rs @@ -6,10 +6,12 @@ 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, @@ -17,25 +19,14 @@ use crate::{ use super::Effect; -#[derive(Debug, Clone)] -enum DelayState { - Uninitialized { - buffer_length: f64, - }, - Initialized { - buffer: Vec, - buffer_length: f64, - write_position: usize, - }, -} - struct Delay { command_readers: CommandReaders, - delay_time: Parameter, + delay_time: Duration, feedback: Parameter, mix: Parameter, - state: DelayState, + buffer: Vec, feedback_effects: Vec>, + temp_buffer: Vec, } impl Delay { @@ -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, set_feedback: ValueChangeCommand, set_mix: ValueChangeCommand, } diff --git a/crates/kira/src/effect/delay/builder.rs b/crates/kira/src/effect/delay/builder.rs index a20e2f1d..13ce34e0 100644 --- a/crates/kira/src/effect/delay/builder.rs +++ b/crates/kira/src/effect/delay/builder.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use crate::{ effect::{Effect, EffectBuilder}, tween::Value, @@ -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, + /// The amount of time the input audio is delayed by. + pub(super) delay_time: Duration, /// The amount of feedback. pub(super) feedback: Value, - /// 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>, /// How much dry (unprocessed) signal should be blended @@ -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>) -> 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. @@ -49,15 +45,6 @@ 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(&mut self, builder: B) -> B::Handle { let (effect, handle) = builder.build(); @@ -65,6 +52,14 @@ impl DelayBuilder { 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(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 @@ -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)), } diff --git a/crates/kira/src/effect/delay/handle.rs b/crates/kira/src/effect/delay/handle.rs index 1f42d3bd..5da27474 100644 --- a/crates/kira/src/effect/delay/handle.rs +++ b/crates/kira/src/effect/delay/handle.rs @@ -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,