struct CombFilter {
buffer: Vec<f32>,
write_pos: usize,
feedback: f32,
damp: f32,
filter_store: f32,
}
impl CombFilter {
fn new(delay_samples: usize) -> Self {
let size = delay_samples.max(1);
Self {
buffer: vec![0.0_f32; size],
write_pos: 0,
feedback: 0.84,
damp: 0.2,
filter_store: 0.0,
}
}
fn process(&mut self, input: f32) -> f32 {
let buf_len = self.buffer.len();
let output = self.buffer[self.write_pos];
self.filter_store = output * (1.0 - self.damp) + self.filter_store * self.damp;
self.buffer[self.write_pos] = input + self.filter_store * self.feedback;
self.write_pos += 1;
if self.write_pos >= buf_len {
self.write_pos = 0;
}
output
}
fn set_feedback(&mut self, fb: f32) {
self.feedback = fb.clamp(0.0, 0.98);
}
fn set_damp(&mut self, d: f32) {
self.damp = d.clamp(0.0, 1.0);
}
fn clear(&mut self) {
self.buffer.fill(0.0);
self.filter_store = 0.0;
self.write_pos = 0;
}
}
struct AllpassFilter {
buffer: Vec<f32>,
write_pos: usize,
feedback: f32,
}
impl AllpassFilter {
fn new(delay_samples: usize) -> Self {
let size = delay_samples.max(1);
Self {
buffer: vec![0.0_f32; size],
write_pos: 0,
feedback: 0.5,
}
}
fn process(&mut self, input: f32) -> f32 {
let buf_len = self.buffer.len();
let buf_out = self.buffer[self.write_pos];
self.buffer[self.write_pos] = input + buf_out * self.feedback;
self.write_pos += 1;
if self.write_pos >= buf_len {
self.write_pos = 0;
}
buf_out - input
}
fn clear(&mut self) {
self.buffer.fill(0.0);
self.write_pos = 0;
}
}
const COMB_DELAYS_BASE: [usize; 8] = [1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617];
const STEREO_SPREAD: usize = 23;
const ALLPASS_DELAYS_BASE: [usize; 4] = [556, 441, 341, 225];
pub struct SchroederReverb {
pub room_size: f32,
pub damping: f32,
pub wet_mix: f32,
pub dry_mix: f32,
pub width: f32,
combs_l: Vec<CombFilter>,
allpass_l: Vec<AllpassFilter>,
combs_r: Vec<CombFilter>,
allpass_r: Vec<AllpassFilter>,
}
impl SchroederReverb {
#[must_use]
pub fn new(sample_rate: u32) -> Self {
let sr = sample_rate.max(1) as f32;
let scale = sr / 44100.0;
let make_combs = |delays: &[usize]| -> Vec<CombFilter> {
delays
.iter()
.map(|&d| {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let scaled = ((d as f32) * scale) as usize;
CombFilter::new(scaled.max(1))
})
.collect()
};
let make_allpasses = |delays: &[usize]| -> Vec<AllpassFilter> {
delays
.iter()
.map(|&d| {
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
let scaled = ((d as f32) * scale) as usize;
AllpassFilter::new(scaled.max(1))
})
.collect()
};
let right_comb_delays: Vec<usize> = COMB_DELAYS_BASE
.iter()
.map(|&d| d + STEREO_SPREAD)
.collect();
let right_allpass_delays: Vec<usize> = ALLPASS_DELAYS_BASE
.iter()
.map(|&d| d + STEREO_SPREAD)
.collect();
let mut rv = Self {
room_size: 0.5,
damping: 0.5,
wet_mix: 0.33,
dry_mix: 0.67,
width: 1.0,
combs_l: make_combs(&COMB_DELAYS_BASE),
allpass_l: make_allpasses(&ALLPASS_DELAYS_BASE),
combs_r: make_combs(&right_comb_delays),
allpass_r: make_allpasses(&right_allpass_delays),
};
rv.apply_params();
rv
}
pub fn set_room_size(&mut self, size: f32) {
self.room_size = size.clamp(0.0, 1.0);
self.apply_params();
}
pub fn set_damping(&mut self, damp: f32) {
self.damping = damp.clamp(0.0, 1.0);
self.apply_params();
}
fn apply_params(&mut self) {
let feedback = 0.7 + self.room_size * 0.28;
let damp = self.damping * 0.4;
for c in &mut self.combs_l {
c.set_feedback(feedback);
c.set_damp(damp);
}
for c in &mut self.combs_r {
c.set_feedback(feedback);
c.set_damp(damp);
}
}
#[must_use]
pub fn process_stereo(&mut self, left: &[f32], right: &[f32]) -> (Vec<f32>, Vec<f32>) {
let n = left.len().min(right.len());
let mut out_l = Vec::with_capacity(n);
let mut out_r = Vec::with_capacity(n);
let wet1 = self.wet_mix * (self.width / 2.0 + 0.5);
let wet2 = self.wet_mix * ((1.0 - self.width) / 2.0);
for i in 0..n {
let in_l = left[i];
let in_r = right[i];
let comb_l: f32 = self.combs_l.iter_mut().map(|c| c.process(in_l)).sum();
let comb_r: f32 = self.combs_r.iter_mut().map(|c| c.process(in_r)).sum();
let mut diff_l = comb_l;
for ap in &mut self.allpass_l {
diff_l = ap.process(diff_l);
}
let mut diff_r = comb_r;
for ap in &mut self.allpass_r {
diff_r = ap.process(diff_r);
}
let o_l = diff_l * wet1 + diff_r * wet2 + in_l * self.dry_mix;
let o_r = diff_r * wet1 + diff_l * wet2 + in_r * self.dry_mix;
out_l.push(o_l);
out_r.push(o_r);
}
(out_l, out_r)
}
#[must_use]
pub fn process_mono(&mut self, input: &[f32]) -> Vec<f32> {
let (l, r) = self.process_stereo(input, input);
l.iter().zip(r.iter()).map(|(a, b)| (a + b) * 0.5).collect()
}
pub fn reset(&mut self) {
for c in &mut self.combs_l {
c.clear();
}
for c in &mut self.combs_r {
c.clear();
}
for ap in &mut self.allpass_l {
ap.clear();
}
for ap in &mut self.allpass_r {
ap.clear();
}
}
}
const BLOCK_SIZE: usize = 512;
pub struct SimpleConvolutionReverb {
pub ir: Vec<f32>,
pub wet_mix: f32,
pub dry_mix: f32,
}
impl SimpleConvolutionReverb {
#[must_use]
pub fn new(ir: Vec<f32>) -> Self {
Self {
ir,
wet_mix: 0.5,
dry_mix: 0.5,
}
}
#[must_use]
pub fn process(&self, input: &[f32]) -> Vec<f32> {
let n = input.len();
if n == 0 || self.ir.is_empty() {
return input.to_vec();
}
let ir_len = self.ir.len();
let out_len = n + ir_len - 1;
let mut conv_out = vec![0.0_f32; out_len];
let mut block_start = 0;
while block_start < n {
let block_end = (block_start + BLOCK_SIZE).min(n);
for (bi, &x) in input[block_start..block_end].iter().enumerate() {
if x == 0.0 {
continue;
}
let out_offset = block_start + bi;
for (ii, &h) in self.ir.iter().enumerate() {
conv_out[out_offset + ii] += x * h;
}
}
block_start = block_end;
}
let mut output = Vec::with_capacity(n);
for i in 0..n {
let wet = conv_out[i];
let dry = input[i];
output.push(wet * self.wet_mix + dry * self.dry_mix);
}
output
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_schroeder_new_44100() {
let rv = SchroederReverb::new(44100);
assert_eq!(rv.combs_l.len(), 8);
assert_eq!(rv.allpass_l.len(), 4);
}
#[test]
fn test_schroeder_new_48000() {
let rv = SchroederReverb::new(48000);
assert_eq!(rv.combs_l.len(), 8);
assert_eq!(rv.allpass_l.len(), 4);
}
#[test]
fn test_schroeder_process_stereo_length() {
let mut rv = SchroederReverb::new(44100);
let l = vec![0.5_f32; 256];
let r = vec![-0.5_f32; 256];
let (ol, or_) = rv.process_stereo(&l, &r);
assert_eq!(ol.len(), 256);
assert_eq!(or_.len(), 256);
}
#[test]
fn test_schroeder_output_finite() {
let mut rv = SchroederReverb::new(44100);
let mut l = vec![0.0_f32; 1024];
let mut r = vec![0.0_f32; 1024];
l[0] = 1.0;
r[0] = 1.0;
let (ol, or_) = rv.process_stereo(&l, &r);
for (i, (&a, &b)) in ol.iter().zip(or_.iter()).enumerate() {
assert!(a.is_finite(), "out_l[{i}] is not finite: {a}");
assert!(b.is_finite(), "out_r[{i}] is not finite: {b}");
}
}
#[test]
fn test_schroeder_silence_in_silence_out() {
let mut rv = SchroederReverb::new(44100);
rv.wet_mix = 0.0;
rv.dry_mix = 0.0;
let l = vec![1.0_f32; 64];
let r = vec![1.0_f32; 64];
let (ol, or_) = rv.process_stereo(&l, &r);
for &s in ol.iter().chain(or_.iter()) {
assert_eq!(s, 0.0);
}
}
#[test]
fn test_schroeder_set_room_size_clamp() {
let mut rv = SchroederReverb::new(44100);
rv.set_room_size(2.5);
assert_eq!(rv.room_size, 1.0);
rv.set_room_size(-1.0);
assert_eq!(rv.room_size, 0.0);
}
#[test]
fn test_schroeder_set_damping_clamp() {
let mut rv = SchroederReverb::new(44100);
rv.set_damping(1.5);
assert_eq!(rv.damping, 1.0);
rv.set_damping(-0.1);
assert_eq!(rv.damping, 0.0);
}
#[test]
fn test_schroeder_reset_clears_state() {
let mut rv = SchroederReverb::new(44100);
let impulse = {
let mut v = vec![0.0_f32; 256];
v[0] = 1.0;
v
};
rv.process_stereo(&impulse, &impulse);
rv.reset();
let silence = vec![0.0_f32; 64];
let (ol, or_) = rv.process_stereo(&silence, &silence);
for &s in ol.iter().chain(or_.iter()) {
assert!(s.abs() < 1e-9, "Expected silence after reset, got {s}");
}
}
#[test]
fn test_schroeder_stereo_channels_differ() {
let mut rv = SchroederReverb::new(44100);
rv.width = 1.0;
let mut l = vec![0.0_f32; 512];
let r = vec![0.0_f32; 512];
l[0] = 1.0; let (ol, or_) = rv.process_stereo(&l, &r);
let identical = ol.iter().zip(or_.iter()).all(|(a, b)| (a - b).abs() < 1e-9);
assert!(!identical, "Expected stereo channels to differ");
}
#[test]
fn test_schroeder_process_mono() {
let mut rv = SchroederReverb::new(44100);
let input = vec![0.5_f32; 128];
let out = rv.process_mono(&input);
assert_eq!(out.len(), 128);
for &s in &out {
assert!(s.is_finite());
}
}
#[test]
fn test_conv_reverb_new() {
let ir = vec![1.0_f32, 0.5, 0.25];
let rv = SimpleConvolutionReverb::new(ir.clone());
assert_eq!(rv.ir, ir);
assert!((rv.wet_mix - 0.5).abs() < 1e-6);
assert!((rv.dry_mix - 0.5).abs() < 1e-6);
}
#[test]
fn test_conv_reverb_output_length() {
let ir: Vec<f32> = (0..50).map(|i| 0.99_f32.powi(i)).collect();
let rv = SimpleConvolutionReverb::new(ir);
let input = vec![1.0_f32; 512];
let out = rv.process(&input);
assert_eq!(out.len(), 512);
}
#[test]
fn test_conv_reverb_empty_input() {
let ir = vec![1.0_f32, 0.5];
let rv = SimpleConvolutionReverb::new(ir);
let out = rv.process(&[]);
assert!(out.is_empty());
}
#[test]
fn test_conv_reverb_output_finite() {
let ir: Vec<f32> = (0..128).map(|i| 0.97_f32.powi(i)).collect();
let rv = SimpleConvolutionReverb::new(ir);
let input: Vec<f32> = (0..256).map(|i| (i as f32 * 0.05).sin()).collect();
let out = rv.process(&input);
for (i, &s) in out.iter().enumerate() {
assert!(s.is_finite(), "out[{i}] not finite: {s}");
}
}
}