use crate::{
utils::{DelayLine, ParameterSmoother},
AudioEffect,
};
#[derive(Debug, Clone)]
pub struct DelayConfig {
pub delay_ms: f32,
pub feedback: f32,
pub wet: f32,
pub dry: f32,
pub tone: f32,
}
impl Default for DelayConfig {
fn default() -> Self {
Self {
delay_ms: 500.0,
feedback: 0.4,
wet: 0.5,
dry: 0.5,
tone: 0.0,
}
}
}
impl DelayConfig {
#[must_use]
pub fn new(delay_ms: f32, feedback: f32, wet: f32) -> Self {
Self {
delay_ms: delay_ms.max(0.0),
feedback: feedback.clamp(0.0, 0.99),
wet: wet.clamp(0.0, 1.0),
dry: (1.0 - wet).clamp(0.0, 1.0),
tone: 0.0,
}
}
#[must_use]
pub fn slapback() -> Self {
Self::new(100.0, 0.0, 0.3)
}
#[must_use]
pub fn dotted_eighth() -> Self {
Self::new(375.0, 0.35, 0.4)
}
#[must_use]
pub fn ambient() -> Self {
Self::new(750.0, 0.6, 0.5).with_tone(0.4)
}
#[must_use]
pub fn with_tone(mut self, tone: f32) -> Self {
self.tone = tone.clamp(0.0, 1.0);
self
}
}
pub struct MonoDelay {
delay_line: DelayLine,
delay_samples: usize,
config: DelayConfig,
tone_filter: f32,
tone_smoother: ParameterSmoother,
sample_rate: f32,
}
impl MonoDelay {
#[must_use]
pub fn new(config: DelayConfig, sample_rate: f32) -> Self {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let max_delay_samples = ((2000.0 * sample_rate) / 1000.0) as usize;
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let delay_samples = ((config.delay_ms * sample_rate) / 1000.0) as usize;
Self {
delay_line: DelayLine::new(max_delay_samples),
delay_samples,
config,
tone_filter: 0.0,
tone_smoother: ParameterSmoother::new(10.0, sample_rate),
sample_rate,
}
}
pub fn set_delay_ms(&mut self, delay_ms: f32) {
self.config.delay_ms = delay_ms.max(0.0);
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let delay_samp = ((delay_ms * self.sample_rate) / 1000.0) as usize;
self.delay_samples = delay_samp.min(self.delay_line.max_delay());
}
pub fn set_feedback(&mut self, feedback: f32) {
self.config.feedback = feedback.clamp(0.0, 0.99);
}
pub fn set_wet(&mut self, wet: f32) {
self.config.wet = wet.clamp(0.0, 1.0);
}
pub fn set_dry(&mut self, dry: f32) {
self.config.dry = dry.clamp(0.0, 1.0);
}
pub fn set_tone(&mut self, tone: f32) {
self.config.tone = tone.clamp(0.0, 1.0);
self.tone_smoother.set_target(self.config.tone);
}
}
impl AudioEffect for MonoDelay {
fn process_sample(&mut self, input: f32) -> f32 {
let delayed = self.delay_line.read(self.delay_samples);
let tone = self.tone_smoother.next();
self.tone_filter = delayed * (1.0 - tone) + self.tone_filter * tone;
let feedback_signal = self.tone_filter * self.config.feedback;
self.delay_line.write(input + feedback_signal);
delayed * self.config.wet + input * self.config.dry
}
fn reset(&mut self) {
self.delay_line.clear();
self.tone_filter = 0.0;
self.tone_smoother.reset(0.0);
}
fn latency_samples(&self) -> usize {
0 }
}
pub struct StereoDelay {
left: MonoDelay,
right: MonoDelay,
cross_feedback: f32,
}
impl StereoDelay {
#[must_use]
pub fn new(config: DelayConfig, sample_rate: f32) -> Self {
Self {
left: MonoDelay::new(config.clone(), sample_rate),
right: MonoDelay::new(config, sample_rate),
cross_feedback: 0.0,
}
}
#[must_use]
pub fn new_dual(left_config: DelayConfig, right_config: DelayConfig, sample_rate: f32) -> Self {
Self {
left: MonoDelay::new(left_config, sample_rate),
right: MonoDelay::new(right_config, sample_rate),
cross_feedback: 0.0,
}
}
pub fn set_cross_feedback(&mut self, amount: f32) {
self.cross_feedback = amount.clamp(0.0, 0.99);
}
pub fn set_delay_ms(&mut self, delay_ms: f32) {
self.left.set_delay_ms(delay_ms);
self.right.set_delay_ms(delay_ms);
}
pub fn set_left_delay_ms(&mut self, delay_ms: f32) {
self.left.set_delay_ms(delay_ms);
}
pub fn set_right_delay_ms(&mut self, delay_ms: f32) {
self.right.set_delay_ms(delay_ms);
}
fn process_sample_internal(&mut self, input_l: f32, input_r: f32) -> (f32, f32) {
let out_l = self.left.process_sample(input_l);
let out_r = self.right.process_sample(input_r);
if self.cross_feedback > 0.0 {
let cross_l = out_r * self.cross_feedback;
let cross_r = out_l * self.cross_feedback;
(out_l + cross_l, out_r + cross_r)
} else {
(out_l, out_r)
}
}
}
impl AudioEffect for StereoDelay {
fn process_sample(&mut self, input: f32) -> f32 {
let (left, _right) = self.process_sample_internal(input, input);
left
}
fn process_sample_stereo(&mut self, left: f32, right: f32) -> (f32, f32) {
self.process_sample_internal(left, right)
}
fn reset(&mut self) {
self.left.reset();
self.right.reset();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_delay_config() {
let config = DelayConfig::default();
assert_eq!(config.delay_ms, 500.0);
assert_eq!(config.feedback, 0.4);
}
#[test]
fn test_delay_presets() {
let slapback = DelayConfig::slapback();
assert!(slapback.delay_ms < 200.0);
let ambient = DelayConfig::ambient();
assert!(ambient.delay_ms > 500.0);
}
#[test]
fn test_mono_delay() {
let config = DelayConfig::new(100.0, 0.5, 0.5);
let mut delay = MonoDelay::new(config, 48000.0);
let out1 = delay.process_sample(1.0);
assert!((out1 - 0.5).abs() < 0.01);
for _ in 0..4799 {
delay.process_sample(0.0);
}
let echo = delay.process_sample(0.0);
assert!(echo.abs() > 0.1); }
#[test]
fn test_stereo_delay() {
let config = DelayConfig::default();
let mut delay = StereoDelay::new(config, 48000.0);
let (out_l, out_r) = delay.process_sample_stereo(1.0, 0.5);
assert!(out_l != out_r); }
#[test]
fn test_delay_reset() {
let config = DelayConfig::new(100.0, 0.9, 0.5);
let mut delay = MonoDelay::new(config, 48000.0);
for _ in 0..1000 {
delay.process_sample(1.0);
}
delay.reset();
let output = delay.process_sample(0.0);
assert!(output.abs() < 0.01);
}
}