1use crate::delay::{Delay, InterpolatorLinear};
7use num_traits::{Float, FromPrimitive};
8use crate::filters::Biquad;
9use crate::mix::Hadamard;
10
11#[derive(Clone, Copy, Debug)]
13pub struct Position<T: Float> {
14 pub x: T,
15 pub y: T,
16 pub z: T,
17}
18
19impl<T: Float> Position<T> {
20 pub fn distance(&self, other: &Position<T>) -> T {
21 ((self.x - other.x).powi(2) + (self.y - other.y).powi(2) + (self.z - other.z).powi(2)).sqrt()
22 }
23}
24
25#[derive(Clone, Debug)]
27pub struct Path<T: Float> {
28 pub delay_samples: T,
29 pub gain: T,
30 }
32
33pub struct Spacing<T: Float> {
35 pub sample_rate: T,
36 pub sources: Vec<Position<T>>,
37 pub receivers: Vec<Position<T>>,
38 pub paths: Vec<(usize, usize, Path<T>)>, pub delays: Vec<Delay<T, InterpolatorLinear<T>>>,
40 pub room_size: T, pub damping: T, pub diff: T, pub bass: T, pub decay: T, pub cross: T, pub bass_filters: Vec<Biquad<T>>,
50 pub cross_mixer: Option<Hadamard<T>>,
51}
52
53impl<T: Float + FromPrimitive> Spacing<T> {
54 pub fn new(sample_rate: T) -> Self {
56 Self {
57 sample_rate,
58 sources: Vec::new(),
59 receivers: Vec::new(),
60 paths: Vec::new(),
61 delays: Vec::new(),
62 room_size: T::one(),
63 damping: T::zero(),
64 diff: T::zero(),
65 bass: T::zero(),
66 decay: T::zero(),
67 cross: T::zero(),
68 bass_filters: Vec::new(),
69 cross_mixer: None,
70 }
71 }
72
73 pub fn set_room_size(&mut self, size: T) {
75 let min = T::from(0.01).unwrap();
76 self.room_size = if size < min { min } else { size };
77 }
78 pub fn set_damping(&mut self, damping: T) {
80 self.damping = if damping < T::zero() {
81 T::zero()
82 } else if damping > T::one() {
83 T::one()
84 } else {
85 damping
86 };
87 }
88 pub fn set_diff(&mut self, diff: T) {
90 self.diff = if diff < T::zero() { T::zero() } else if diff > T::one() { T::one() } else { diff };
91 }
92 pub fn set_bass(&mut self, bass: T) {
94 self.bass = bass;
95 self.bass_filters.clear();
97 for _ in 0..self.receivers.len() {
98 let mut biq = Biquad::new(false);
99 let freq = T::from_f32(200.0).unwrap() / self.sample_rate; biq.low_shelf(freq, bass);
101 self.bass_filters.push(biq);
102 }
103 }
104 pub fn set_decay(&mut self, decay: T) {
106 self.decay = if decay < T::zero() { T::zero() } else if decay > T::one() { T::one() } else { decay };
107 }
108 pub fn set_cross(&mut self, cross: T) {
110 self.cross = if cross < T::zero() { T::zero() } else if cross > T::one() { T::one() } else { cross };
111 if self.cross > T::zero() {
112 self.cross_mixer = Some(Hadamard::new(self.receivers.len()));
113 } else {
114 self.cross_mixer = None;
115 }
116 }
117
118 pub fn add_source(&mut self, pos: Position<T>) -> usize {
120 self.sources.push(pos);
121 self.sources.len() - 1
122 }
123
124 pub fn add_receiver(&mut self, pos: Position<T>) -> usize {
126 self.receivers.push(pos);
127 let mut biq = Biquad::new(false);
129 let freq = T::from_f32(200.0).unwrap() / self.sample_rate;
130 biq.low_shelf(freq, self.bass);
131 self.bass_filters.push(biq);
132 self.receivers.len() - 1
133 }
134
135 pub fn add_path(&mut self, source_idx: usize, receiver_idx: usize, gain: T, extra_distance: T) {
137 let src = self.sources[source_idx];
138 let recv = self.receivers[receiver_idx];
139 let distance = (src.distance(&recv) + extra_distance) * self.room_size;
140 let speed_of_sound = T::from(343.0).unwrap();
141 let delay_samples = distance / speed_of_sound * self.sample_rate;
142 self.paths.push((source_idx, receiver_idx, Path { delay_samples, gain }));
143 let delay_len = delay_samples.ceil().to_usize().unwrap_or(1) + 1;
144 self.delays.push(Delay::new(InterpolatorLinear::new(), delay_len));
145 }
146
147 pub fn clear_paths(&mut self) {
149 self.paths.clear();
150 self.delays.clear();
151 }
152
153 pub fn process(&mut self, inputs: &[&[T]], outputs: &mut [Vec<T>]) {
155 let len = if let Some(input) = inputs.get(0) { input.len() } else { 0 };
156 for out in outputs.iter_mut() {
157 out.clear();
158 out.resize(len, T::zero());
159 }
160 let mut diff_inputs: Vec<Vec<T>> = vec![];
162 if self.diff > T::zero() && inputs.len() > 1 {
163 diff_inputs = inputs.iter().map(|x| x.to_vec()).collect();
164 let mut frame: Vec<T> = diff_inputs.iter().map(|v| v[0]).collect();
165 let hadamard = Hadamard::new(inputs.len());
166 for i in 0..len {
167 for (j, v) in diff_inputs.iter_mut().enumerate() {
168 frame[j] = v[i];
169 }
170 hadamard.in_place(&mut frame);
171 for (j, v) in diff_inputs.iter_mut().enumerate() {
172 v[i] = frame[j] * self.diff + v[i] * (T::one() - self.diff);
173 }
174 }
175 }
176 let use_inputs: Vec<&[T]> = if self.diff > T::zero() && inputs.len() > 1 {
177 diff_inputs.iter().map(|v| v.as_slice()).collect()
178 } else {
179 inputs.iter().map(|x| *x).collect()
180 };
181 for i in 0..len {
182 let mut wet_sum = vec![T::zero(); outputs.len()];
183 for ((path_idx, (source_idx, receiver_idx, path)), delay) in self.paths.iter().enumerate().zip(self.delays.iter_mut()) {
184 let sample = if let Some(input) = use_inputs.get(*source_idx) {
185 input[i]
186 } else {
187 T::zero()
188 };
189 delay.write(sample);
190 let mut delayed = delay.read(path.delay_samples) * path.gain;
191 if self.decay > T::zero() {
193 let decay_factor = T::one() - self.decay * T::from_f32(0.001).unwrap();
194 delayed = delayed * decay_factor.powi(i as i32);
195 }
196 if self.damping > T::zero() {
198 delayed = delayed * (T::one() - self.damping).powi(i as i32);
199 }
200 wet_sum[*receiver_idx] = wet_sum[*receiver_idx] + delayed;
201 }
202 for (r, wet) in wet_sum.iter_mut().enumerate() {
204 if let Some(filt) = self.bass_filters.get_mut(r) {
205 *wet = filt.process(*wet);
206 }
207 }
208 if let Some(mixer) = &self.cross_mixer {
210 let mut frame = wet_sum.clone();
211 mixer.in_place(&mut frame);
212 for (r, out) in outputs.iter_mut().enumerate() {
213 out[i] = frame[r] * self.cross + wet_sum[r] * (T::one() - self.cross);
214 }
215 } else {
216 for (r, out) in outputs.iter_mut().enumerate() {
217 out[i] = wet_sum[r];
218 }
219 }
220 }
221 }
222
223 }
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229 #[test]
230 fn test_direct_path() {
231 let sample_rate = 48000.0f32;
232 let mut spacing = Spacing::<f32>::new(sample_rate);
233 let src = spacing.add_source(Position { x: 0.0, y: 0.0, z: 0.0 });
234 let recv = spacing.add_receiver(Position { x: 3.43, y: 0.0, z: 0.0 }); spacing.add_path(src, recv, 1.0, 0.0);
236 let mut input = vec![0.0; 500];
237 input[0] = 1.0;
238 let mut outputs = vec![vec![0.0; 500]];
239 spacing.process(&[&input], &mut outputs);
240 let found = outputs[0][478..=482].iter().cloned().fold(f32::MIN, f32::max);
241 assert!((found - 1.0).abs() < 1e-5, "max in window around 480: {}", found);
242 }
243 #[test]
244 fn test_multiple_receivers_and_reflection() {
245 let sample_rate = 48000.0f32;
246 let mut spacing = Spacing::<f32>::new(sample_rate);
247 let src = spacing.add_source(Position { x: 0.0, y: 0.0, z: 0.0 });
248 let recv1 = spacing.add_receiver(Position { x: 3.43, y: 0.0, z: 0.0 });
249 let recv2 = spacing.add_receiver(Position { x: 3.43, y: 3.0, z: 0.0 });
250 spacing.add_path(src, recv1, 1.0, 0.0);
251 spacing.add_path(src, recv2, 0.5, 3.0); let mut input = vec![0.0; 1200];
253 input[0] = 1.0;
254 let mut outputs = vec![vec![0.0; 1200]; 2];
255 spacing.process(&[&input], &mut outputs);
256 let found1 = outputs[0][478..=482].iter().cloned().fold(f32::MIN, f32::max);
257 assert!((found1 - 1.0).abs() < 1e-5, "max in window around 480: {}", found1);
258 let d = (3.43f32.powi(2) + 3.0f32.powi(2)).sqrt() + 3.0;
259 let delay = (d / 343.0 * sample_rate).round() as usize;
260 if delay < outputs[1].len() - 1 {
261 let window = &outputs[1][delay.saturating_sub(2)..(delay+3).min(outputs[1].len())];
262 let max_val = window.iter().cloned().fold(f32::MIN, f32::max);
263 assert!((max_val > 0.2 && max_val < 0.3), "max_val={} in window around delay={}", max_val, delay);
264 } else {
265 panic!("Reflection delay {} exceeds output buffer length {}", delay, outputs[1].len());
266 }
267 }
268 #[test]
269 fn test_damping_reduces_amplitude() {
270 let sample_rate = 48000.0f32;
271 let mut spacing = Spacing::<f32>::new(sample_rate);
272 let src = spacing.add_source(Position { x: 0.0, y: 0.0, z: 0.0 });
273 let recv = spacing.add_receiver(Position { x: 3.43, y: 0.0, z: 0.0 });
274 spacing.add_path(src, recv, 1.0, 0.0);
275 let mut input = vec![0.0; 500];
276 input[0] = 1.0;
277 let mut outputs = vec![vec![0.0; 500]];
278 spacing.set_damping(0.0);
279 spacing.process(&[&input], &mut outputs);
280 let found_no_damping = outputs[0][478..=482].iter().cloned().fold(f32::MIN, f32::max);
281 spacing.clear_paths(); spacing.delays.clear();
282 spacing.add_path(src, recv, 1.0, 0.0);
283 spacing.set_damping(0.5);
284 let mut outputs2 = vec![vec![0.0; 500]];
285 spacing.process(&[&input], &mut outputs2);
286 let found_damping = outputs2[0][478..=482].iter().cloned().fold(f32::MIN, f32::max);
287 assert!(found_damping < found_no_damping, "Damping should reduce amplitude: {} vs {}", found_damping, found_no_damping);
288 }
289 #[test]
290 fn test_room_size_affects_delay() {
291 let sample_rate = 48000.0f32;
292 let mut spacing = Spacing::<f32>::new(sample_rate);
293 let src = spacing.add_source(Position { x: 0.0, y: 0.0, z: 0.0 });
294 let recv = spacing.add_receiver(Position { x: 3.43, y: 0.0, z: 0.0 });
295 spacing.set_room_size(1.0);
296 spacing.add_path(src, recv, 1.0, 0.0);
297 let mut input = vec![0.0; 2500];
298 input[0] = 1.0;
299 let mut outputs = vec![vec![0.0; 2500]];
300 spacing.process(&[&input], &mut outputs);
301 let _found1 = outputs[0][478..=482].iter().cloned().fold(f32::MIN, f32::max);
302 spacing.clear_paths(); spacing.delays.clear();
303 spacing.set_room_size(2.0);
304 spacing.add_path(src, recv, 1.0, 0.0);
305 let mut input = vec![0.0; 2500];
306 input[0] = 1.0;
307 let mut outputs2 = vec![vec![0.0; 2500]];
308 spacing.process(&[&input], &mut outputs2);
309 let found2 = outputs2[0][958..=962].iter().cloned().fold(f32::MIN, f32::max);
310 assert!(found2 > 0.2, "Impulse at double room size should be present: {}", found2);
311 assert!(outputs2[0][478..=482].iter().all(|&v| v < 1e-3), "Impulse should not be at original delay");
312 }
313 #[test]
314 fn test_multiple_sources() {
315 let sample_rate = 48000.0f32;
316 let mut spacing = Spacing::<f32>::new(sample_rate);
317 let src1 = spacing.add_source(Position { x: 0.0, y: 0.0, z: 0.0 });
318 let src2 = spacing.add_source(Position { x: 1.0, y: 0.0, z: 0.0 });
319 let recv = spacing.add_receiver(Position { x: 3.43, y: 0.0, z: 0.0 });
320 spacing.add_path(src1, recv, 1.0, 0.0);
321 spacing.add_path(src2, recv, 0.5, 0.0);
322 let mut input1 = vec![0.0; 540];
323 input1[0] = 1.0;
324 let mut input2 = vec![0.0; 540];
325 input2[30] = 1.0;
326 let mut outputs = vec![vec![0.0; 540]];
327 spacing.process(&[&input1, &input2], &mut outputs);
328 let peak1 = outputs[0][478..=482].iter().cloned().fold(f32::MIN, f32::max);
329 let peak2 = outputs[0][368..=372].iter().cloned().fold(f32::MIN, f32::max);
330 assert!(peak1 > 0.4 && peak2 > 0.2, "Both impulses should be present: peak1={}, peak2={}", peak1, peak2);
331 assert!(peak1 + peak2 > 0.9, "Sum of peaks should be > 0.9: {}", peak1 + peak2);
332 }
333}