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))
159 }
160
161 pub fn reset(&mut self) {
163 for dl in &mut self.delay_lines {
164 dl.fill(0.0);
165 }
166 self.write_positions.fill(0);
167 self.absorption_state.fill(0.0);
168 }
169}
170
171fn hadamard_flat(n: usize) -> Vec<f32> {
174 let mut h = vec![0.0f32; n * n];
175 h[0] = 1.0;
176
177 let mut size = 1;
178 while size < n {
179 for i in 0..size {
181 for j in 0..size {
182 let val = h[i * n + j];
183 h[i * n + (j + size)] = val; h[(i + size) * n + j] = val; h[(i + size) * n + (j + size)] = -val; }
187 }
188 size *= 2;
189 }
190
191 let scale = 1.0 / (n as f32).sqrt();
193 for v in &mut h {
194 *v *= scale;
195 }
196 h
197}
198
199fn prime_delays(n: usize, sample_rate: u32) -> Vec<usize> {
202 let min_samples = (0.020 * sample_rate as f32) as usize;
204 let max_samples = (0.080 * sample_rate as f32) as usize;
205
206 let primes: Vec<usize> = (min_samples..=max_samples)
208 .filter(|&n| is_prime(n))
209 .collect();
210
211 if primes.len() >= n {
213 let step = primes.len() / n;
214 (0..n)
215 .map(|i| primes[(i * step).min(primes.len() - 1)])
216 .collect()
217 } else {
218 let base = (min_samples + max_samples) / 2;
220 (0..n).map(|i| next_prime(base + i * 7)).collect()
221 }
222}
223
224fn is_prime(n: usize) -> bool {
225 if n < 2 {
226 return false;
227 }
228 if n < 4 {
229 return true;
230 }
231 if n.is_multiple_of(2) || n.is_multiple_of(3) {
232 return false;
233 }
234 let mut i = 5;
235 while i * i <= n {
236 if n.is_multiple_of(i) || n.is_multiple_of(i + 2) {
237 return false;
238 }
239 i += 6;
240 }
241 true
242}
243
244fn next_prime(n: usize) -> usize {
245 let mut p = n;
246 while !is_prime(p) {
247 p += 1;
248 }
249 p
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 #[test]
257 fn test_hadamard_orthogonal() {
258 let n = 4;
259 let h = hadamard_flat(n);
260 for i in 0..n {
262 for j in 0..n {
263 let mut dot = 0.0;
264 for k in 0..n {
265 dot += h[i * n + k] * h[j * n + k];
266 }
267 let expected = if i == j { 1.0 } else { 0.0 };
268 assert!(
269 (dot - expected).abs() < 1e-5,
270 "H*H^T[{i}][{j}] = {dot}, expected {expected}"
271 );
272 }
273 }
274 }
275
276 #[test]
277 fn test_fdn_energy_decay() {
278 let mut fdn = Fdn::new(8, 48000);
279 fdn.set_room_params(1.0, 0.3, 1.0, 48000);
280
281 let (l, r) = fdn.process_stereo(1.0, 1.0);
283 let _ = (l, r);
284
285 let mut energy = 0.0f32;
287 for _ in 0..48000 {
288 let (l, r) = fdn.process_stereo(0.0, 0.0);
289 energy += l * l + r * r;
290 }
291
292 assert!(energy > 0.01, "FDN produced no energy: {energy}");
294 assert!(energy.is_finite(), "FDN energy is not finite");
296 }
297
298 #[test]
299 fn test_fdn_stability() {
300 let mut fdn = Fdn::new(8, 48000);
301 fdn.set_room_params(3.0, 0.5, 1.5, 48000);
302
303 for _ in 0..96000 {
305 let (l, r) = fdn.process_stereo(0.01, 0.01);
306 assert!(
307 l.is_finite() && r.is_finite(),
308 "Non-finite output: {l}, {r}"
309 );
310 assert!(l.abs() < 5.0 && r.abs() < 5.0, "Output too large: {l}, {r}");
311 }
312 }
313
314 #[test]
315 fn test_fdn_reset() {
316 let mut fdn = Fdn::new(4, 48000);
317 fdn.process_stereo(1.0, 1.0);
318 fdn.reset();
319 let (l, r) = fdn.process_stereo(0.0, 0.0);
320 assert!(
321 l.abs() < 1e-10 && r.abs() < 1e-10,
322 "State not cleared: {l}, {r}"
323 );
324 }
325
326 #[test]
327 fn test_prime_delays() {
328 let delays = prime_delays(8, 48000);
329 assert_eq!(delays.len(), 8);
330 for &d in &delays {
332 assert!(is_prime(d), "{d} is not prime");
333 }
334 for &d in &delays {
336 assert!((960..=3840).contains(&d), "Delay {d} out of range");
337 }
338 }
339}