1use super::wave::{MemoryWave, WaveGrid};
14use std::sync::{Arc, RwLock};
15
16const SPEED_OF_SOUND: f32 = 343.0 / 10.0; const DEFAULT_EAR_SEPARATION: u8 = 6;
22
23#[derive(Debug, Clone, Copy, PartialEq)]
25pub struct Position {
26 pub x: u8,
27 pub y: u8,
28}
29
30impl Position {
31 pub fn new(x: u8, y: u8) -> Self {
32 Self { x, y }
33 }
34
35 pub fn distance_to(&self, other: &Position) -> f32 {
37 let dx = self.x as f32 - other.x as f32;
38 let dy = self.y as f32 - other.y as f32;
39 (dx * dx + dy * dy).sqrt()
40 }
41
42 pub fn angle_to(&self, other: &Position) -> f32 {
44 let dx = other.x as f32 - self.x as f32;
45 let dy = other.y as f32 - self.y as f32;
46 dy.atan2(dx)
47 }
48}
49
50#[derive(Debug, Clone)]
52pub struct SoundSource {
53 pub position: Position,
55 pub wave: MemoryWave,
57 pub decay: f32,
59 pub active: bool,
61}
62
63impl SoundSource {
64 pub fn new(x: u8, y: u8, frequency: f32, amplitude: f32) -> Self {
65 Self {
66 position: Position::new(x, y),
67 wave: MemoryWave::new(frequency, amplitude),
68 decay: 1.0, active: true,
70 }
71 }
72
73 pub fn sample_at(&self, listener: &Position, t: f32) -> f32 {
75 if !self.active {
76 return 0.0;
77 }
78
79 let distance = self.position.distance_to(listener);
80
81 let delay = distance / SPEED_OF_SOUND;
83
84 let amplitude_factor = 1.0 / (1.0 + self.decay * distance * 0.1);
86
87 self.wave.calculate(t - delay) * amplitude_factor
89 }
90}
91
92#[derive(Debug, Clone, Copy, Default)]
94pub struct StereoSample {
95 pub left: f32,
96 pub right: f32,
97}
98
99impl StereoSample {
100 pub fn new(left: f32, right: f32) -> Self {
101 Self { left, right }
102 }
103
104 pub fn mix(&mut self, other: StereoSample) {
106 self.left += other.left;
107 self.right += other.right;
108 }
109
110 pub fn apply_gain(&mut self, gain: f32) {
112 self.left *= gain;
113 self.right *= gain;
114 }
115
116 pub fn clamp(&mut self) {
118 self.left = self.left.clamp(-1.0, 1.0);
119 self.right = self.right.clamp(-1.0, 1.0);
120 }
121}
122
123pub struct SpatialAudioField {
125 grid: Arc<RwLock<WaveGrid>>,
127
128 sources: Vec<SoundSource>,
130
131 left_ear: Position,
133
134 right_ear: Position,
136
137 head_center: Position,
139
140 current_time: f32,
142
143 sample_rate: f32,
145}
146
147impl SpatialAudioField {
148 pub fn new() -> Self {
151 let center_y = 128u8;
152 let center_x = 128u8;
153 let half_sep = DEFAULT_EAR_SEPARATION / 2;
154
155 Self {
156 grid: Arc::new(RwLock::new(WaveGrid::new())),
157 sources: Vec::new(),
158 left_ear: Position::new(center_x - half_sep, center_y),
159 right_ear: Position::new(center_x + half_sep, center_y),
160 head_center: Position::new(center_x, center_y),
161 current_time: 0.0,
162 sample_rate: 44100.0, }
164 }
165
166 pub fn with_ears(left: Position, right: Position) -> Self {
168 let center_x = (left.x as u16 + right.x as u16) / 2;
169 let center_y = (left.y as u16 + right.y as u16) / 2;
170
171 Self {
172 grid: Arc::new(RwLock::new(WaveGrid::new())),
173 sources: Vec::new(),
174 left_ear: left,
175 right_ear: right,
176 head_center: Position::new(center_x as u8, center_y as u8),
177 current_time: 0.0,
178 sample_rate: 44100.0,
179 }
180 }
181
182 pub fn set_sample_rate(&mut self, rate: f32) {
184 self.sample_rate = rate;
185 }
186
187 pub fn add_source(&mut self, source: SoundSource) -> usize {
189 let idx = self.sources.len();
190
191 if let Ok(mut grid) = self.grid.write() {
193 grid.store(
194 source.position.x,
195 source.position.y,
196 (source.wave.amplitude * 65535.0) as u16,
197 source.wave.clone(),
198 );
199 }
200
201 self.sources.push(source);
202 idx
203 }
204
205 pub fn add_tone(&mut self, x: u8, y: u8, frequency: f32, amplitude: f32) -> usize {
207 self.add_source(SoundSource::new(x, y, frequency, amplitude))
208 }
209
210 pub fn remove_source(&mut self, idx: usize) -> Option<SoundSource> {
212 if idx < self.sources.len() {
213 Some(self.sources.remove(idx))
214 } else {
215 None
216 }
217 }
218
219 pub fn set_source_active(&mut self, idx: usize, active: bool) {
221 if let Some(source) = self.sources.get_mut(idx) {
222 source.active = active;
223 }
224 }
225
226 pub fn move_source(&mut self, idx: usize, new_pos: Position) {
228 if let Some(source) = self.sources.get_mut(idx) {
229 source.position = new_pos;
230 }
231 }
232
233 pub fn sample(&mut self) -> StereoSample {
235 let mut output = StereoSample::default();
236
237 for source in &self.sources {
238 let left_sample = source.sample_at(&self.left_ear, self.current_time);
239 let right_sample = source.sample_at(&self.right_ear, self.current_time);
240
241 output.left += left_sample;
242 output.right += right_sample;
243 }
244
245 self.current_time += 1.0 / self.sample_rate;
247
248 output.clamp();
249 output
250 }
251
252 pub fn sample_frames(&mut self, num_frames: usize) -> Vec<f32> {
254 let mut buffer = Vec::with_capacity(num_frames * 2);
255
256 for _ in 0..num_frames {
257 let sample = self.sample();
258 buffer.push(sample.left);
259 buffer.push(sample.right);
260 }
261
262 buffer
263 }
264
265 pub fn direction_of(&self, pos: &Position) -> f32 {
268 let angle = self.head_center.angle_to(pos);
269 let degrees = angle.to_degrees();
271 degrees - 90.0
273 }
274
275 pub fn itd_for(&self, pos: &Position) -> f32 {
277 let dist_left = pos.distance_to(&self.left_ear);
278 let dist_right = pos.distance_to(&self.right_ear);
279 (dist_left - dist_right) / SPEED_OF_SOUND
280 }
281
282 pub fn ild_for(&self, pos: &Position) -> f32 {
284 let dist_left = pos.distance_to(&self.left_ear);
285 let dist_right = pos.distance_to(&self.right_ear);
286
287 if dist_left > 0.1 && dist_right > 0.1 {
289 dist_right / dist_left
290 } else {
291 1.0
292 }
293 }
294
295 pub fn time(&self) -> f32 {
297 self.current_time
298 }
299
300 pub fn reset_time(&mut self) {
302 self.current_time = 0.0;
303 }
304
305 pub fn source_count(&self) -> usize {
307 self.sources.iter().filter(|s| s.active).count()
308 }
309}
310
311impl Default for SpatialAudioField {
312 fn default() -> Self {
313 Self::new()
314 }
315}
316
317#[cfg(test)]
318mod tests {
319 use super::*;
320
321 #[test]
322 fn test_position_distance() {
323 let p1 = Position::new(0, 0);
324 let p2 = Position::new(3, 4);
325 assert!((p1.distance_to(&p2) - 5.0).abs() < 0.001);
326 }
327
328 #[test]
329 fn test_stereo_separation() {
330 let mut field = SpatialAudioField::new();
331
332 field.add_tone(64, 128, 440.0, 0.5); let mut left_power = 0.0f32;
338 let mut right_power = 0.0f32;
339
340 for _ in 0..1000 {
341 let sample = field.sample();
342 left_power += sample.left * sample.left;
343 right_power += sample.right * sample.right;
344 }
345
346 assert!(left_power > right_power,
348 "Left should be louder for left-positioned source (L:{:.4} R:{:.4})",
349 left_power.sqrt(), right_power.sqrt());
350 }
351
352 #[test]
353 fn test_itd_calculation() {
354 let field = SpatialAudioField::new();
355
356 let left_source = Position::new(64, 128);
358 let itd = field.itd_for(&left_source);
359
360 assert!(itd < 0.0, "ITD should be negative for left source");
362 }
363
364 #[test]
365 fn test_center_source_equal() {
366 let mut field = SpatialAudioField::new();
367
368 field.add_tone(128, 200, 440.0, 0.5); for _ in 0..100 {
373 let sample = field.sample();
374 let diff = (sample.left - sample.right).abs();
375 assert!(diff < 0.1, "Center source should have similar L/R");
376 }
377 }
378}