use sim_lib_audio_graph_core::{PrepareConfig, ProcessBlock, Processor};
use crate::common::{input_sample, output_channels, prepare_channels};
#[derive(Clone, Debug, PartialEq)]
pub struct DelayLine {
buffer: Vec<f32>,
write: usize,
}
impl DelayLine {
pub fn new(max_delay_samples: usize) -> Self {
Self {
buffer: vec![0.0; max_delay_samples.max(2) + 2],
write: 0,
}
}
pub fn reset(&mut self) {
self.buffer.fill(0.0);
self.write = 0;
}
pub fn read(&self, delay_samples: f32) -> f32 {
let len = self.buffer.len();
let delay = delay_samples.clamp(0.0, (len - 2) as f32);
let read = (self.write as f32 - delay).rem_euclid(len as f32);
let i0 = read.floor() as usize % len;
let i1 = (i0 + 1) % len;
let frac = read - i0 as f32;
self.buffer[i0] * (1.0 - frac) + self.buffer[i1] * frac
}
pub fn push(&mut self, sample: f32) {
self.buffer[self.write] = sample;
self.write = (self.write + 1) % self.buffer.len();
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct DelayProcessor {
delay_seconds: f32,
max_delay_seconds: f32,
feedback: f32,
wet: f32,
dry: f32,
sample_rate_hz: f32,
lines: Vec<DelayLine>,
}
impl DelayProcessor {
pub fn new(delay_seconds: f32, max_delay_seconds: f32) -> Self {
Self {
delay_seconds: delay_seconds.max(0.0),
max_delay_seconds: max_delay_seconds.max(delay_seconds).max(0.001),
feedback: 0.0,
wet: 1.0,
dry: 0.0,
sample_rate_hz: 48_000.0,
lines: Vec::new(),
}
}
pub fn milliseconds(delay_ms: f32, max_delay_ms: f32) -> Self {
Self::new(delay_ms / 1000.0, max_delay_ms / 1000.0)
}
pub fn with_mix(mut self, dry: f32, wet: f32) -> Self {
self.dry = dry;
self.wet = wet;
self
}
pub fn with_feedback(mut self, feedback: f32) -> Self {
self.feedback = feedback.clamp(-0.99, 0.99);
self
}
fn delay_samples(&self) -> f32 {
self.delay_seconds * self.sample_rate_hz
}
fn max_delay_samples(&self) -> usize {
(self.max_delay_seconds * self.sample_rate_hz).ceil() as usize
}
}
impl Processor for DelayProcessor {
fn prepare(&mut self, cfg: PrepareConfig) {
self.sample_rate_hz = cfg.sample_rate_hz as f32;
let line = DelayLine::new(self.max_delay_samples());
prepare_channels(&mut self.lines, cfg.out_channels as usize, line);
}
fn reset(&mut self) {
for line in &mut self.lines {
line.reset();
}
}
fn process(&mut self, block: &mut ProcessBlock<'_>) {
let prepared = self.lines.len();
debug_assert!(
output_channels(block) <= prepared,
"DelayProcessor::process received more channels than prepare configured"
);
let channels = output_channels(block).min(prepared);
let delay = self.delay_samples();
let frames = block.frames as usize;
for channel in 0..channels {
let line = &mut self.lines[channel];
for frame in 0..frames {
let input = input_sample(block, channel, frame);
let delayed = line.read(delay);
line.push(input + delayed * self.feedback);
block.out_audio[channel][frame] = input * self.dry + delayed * self.wet;
}
}
}
fn tail_frames(&self) -> u64 {
self.delay_samples().ceil() as u64
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct FractionalDelay {
inner: DelayProcessor,
}
impl FractionalDelay {
pub fn milliseconds(delay_ms: f32, max_delay_ms: f32) -> Self {
Self {
inner: DelayProcessor::milliseconds(delay_ms, max_delay_ms),
}
}
}
impl Processor for FractionalDelay {
fn prepare(&mut self, cfg: PrepareConfig) {
self.inner.prepare(cfg);
}
fn reset(&mut self) {
self.inner.reset();
}
fn process(&mut self, block: &mut ProcessBlock<'_>) {
self.inner.process(block);
}
fn tail_frames(&self) -> u64 {
self.inner.tail_frames()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct CombFilter {
delay: DelayProcessor,
}
impl CombFilter {
pub fn milliseconds(delay_ms: f32, feedback: f32) -> Self {
Self {
delay: DelayProcessor::milliseconds(delay_ms, delay_ms.max(1.0))
.with_feedback(feedback)
.with_mix(0.0, 1.0),
}
}
}
impl Processor for CombFilter {
fn prepare(&mut self, cfg: PrepareConfig) {
self.delay.prepare(cfg);
}
fn reset(&mut self) {
self.delay.reset();
}
fn process(&mut self, block: &mut ProcessBlock<'_>) {
self.delay.process(block);
}
fn tail_frames(&self) -> u64 {
self.delay.tail_frames()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct AllPassFilter {
delay_seconds: f32,
feedback: f32,
sample_rate_hz: f32,
lines: Vec<DelayLine>,
}
impl AllPassFilter {
pub fn milliseconds(delay_ms: f32, feedback: f32) -> Self {
Self {
delay_seconds: (delay_ms / 1000.0).max(0.0),
feedback: feedback.clamp(-0.99, 0.99),
sample_rate_hz: 48_000.0,
lines: Vec::new(),
}
}
fn delay_samples(&self) -> f32 {
self.delay_seconds * self.sample_rate_hz
}
}
impl Processor for AllPassFilter {
fn prepare(&mut self, cfg: PrepareConfig) {
self.sample_rate_hz = cfg.sample_rate_hz as f32;
let samples = self.delay_samples().ceil() as usize;
prepare_channels(
&mut self.lines,
cfg.out_channels as usize,
DelayLine::new(samples),
);
}
fn reset(&mut self) {
for line in &mut self.lines {
line.reset();
}
}
fn process(&mut self, block: &mut ProcessBlock<'_>) {
let prepared = self.lines.len();
debug_assert!(
output_channels(block) <= prepared,
"AllPassFilter::process received more channels than prepare configured"
);
let channels = output_channels(block).min(prepared);
let delay = self.delay_samples();
let frames = block.frames as usize;
for channel in 0..channels {
let line = &mut self.lines[channel];
for frame in 0..frames {
let input = input_sample(block, channel, frame);
let delayed = line.read(delay);
let output = delayed - self.feedback * input;
line.push(input + self.feedback * output);
block.out_audio[channel][frame] = output;
}
}
}
fn tail_frames(&self) -> u64 {
self.delay_samples().ceil() as u64
}
}