#![allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DelayMode {
Mono,
Stereo,
Ping,
Pong,
}
impl DelayMode {
#[must_use]
pub fn channels(self) -> usize {
match self {
DelayMode::Mono => 1,
DelayMode::Stereo | DelayMode::Ping | DelayMode::Pong => 2,
}
}
#[must_use]
pub fn is_ping_pong(self) -> bool {
matches!(self, DelayMode::Ping | DelayMode::Pong)
}
}
#[derive(Debug, Clone)]
pub struct DelayTap {
pub delay_samples: usize,
pub gain: f32,
pub feedback: f32,
pub enabled: bool,
}
impl DelayTap {
#[must_use]
pub fn new(delay_samples: usize, gain: f32) -> Self {
Self {
delay_samples,
gain,
feedback: 0.0,
enabled: true,
}
}
#[must_use]
pub fn with_feedback(mut self, feedback: f32) -> Self {
self.feedback = feedback.clamp(0.0, 0.99);
self
}
#[must_use]
pub fn is_feedback(&self) -> bool {
self.feedback > 0.0
}
}
pub struct DelayLine {
buffer: Vec<f32>,
write_pos: usize,
taps: Vec<DelayTap>,
pub mode: DelayMode,
pub dry_mix: f32,
pub wet_mix: f32,
}
impl DelayLine {
#[must_use]
pub fn new(capacity_samples: usize, mode: DelayMode) -> Self {
Self {
buffer: vec![0.0; capacity_samples.max(1)],
write_pos: 0,
taps: Vec::new(),
mode,
dry_mix: 1.0,
wet_mix: 0.5,
}
}
pub fn write_sample(&mut self, sample: f32) {
self.buffer[self.write_pos] = sample;
self.write_pos = (self.write_pos + 1) % self.buffer.len();
}
#[must_use]
pub fn read_tap(&self, tap_index: usize) -> f32 {
let tap = match self.taps.get(tap_index) {
Some(t) if t.enabled => t,
_ => return 0.0,
};
let buf_len = self.buffer.len();
let read_pos = (self.write_pos + buf_len - tap.delay_samples.min(buf_len - 1)) % buf_len;
self.buffer[read_pos] * tap.gain
}
#[must_use]
pub fn mix_taps(&self) -> f32 {
(0..self.taps.len()).map(|i| self.read_tap(i)).sum()
}
pub fn process(&mut self, input: f32) -> f32 {
let wet = self.mix_taps();
let feedback_sum: f32 = self
.taps
.iter()
.filter(|t| t.is_feedback() && t.enabled)
.map(|t| {
let buf_len = self.buffer.len();
let pos = (self.write_pos + buf_len - t.delay_samples.min(buf_len - 1)) % buf_len;
self.buffer[pos] * t.feedback
})
.sum();
self.write_sample(input + feedback_sum);
input * self.dry_mix + wet * self.wet_mix
}
pub fn add_tap(&mut self, tap: DelayTap) {
self.taps.push(tap);
}
#[must_use]
pub fn tap_count(&self) -> usize {
self.taps.len()
}
#[must_use]
pub fn capacity(&self) -> usize {
self.buffer.len()
}
pub fn reset(&mut self) {
self.buffer.fill(0.0);
self.write_pos = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_delay_mode_channels() {
assert_eq!(DelayMode::Mono.channels(), 1);
assert_eq!(DelayMode::Stereo.channels(), 2);
assert_eq!(DelayMode::Ping.channels(), 2);
assert_eq!(DelayMode::Pong.channels(), 2);
}
#[test]
fn test_delay_mode_is_ping_pong() {
assert!(DelayMode::Ping.is_ping_pong());
assert!(DelayMode::Pong.is_ping_pong());
assert!(!DelayMode::Mono.is_ping_pong());
assert!(!DelayMode::Stereo.is_ping_pong());
}
#[test]
fn test_delay_tap_is_feedback_false() {
let tap = DelayTap::new(100, 0.8);
assert!(!tap.is_feedback());
}
#[test]
fn test_delay_tap_is_feedback_true() {
let tap = DelayTap::new(100, 0.8).with_feedback(0.5);
assert!(tap.is_feedback());
assert!((tap.feedback - 0.5).abs() < 1e-6);
}
#[test]
fn test_delay_tap_feedback_clamped() {
let tap = DelayTap::new(10, 1.0).with_feedback(2.0);
assert!(tap.feedback <= 0.99);
}
#[test]
fn test_delay_line_capacity() {
let dl = DelayLine::new(512, DelayMode::Mono);
assert_eq!(dl.capacity(), 512);
}
#[test]
fn test_delay_line_empty_read() {
let dl = DelayLine::new(128, DelayMode::Mono);
assert_eq!(dl.read_tap(0), 0.0);
}
#[test]
fn test_delay_line_write_and_read() {
let mut dl = DelayLine::new(64, DelayMode::Mono);
dl.add_tap(DelayTap::new(2, 1.0));
dl.write_sample(0.5);
dl.write_sample(0.0);
let out = dl.read_tap(0);
assert!((out - 0.5).abs() < 1e-6, "got {out}");
}
#[test]
fn test_delay_line_mix_taps_empty() {
let dl = DelayLine::new(64, DelayMode::Stereo);
assert_eq!(dl.mix_taps(), 0.0);
}
#[test]
fn test_delay_line_tap_count() {
let mut dl = DelayLine::new(128, DelayMode::Mono);
assert_eq!(dl.tap_count(), 0);
dl.add_tap(DelayTap::new(10, 0.5));
dl.add_tap(DelayTap::new(20, 0.3));
assert_eq!(dl.tap_count(), 2);
}
#[test]
fn test_delay_line_reset() {
let mut dl = DelayLine::new(32, DelayMode::Mono);
dl.write_sample(1.0);
dl.reset();
dl.add_tap(DelayTap::new(1, 1.0));
assert_eq!(dl.read_tap(0), 0.0);
}
#[test]
fn test_delay_line_disabled_tap() {
let mut dl = DelayLine::new(64, DelayMode::Mono);
let mut tap = DelayTap::new(1, 1.0);
tap.enabled = false;
dl.add_tap(tap);
dl.write_sample(1.0);
assert_eq!(dl.read_tap(0), 0.0);
}
#[test]
fn test_delay_line_process_dry() {
let mut dl = DelayLine::new(64, DelayMode::Mono);
dl.wet_mix = 0.0;
dl.dry_mix = 1.0;
let out = dl.process(0.7);
assert!((out - 0.7).abs() < 1e-5, "got {out}");
}
}