use crate::algorithm::{ActionContext, Algorithm, AlgorithmCategory, AlgorithmMetadata, ParameterizedAlgorithm, ProcessResult};
use crate::buffer::{DelayLine, RingBuffer};
use rill_core::Transcendental;
#[derive(Debug, Clone)]
pub struct DelayParams {
pub delay_time: f32,
pub feedback: f32,
pub mix: f32,
pub invert_feedback: bool,
}
impl Default for DelayParams {
fn default() -> Self {
Self {
delay_time: 0.5,
feedback: 0.3,
mix: 0.5,
invert_feedback: false,
}
}
}
impl DelayParams {
pub fn simple(delay_time: f32, mix: f32) -> Self {
Self {
delay_time,
feedback: 0.0,
mix,
invert_feedback: false,
}
}
pub fn feedback(delay_time: f32, feedback: f32, mix: f32) -> Self {
Self {
delay_time,
feedback: feedback.clamp(0.0, 1.0),
mix,
invert_feedback: false,
}
}
}
pub struct Delay<T: Transcendental, const MAX_DELAY: usize> {
params: DelayParams,
delay_line: DelayLine<T, MAX_DELAY>,
delay_samples: usize,
sample_rate: f32,
}
impl<T: Transcendental, const MAX_DELAY: usize> Delay<T, MAX_DELAY> {
pub fn new(params: DelayParams) -> Self {
Self {
params,
delay_line: DelayLine::new(),
delay_samples: 0,
sample_rate: 44100.0,
}
}
fn update_delay_samples(&mut self) {
self.delay_samples =
crate::math::seconds_to_samples(self.params.delay_time, self.sample_rate);
debug_assert!(
self.delay_samples < MAX_DELAY,
"Delay time too long for this delay line"
);
}
pub fn set_delay_time(&mut self, time: f32) {
self.params.delay_time = time;
self.update_delay_samples();
}
pub fn set_feedback(&mut self, feedback: f32) {
self.params.feedback = feedback.clamp(0.0, 1.0);
}
pub fn set_mix(&mut self, mix: f32) {
self.params.mix = mix.clamp(0.0, 1.0);
}
pub fn delay_samples(&self) -> usize {
self.delay_samples
}
}
impl<T: Transcendental, const MAX_DELAY: usize> Algorithm<T> for Delay<T, MAX_DELAY> {
fn init(&mut self, sample_rate: f32) {
self.sample_rate = sample_rate;
self.update_delay_samples();
self.delay_line.reset();
}
fn reset(&mut self) {
self.delay_line.reset();
}
fn process(
&mut self,
input: Option<&[T]>,
output: &mut [T],
_ctx: &ActionContext,
) -> ProcessResult<()> {
let input = input.unwrap_or(&[]);
let len = input.len().min(output.len());
for i in 0..len {
let len = input.len().min(output.len());
for i in 0..len {
let delayed = self.delay_line.read();
let wet = delayed;
let dry = input[i];
let mix = T::from_f32(self.params.mix);
let one_minus_mix = T::from_f32(1.0 - self.params.mix);
output[i] = dry.mul(one_minus_mix).add(wet.mul(mix));
let feedback = T::from_f32(self.params.feedback);
let write_signal = if self.params.invert_feedback {
input[i].sub(delayed.mul(feedback))
} else {
input[i].add(delayed.mul(feedback))
};
let _ = self.delay_line.write(write_signal);
}
Ok(())
}
fn metadata(&self) -> AlgorithmMetadata {
AlgorithmMetadata {
name: "Delay",
category: AlgorithmCategory::Effect,
description: "Simple delay with feedback",
author: "Rill",
version: env!("CARGO_PKG_VERSION"),
}
}
}
impl<T: Transcendental, const MAX_DELAY: usize> ParameterizedAlgorithm<T> for Delay<T, MAX_DELAY> {
type Params = DelayParams;
fn params(&self) -> &Self::Params {
&self.params
}
fn set_params(&mut self, params: Self::Params) {
self.params = params;
self.update_delay_samples();
}
}
#[derive(Debug, Clone)]
pub struct TapParams {
pub delay_time: f32,
pub gain: f32,
pub pan: f32,
}
pub struct MultiTapDelay<T: Transcendental, const MAX_DELAY: usize, const MAX_TAPS: usize> {
delay_line: DelayLine<T, MAX_DELAY>,
taps: [TapParams; MAX_TAPS],
tap_samples: [usize; MAX_TAPS],
active_taps: usize,
sample_rate: f32,
}
impl<T: Transcendental, const MAX_DELAY: usize, const MAX_TAPS: usize>
MultiTapDelay<T, MAX_DELAY, MAX_TAPS>
{
pub fn new() -> Self {
Self {
delay_line: DelayLine::new(),
taps: [TapParams {
delay_time: 0.0,
gain: 0.0,
pan: 0.0,
}; MAX_TAPS],
tap_samples: [0; MAX_TAPS],
active_taps: 0,
sample_rate: 44100.0,
}
}
pub fn add_tap(&mut self, params: TapParams) -> Result<(), &'static str> {
if self.active_taps >= MAX_TAPS {
return Err("Maximum number of taps reached");
}
self.taps[self.active_taps] = params;
self.update_tap_samples(self.active_taps);
self.active_taps += 1;
Ok(())
}
pub fn set_tap(&mut self, index: usize, params: TapParams) -> Result<(), &'static str> {
if index >= self.active_taps {
return Err("Tap index out of range");
}
self.taps[index] = params;
self.update_tap_samples(index);
Ok(())
}
fn update_tap_samples(&mut self, index: usize) {
self.tap_samples[index] =
crate::math::seconds_to_samples(self.taps[index].delay_time, self.sample_rate);
debug_assert!(self.tap_samples[index] < MAX_DELAY, "Tap delay too long");
}
pub fn active_taps(&self) -> usize {
self.active_taps
}
}
impl<T: Transcendental, const MAX_DELAY: usize, const MAX_TAPS: usize> Algorithm<T>
for MultiTapDelay<T, MAX_DELAY, MAX_TAPS>
{
fn init(&mut self, sample_rate: f32) {
self.sample_rate = sample_rate;
for i in 0..self.active_taps {
self.update_tap_samples(i);
}
self.delay_line.reset();
}
fn reset(&mut self) {
self.delay_line.reset();
}
fn process(
&mut self,
input: Option<&[T]>,
output: &mut [T],
_ctx: &ActionContext,
) -> ProcessResult<()> {
let input = input.unwrap_or(&[]);
let len = input.len().min(output.len());
for i in 0..len {
let _ = self.delay_line.write(input[i]);
let mut out = T::ZERO;
for tap_idx in 0..self.active_taps {
let tap = &self.taps[tap_idx];
let delayed = input[i];
let sample = delayed.mul(T::from_f32(tap.gain));
out = out.add(sample);
}
output[i] = out;
}
Ok(())
}
fn metadata(&self) -> AlgorithmMetadata {
AlgorithmMetadata {
name: "Multi-Tap Delay",
category: AlgorithmCategory::Effect,
description: "Delay with multiple independent taps",
author: "Rill",
version: env!("CARGO_PKG_VERSION"),
}
}
}
pub struct DiffusionDelay<T: Transcendental, const STAGES: usize, const MAX_DELAY: usize> {
delays: [DelayLine<T, MAX_DELAY>; STAGES],
delay_times: [f32; STAGES],
feedback: [T; STAGES],
sample_rate: f32,
}
impl<T: Transcendental, const STAGES: usize, const MAX_DELAY: usize>
DiffusionDelay<T, STAGES, MAX_DELAY>
{
pub fn new(delay_times: [f32; STAGES], feedback: [f32; STAGES]) -> Self {
let mut feedback_typed = [T::ZERO; STAGES];
for i in 0..STAGES {
feedback_typed[i] = T::from_f32(feedback[i]);
}
Self {
delays: [DelayLine::new(); STAGES],
delay_times,
feedback: feedback_typed,
sample_rate: 44100.0,
}
}
pub fn process_diffusion(&mut self, input: T) -> T {
let mut x = input;
for i in 0..STAGES {
let delayed = self.delays[i].read();
let g = self.feedback[i];
let y = x
.mul(g.neg())
.add(delayed)
.add(self.delays[i].read().mul(g));
let _ = self.delays[i].write(x.add(delayed.mul(g.neg())));
x = y;
}
x
}
}
impl<T: Transcendental, const STAGES: usize, const MAX_DELAY: usize> Algorithm<T>
for DiffusionDelay<T, STAGES, MAX_DELAY>
{
fn init(&mut self, sample_rate: f32) {
self.sample_rate = sample_rate;
for i in 0..STAGES {
let delay_samples = crate::math::seconds_to_samples(self.delay_times[i], sample_rate);
}
self.reset();
}
fn reset(&mut self) {
for delay in &mut self.delays {
delay.reset();
}
}
fn process(
&mut self,
input: Option<&[T]>,
output: &mut [T],
_ctx: &ActionContext,
) -> ProcessResult<()> {
let input = input.unwrap_or(&[]);
let len = input.len().min(output.len());
for i in 0..len {
output[i] = self.process_diffusion(input[i]);
}
Ok(())
}
fn metadata(&self) -> AlgorithmMetadata {
AlgorithmMetadata {
name: "Diffusion Delay",
category: AlgorithmCategory::Effect,
description: "Diffusion network for reverb",
author: "Rill",
version: env!("CARGO_PKG_VERSION"),
}
}
}
#[derive(Debug, Clone)]
pub struct ModulatedDelayParams {
pub base_delay: f32,
pub depth: f32,
pub rate: f32,
pub feedback: f32,
pub mix: f32,
}
pub struct ModulatedDelay<T: Transcendental, const MAX_DELAY: usize> {
params: ModulatedDelayParams,
delay_line: DelayLine<T, MAX_DELAY>,
lfo_phase: T,
sample_rate: f32,
}
impl<T: Transcendental, const MAX_DELAY: usize> ModulatedDelay<T, MAX_DELAY> {
pub fn new(params: ModulatedDelayParams) -> Self {
Self {
params,
delay_line: DelayLine::new(),
lfo_phase: T::ZERO,
sample_rate: 44100.0,
}
}
fn current_delay(&self) -> f32 {
let lfo = self.lfo_phase.to_f32().sin() * 0.5 + 0.5; self.params.base_delay + lfo * self.params.depth
}
}
impl<T: Transcendental, const MAX_DELAY: usize> Algorithm<T> for ModulatedDelay<T, MAX_DELAY> {
fn init(&mut self, sample_rate: f32) {
self.sample_rate = sample_rate;
self.lfo_phase = T::ZERO;
self.delay_line.reset();
}
fn reset(&mut self) {
self.lfo_phase = T::ZERO;
self.delay_line.reset();
}
fn process(
&mut self,
input: Option<&[T]>,
output: &mut [T],
_ctx: &ActionContext,
) -> ProcessResult<()> {
let input = input.unwrap_or(&[]);
let len = input.len().min(output.len());
for i in 0..len {
let phase_inc = T::from_f32(self.params.rate / self.sample_rate);
self.lfo_phase = self.lfo_phase.add(phase_inc);
if self.lfo_phase.to_f32() >= 1.0 {
self.lfo_phase = self.lfo_phase.sub(T::from_f32(1.0));
}
let current_delay = self.current_delay();
let delay_samples = crate::math::seconds_to_samples(current_delay, self.sample_rate);
let delayed = self.delay_line.read();
let feedback = T::from_f32(self.params.feedback);
let write_signal = input[i].add(delayed.mul(feedback));
let _ = self.delay_line.write(write_signal);
let dry = input[i];
let wet = delayed;
let mix = T::from_f32(self.params.mix);
let one_minus_mix = T::from_f32(1.0 - self.params.mix);
output[i] = dry.mul(one_minus_mix).add(wet.mul(mix));
}
Ok(())
}
fn metadata(&self) -> AlgorithmMetadata {
AlgorithmMetadata {
name: "Modulated Delay",
category: AlgorithmCategory::Effect,
description: "Delay with LFO modulation (Chorus/Flanger)",
author: "Rill",
version: env!("CARGO_PKG_VERSION"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rill_core::time::ClockTick;
use rill_core::traits::ActionContext;
#[test]
fn test_delay_basic() {
let mut delay = Delay::<f32, 1024>::new(DelayParams::simple(0.1, 0.5));
delay.init(44100.0);
let mut output = [0.0];
let tick = ClockTick::default();
let ctx = ActionContext::new(&tick);
delay.process(Some(&[1.0]), &mut output, &ctx).unwrap();
assert_eq!(output[0], 0.5);
delay.process(Some(&[1.0]), &mut output, &ctx).unwrap();
assert_eq!(output[0], 0.5);
}
#[test]
fn test_delay_feedback() {
let mut delay = Delay::<f32, 1024>::new(DelayParams::feedback(0.1, 0.5, 0.7));
delay.init(44100.0);
let mut output = [0.0];
let tick = ClockTick::default();
let ctx = ActionContext::new(&tick);
delay.process(Some(&[1.0]), &mut output, &ctx).unwrap();
let out1 = output[0];
delay.process(Some(&[0.0]), &mut output, &ctx).unwrap();
let out2 = output[0];
assert!(out2 > 0.0); }
#[test]
fn test_multi_tap() {
let mut multitap = MultiTapDelay::<f32, 1024, 4>::new();
multitap.init(44100.0);
multitap
.add_tap(TapParams {
delay_time: 0.1,
gain: 0.5,
pan: 0.0,
})
.unwrap();
assert_eq!(multitap.active_taps(), 1);
}
}