1use aether_core::{node::DspNode, param::ParamBlock, BUFFER_SIZE, MAX_INPUTS};
13
14const COMB_TUNING: [usize; 8] = [1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617];
16const ALLPASS_TUNING: [usize; 4] = [556, 441, 341, 225];
17const STEREO_SPREAD: usize = 23;
18const SCALE_ROOM: f32 = 0.28;
19const OFFSET_ROOM: f32 = 0.7;
20const SCALE_DAMP: f32 = 0.4;
21const FIXED_GAIN: f32 = 0.015;
22
23struct CombFilter {
24 buf: Vec<f32>,
25 pos: usize,
26 feedback: f32,
27 damp1: f32,
28 damp2: f32,
29 filterstore: f32,
30}
31
32impl CombFilter {
33 fn new(size: usize) -> Self {
34 Self {
35 buf: vec![0.0; size],
36 pos: 0,
37 feedback: 0.5,
38 damp1: 0.5,
39 damp2: 0.5,
40 filterstore: 0.0,
41 }
42 }
43
44 #[inline(always)]
45 fn process(&mut self, input: f32) -> f32 {
46 let output = self.buf[self.pos];
47 self.filterstore = output * self.damp2 + self.filterstore * self.damp1;
48 self.buf[self.pos] = input + self.filterstore * self.feedback;
49 self.pos = (self.pos + 1) % self.buf.len();
50 output
51 }
52
53 fn set_feedback(&mut self, v: f32) { self.feedback = v; }
54 fn set_damp(&mut self, v: f32) { self.damp1 = v; self.damp2 = 1.0 - v; }
55}
56
57struct AllpassFilter {
58 buf: Vec<f32>,
59 pos: usize,
60}
61
62impl AllpassFilter {
63 fn new(size: usize) -> Self {
64 Self { buf: vec![0.0; size], pos: 0 }
65 }
66
67 #[inline(always)]
68 fn process(&mut self, input: f32) -> f32 {
69 let bufout = self.buf[self.pos];
70 let output = -input + bufout;
71 self.buf[self.pos] = input + bufout * 0.5;
72 self.pos = (self.pos + 1) % self.buf.len();
73 output
74 }
75}
76
77pub struct Reverb {
78 comb_l: [CombFilter; 8],
79 comb_r: [CombFilter; 8],
80 allpass_l: [AllpassFilter; 4],
81 allpass_r: [AllpassFilter; 4],
82}
83
84impl Reverb {
85 pub fn new(sample_rate: f32) -> Self {
86 let scale = sample_rate / 44100.0;
87 let scaled = |base: usize| ((base as f32 * scale) as usize).max(1);
88
89 macro_rules! make_combs {
90 ($spread:expr) => {
91 [
92 CombFilter::new(scaled(COMB_TUNING[0] + $spread)),
93 CombFilter::new(scaled(COMB_TUNING[1] + $spread)),
94 CombFilter::new(scaled(COMB_TUNING[2] + $spread)),
95 CombFilter::new(scaled(COMB_TUNING[3] + $spread)),
96 CombFilter::new(scaled(COMB_TUNING[4] + $spread)),
97 CombFilter::new(scaled(COMB_TUNING[5] + $spread)),
98 CombFilter::new(scaled(COMB_TUNING[6] + $spread)),
99 CombFilter::new(scaled(COMB_TUNING[7] + $spread)),
100 ]
101 };
102 }
103
104 macro_rules! make_allpasses {
105 ($spread:expr) => {
106 [
107 AllpassFilter::new(scaled(ALLPASS_TUNING[0] + $spread)),
108 AllpassFilter::new(scaled(ALLPASS_TUNING[1] + $spread)),
109 AllpassFilter::new(scaled(ALLPASS_TUNING[2] + $spread)),
110 AllpassFilter::new(scaled(ALLPASS_TUNING[3] + $spread)),
111 ]
112 };
113 }
114
115 let mut r = Self {
116 comb_l: make_combs!(0),
117 comb_r: make_combs!(STEREO_SPREAD),
118 allpass_l: make_allpasses!(0),
119 allpass_r: make_allpasses!(STEREO_SPREAD),
120 };
121 r.set_params(0.5, 0.5);
122 r
123 }
124
125 fn set_params(&mut self, room_size: f32, damping: f32) {
126 let feedback = room_size * SCALE_ROOM + OFFSET_ROOM;
127 let damp = damping * SCALE_DAMP;
128 for c in self.comb_l.iter_mut().chain(self.comb_r.iter_mut()) {
129 c.set_feedback(feedback);
130 c.set_damp(damp);
131 }
132 }
133
134 #[inline(always)]
135 fn process_sample(&mut self, input: f32) -> (f32, f32) {
136 let input_gain = input * FIXED_GAIN;
137 let mut out_l = 0.0f32;
138 let mut out_r = 0.0f32;
139
140 for c in &mut self.comb_l { out_l += c.process(input_gain); }
141 for c in &mut self.comb_r { out_r += c.process(input_gain); }
142 for a in &mut self.allpass_l { out_l = a.process(out_l); }
143 for a in &mut self.allpass_r { out_r = a.process(out_r); }
144
145 (out_l, out_r)
146 }
147}
148
149impl DspNode for Reverb {
150 fn process(
151 &mut self,
152 inputs: &[Option<&[f32; BUFFER_SIZE]>; MAX_INPUTS],
153 output: &mut [f32; BUFFER_SIZE],
154 params: &mut ParamBlock,
155 _sample_rate: f32,
156 ) {
157 let silence = [0.0f32; BUFFER_SIZE];
158 let input = inputs[0].unwrap_or(&silence);
159
160 for (i, out) in output.iter_mut().enumerate() {
161 let room = params.get(0).current.clamp(0.0, 1.0);
162 let damp = params.get(1).current.clamp(0.0, 1.0);
163 let wet = params.get(2).current.clamp(0.0, 1.0);
164
165 self.set_params(room, damp);
166 let (wet_l, wet_r) = self.process_sample(input[i]);
167 *out = input[i] * (1.0 - wet) + (wet_l + wet_r) * 0.5 * wet;
169 params.tick_all();
170 }
171 }
172
173 fn type_name(&self) -> &'static str {
174 "Reverb"
175 }
176}