1pub struct Fdn {
19 num_lines: usize,
20 delay_lines: Vec<Vec<f32>>,
21 delay_lengths: Vec<usize>,
22 write_positions: Vec<usize>,
23 feedback_matrix: Vec<f32>,
25 feedback_gain: f32,
27 absorption_state: Vec<f32>,
29 absorption_coeff: Vec<f32>,
31 input_gains: Vec<f32>,
33 output_gains: Vec<[f32; 2]>,
35 scratch: Vec<f32>,
37}
38
39impl Fdn {
40 pub fn new(num_lines: usize, sample_rate: u32) -> Self {
44 let num_lines = num_lines.next_power_of_two().max(2);
45
46 let base_delays = prime_delays(num_lines, sample_rate);
48 let delay_lines: Vec<Vec<f32>> = base_delays.iter().map(|&len| vec![0.0; len]).collect();
49
50 let feedback_matrix = hadamard_flat(num_lines);
51 let scale = 1.0 / (num_lines as f32).sqrt();
52
53 let input_gains = vec![scale; num_lines];
55 let output_gains: Vec<[f32; 2]> = (0..num_lines)
56 .map(|i| {
57 let angle = std::f32::consts::PI * i as f32 / num_lines as f32;
58 [angle.cos(), angle.sin()]
59 })
60 .collect();
61
62 Self {
63 num_lines,
64 delay_lines,
65 delay_lengths: base_delays,
66 write_positions: vec![0; num_lines],
67 feedback_matrix,
68 feedback_gain: 0.7,
69 absorption_state: vec![0.0; num_lines],
70 absorption_coeff: vec![0.3; num_lines],
71 input_gains,
72 output_gains,
73 scratch: vec![0.0; num_lines],
74 }
75 }
76
77 pub fn set_room_params(&mut self, rt60: f32, damping: f32, size: f32, sample_rate: u32) {
83 let base_delays = prime_delays(self.num_lines, sample_rate);
85 for (i, &base) in base_delays.iter().enumerate() {
86 let new_len = ((base as f32 * size) as usize).max(1);
87 if new_len != self.delay_lengths[i] {
88 self.delay_lengths[i] = new_len;
89 self.delay_lines[i].resize(new_len, 0.0);
90 self.write_positions[i] %= new_len;
91 }
92 }
93
94 let avg_delay = self.delay_lengths.iter().sum::<usize>() as f32 / self.num_lines as f32;
97 let rt60_samples = rt60 * sample_rate as f32;
98 self.feedback_gain = if rt60_samples > 0.0 {
99 10.0_f32
100 .powf(-3.0 * avg_delay / rt60_samples)
101 .clamp(0.0, 0.999)
102 } else {
103 0.0
104 };
105
106 let damp = damping.clamp(0.0, 1.0);
108 for coeff in &mut self.absorption_coeff {
109 *coeff = damp * 0.6 + 0.1; }
111 }
112
113 #[inline]
117 pub fn process_stereo(&mut self, left: f32, right: f32) -> (f32, f32) {
118 let n = self.num_lines;
119 let mono_in = (left + right) * 0.5;
120
121 for i in 0..n {
123 let pos = self.write_positions[i];
124 self.scratch[i] = self.delay_lines[i][pos];
125 }
126
127 for i in 0..n {
130 let mut feedback_sum = 0.0;
131 for j in 0..n {
132 feedback_sum += self.feedback_matrix[i * n + j] * self.scratch[j];
133 }
134 let input = mono_in * self.input_gains[i];
135 let fb = feedback_sum * self.feedback_gain;
136
137 let alpha = self.absorption_coeff[i];
139 self.absorption_state[i] = alpha * self.absorption_state[i] + (1.0 - alpha) * fb;
140
141 let pos = self.write_positions[i];
143 self.delay_lines[i][pos] = input + self.absorption_state[i];
144 self.write_positions[i] = (pos + 1) % self.delay_lengths[i];
145 }
146
147 let mut out_l = 0.0;
149 let mut out_r = 0.0;
150 for i in 0..n {
151 out_l += self.scratch[i] * self.output_gains[i][0];
152 out_r += self.scratch[i] * self.output_gains[i][1];
153 }
154
155 (out_l.clamp(-4.0, 4.0), out_r.clamp(-4.0, 4.0))
157 }
158
159 pub fn reset(&mut self) {
161 for dl in &mut self.delay_lines {
162 dl.fill(0.0);
163 }
164 self.write_positions.fill(0);
165 self.absorption_state.fill(0.0);
166 }
167}
168
169fn hadamard_flat(n: usize) -> Vec<f32> {
172 let mut h = vec![0.0f32; n * n];
173 h[0] = 1.0;
174
175 let mut size = 1;
176 while size < n {
177 for i in 0..size {
179 for j in 0..size {
180 let val = h[i * n + j];
181 h[i * n + (j + size)] = val; h[(i + size) * n + j] = val; h[(i + size) * n + (j + size)] = -val; }
185 }
186 size *= 2;
187 }
188
189 let scale = 1.0 / (n as f32).sqrt();
191 for v in &mut h {
192 *v *= scale;
193 }
194 h
195}
196
197fn prime_delays(n: usize, sample_rate: u32) -> Vec<usize> {
200 let min_samples = (0.020 * sample_rate as f32) as usize;
202 let max_samples = (0.080 * sample_rate as f32) as usize;
203
204 let primes: Vec<usize> = (min_samples..=max_samples)
206 .filter(|&n| is_prime(n))
207 .collect();
208
209 if primes.len() >= n {
211 let step = primes.len() / n;
212 (0..n)
213 .map(|i| primes[(i * step).min(primes.len() - 1)])
214 .collect()
215 } else {
216 let base = (min_samples + max_samples) / 2;
218 (0..n).map(|i| next_prime(base + i * 7)).collect()
219 }
220}
221
222fn is_prime(n: usize) -> bool {
223 if n < 2 {
224 return false;
225 }
226 if n < 4 {
227 return true;
228 }
229 if n.is_multiple_of(2) || n.is_multiple_of(3) {
230 return false;
231 }
232 let mut i = 5;
233 while i * i <= n {
234 if n.is_multiple_of(i) || n.is_multiple_of(i + 2) {
235 return false;
236 }
237 i += 6;
238 }
239 true
240}
241
242fn next_prime(n: usize) -> usize {
243 let mut p = n;
244 while !is_prime(p) {
245 p += 1;
246 }
247 p
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253
254 #[test]
255 fn test_hadamard_orthogonal() {
256 let n = 4;
257 let h = hadamard_flat(n);
258 for i in 0..n {
260 for j in 0..n {
261 let mut dot = 0.0;
262 for k in 0..n {
263 dot += h[i * n + k] * h[j * n + k];
264 }
265 let expected = if i == j { 1.0 } else { 0.0 };
266 assert!(
267 (dot - expected).abs() < 1e-5,
268 "H*H^T[{i}][{j}] = {dot}, expected {expected}"
269 );
270 }
271 }
272 }
273
274 #[test]
275 fn test_fdn_energy_decay() {
276 let mut fdn = Fdn::new(8, 48000);
277 fdn.set_room_params(1.0, 0.3, 1.0, 48000);
278
279 let (l, r) = fdn.process_stereo(1.0, 1.0);
281 let _ = (l, r);
282
283 let mut energy = 0.0f32;
285 for _ in 0..48000 {
286 let (l, r) = fdn.process_stereo(0.0, 0.0);
287 energy += l * l + r * r;
288 }
289
290 assert!(energy > 0.01, "FDN produced no energy: {energy}");
292 assert!(energy.is_finite(), "FDN energy is not finite");
294 }
295
296 #[test]
297 fn test_fdn_stability() {
298 let mut fdn = Fdn::new(8, 48000);
299 fdn.set_room_params(3.0, 0.5, 1.5, 48000);
300
301 for _ in 0..96000 {
303 let (l, r) = fdn.process_stereo(0.01, 0.01);
304 assert!(
305 l.is_finite() && r.is_finite(),
306 "Non-finite output: {l}, {r}"
307 );
308 assert!(l.abs() < 5.0 && r.abs() < 5.0, "Output too large: {l}, {r}");
309 }
310 }
311
312 #[test]
313 fn test_fdn_reset() {
314 let mut fdn = Fdn::new(4, 48000);
315 fdn.process_stereo(1.0, 1.0);
316 fdn.reset();
317 let (l, r) = fdn.process_stereo(0.0, 0.0);
318 assert!(
319 l.abs() < 1e-10 && r.abs() < 1e-10,
320 "State not cleared: {l}, {r}"
321 );
322 }
323
324 #[test]
325 fn test_prime_delays() {
326 let delays = prime_delays(8, 48000);
327 assert_eq!(delays.len(), 8);
328 for &d in &delays {
330 assert!(is_prime(d), "{d} is not prime");
331 }
332 for &d in &delays {
334 assert!((960..=3840).contains(&d), "Delay {d} out of range");
335 }
336 }
337}