1use crate::simulation::physics::Position3D;
10use std::sync::Arc;
11
12#[derive(Debug, Clone)]
14pub enum SourceType {
15 Impulse { amplitude: f32, fired: bool },
17 Tone {
19 frequency_hz: f32,
20 amplitude: f32,
21 phase: f32,
22 },
23 Noise {
25 amplitude: f32,
26 pink: bool,
27 state: [f32; 7], },
29 AudioFile {
31 samples: Arc<Vec<f32>>,
32 sample_rate: u32,
33 position: f64,
35 looping: bool,
36 },
37 LiveInput {
39 buffer: Arc<std::sync::Mutex<Vec<f32>>>,
40 read_pos: usize,
41 },
42 Chirp {
44 start_freq: f32,
45 end_freq: f32,
46 duration_s: f32,
47 amplitude: f32,
48 elapsed: f32,
49 },
50 GaussianPulse {
52 center_time: f32,
53 sigma: f32,
54 amplitude: f32,
55 elapsed: f32,
56 },
57}
58
59impl Default for SourceType {
60 fn default() -> Self {
61 SourceType::Impulse {
62 amplitude: 1.0,
63 fired: false,
64 }
65 }
66}
67
68#[derive(Debug, Clone)]
70pub struct AudioSource {
71 pub id: u32,
73 pub position: Position3D,
75 pub source_type: SourceType,
77 pub active: bool,
79 pub directivity: f32,
81 pub injection_radius: f32,
83}
84
85impl AudioSource {
86 pub fn impulse(id: u32, position: Position3D, amplitude: f32) -> Self {
88 Self {
89 id,
90 position,
91 source_type: SourceType::Impulse {
92 amplitude,
93 fired: false,
94 },
95 active: true,
96 directivity: 1.0,
97 injection_radius: 1.0,
98 }
99 }
100
101 pub fn tone(id: u32, position: Position3D, frequency_hz: f32, amplitude: f32) -> Self {
103 Self {
104 id,
105 position,
106 source_type: SourceType::Tone {
107 frequency_hz,
108 amplitude,
109 phase: 0.0,
110 },
111 active: true,
112 directivity: 1.0,
113 injection_radius: 1.0,
114 }
115 }
116
117 pub fn noise(id: u32, position: Position3D, amplitude: f32, pink: bool) -> Self {
119 Self {
120 id,
121 position,
122 source_type: SourceType::Noise {
123 amplitude,
124 pink,
125 state: [0.0; 7],
126 },
127 active: true,
128 directivity: 1.0,
129 injection_radius: 1.0,
130 }
131 }
132
133 pub fn chirp(
135 id: u32,
136 position: Position3D,
137 start_freq: f32,
138 end_freq: f32,
139 duration_s: f32,
140 amplitude: f32,
141 ) -> Self {
142 Self {
143 id,
144 position,
145 source_type: SourceType::Chirp {
146 start_freq,
147 end_freq,
148 duration_s,
149 amplitude,
150 elapsed: 0.0,
151 },
152 active: true,
153 directivity: 1.0,
154 injection_radius: 1.0,
155 }
156 }
157
158 pub fn gaussian_pulse(
160 id: u32,
161 position: Position3D,
162 center_time: f32,
163 sigma: f32,
164 amplitude: f32,
165 ) -> Self {
166 Self {
167 id,
168 position,
169 source_type: SourceType::GaussianPulse {
170 center_time,
171 sigma,
172 amplitude,
173 elapsed: 0.0,
174 },
175 active: true,
176 directivity: 1.0,
177 injection_radius: 1.0,
178 }
179 }
180
181 pub fn from_samples(
183 id: u32,
184 position: Position3D,
185 samples: Vec<f32>,
186 sample_rate: u32,
187 looping: bool,
188 ) -> Self {
189 Self {
190 id,
191 position,
192 source_type: SourceType::AudioFile {
193 samples: Arc::new(samples),
194 sample_rate,
195 position: 0.0,
196 looping,
197 },
198 active: true,
199 directivity: 1.0,
200 injection_radius: 1.0,
201 }
202 }
203
204 pub fn with_radius(mut self, radius: f32) -> Self {
206 self.injection_radius = radius.max(0.5);
207 self
208 }
209
210 pub fn with_directivity(mut self, directivity: f32) -> Self {
212 self.directivity = directivity.clamp(0.0, 1.0);
213 self
214 }
215
216 pub fn set_position(&mut self, pos: Position3D) {
218 self.position = pos;
219 }
220
221 pub fn next_sample(&mut self, time_step: f32) -> f32 {
225 if !self.active {
226 return 0.0;
227 }
228
229 match &mut self.source_type {
230 SourceType::Impulse { amplitude, fired } => {
231 if !*fired {
232 *fired = true;
233 *amplitude
234 } else {
235 0.0
236 }
237 }
238 SourceType::Tone {
239 frequency_hz,
240 amplitude,
241 phase,
242 } => {
243 let sample = *amplitude * (*phase * 2.0 * std::f32::consts::PI).sin();
244 *phase += *frequency_hz * time_step;
245 if *phase > 1.0 {
246 *phase -= 1.0;
247 }
248 sample
249 }
250 SourceType::Noise {
251 amplitude,
252 pink,
253 state,
254 } => {
255 let white = (rand::random::<f32>() * 2.0 - 1.0) * *amplitude;
257
258 if *pink {
259 state[0] = 0.99886 * state[0] + white * 0.0555179;
261 state[1] = 0.99332 * state[1] + white * 0.0750759;
262 state[2] = 0.96900 * state[2] + white * 0.153_852;
263 state[3] = 0.86650 * state[3] + white * 0.3104856;
264 state[4] = 0.55000 * state[4] + white * 0.5329522;
265 state[5] = -0.7616 * state[5] - white * 0.0168980;
266 let pink = state[0]
267 + state[1]
268 + state[2]
269 + state[3]
270 + state[4]
271 + state[5]
272 + state[6]
273 + white * 0.5362;
274 state[6] = white * 0.115926;
275 pink * 0.11
276 } else {
277 white
278 }
279 }
280 SourceType::AudioFile {
281 samples,
282 sample_rate,
283 position,
284 looping,
285 } => {
286 let len = samples.len();
287 if len == 0 {
288 return 0.0;
289 }
290
291 if *position >= len as f64 {
293 if *looping {
294 *position = position.rem_euclid(len as f64);
295 } else {
296 return 0.0;
297 }
298 }
299
300 let idx0 = position.floor() as usize;
303 let frac = (*position - position.floor()) as f32;
304
305 let sample = if idx0 + 1 < len {
306 let s0 = samples[idx0];
308 let s1 = samples[idx0 + 1];
309 s0 + frac * (s1 - s0)
310 } else if *looping && len > 0 {
311 let s0 = samples[idx0];
313 let s1 = samples[0];
314 s0 + frac * (s1 - s0)
315 } else {
316 samples[idx0]
318 };
319
320 let samples_per_step = *sample_rate as f64 * time_step as f64;
323 *position += samples_per_step.max(0.001);
324
325 sample
326 }
327 SourceType::LiveInput { buffer, read_pos } => {
328 if let Ok(buf) = buffer.lock() {
329 if *read_pos < buf.len() {
330 let sample = buf[*read_pos];
331 *read_pos += 1;
332 return sample;
333 }
334 }
335 0.0
336 }
337 SourceType::Chirp {
338 start_freq,
339 end_freq,
340 duration_s,
341 amplitude,
342 elapsed,
343 } => {
344 if *elapsed >= *duration_s {
345 return 0.0;
346 }
347
348 let t = *elapsed / *duration_s;
349 let freq = *start_freq * (*end_freq / *start_freq).powf(t);
351 let phase = 2.0 * std::f32::consts::PI * freq * *elapsed;
352 let sample = *amplitude * phase.sin();
353
354 *elapsed += time_step;
355 sample
356 }
357 SourceType::GaussianPulse {
358 center_time,
359 sigma,
360 amplitude,
361 elapsed,
362 } => {
363 let t = *elapsed - *center_time;
364 let gaussian = (-t * t / (2.0 * *sigma * *sigma)).exp();
365 let sample = *amplitude * gaussian;
366
367 *elapsed += time_step;
368 sample
369 }
370 }
371 }
372
373 pub fn reset(&mut self) {
375 match &mut self.source_type {
376 SourceType::Impulse { fired, .. } => *fired = false,
377 SourceType::Tone { phase, .. } => *phase = 0.0,
378 SourceType::Noise { state, .. } => *state = [0.0; 7],
379 SourceType::AudioFile { position, .. } => *position = 0.0,
380 SourceType::LiveInput { read_pos, .. } => *read_pos = 0,
381 SourceType::Chirp { elapsed, .. } => *elapsed = 0.0,
382 SourceType::GaussianPulse { elapsed, .. } => *elapsed = 0.0,
383 }
384 }
385
386 pub fn is_finished(&self) -> bool {
388 match &self.source_type {
389 SourceType::Impulse { fired, .. } => *fired,
390 SourceType::AudioFile {
391 samples,
392 position,
393 looping,
394 ..
395 } => !*looping && *position >= samples.len() as f64,
396 SourceType::Chirp {
397 duration_s,
398 elapsed,
399 ..
400 } => *elapsed >= *duration_s,
401 SourceType::GaussianPulse { elapsed, sigma, .. } => *elapsed > sigma * 6.0,
402 _ => false, }
404 }
405}
406
407#[derive(Default)]
409pub struct SourceManager {
410 sources: Vec<AudioSource>,
411 next_id: u32,
412}
413
414impl SourceManager {
415 pub fn new() -> Self {
416 Self::default()
417 }
418
419 pub fn add(&mut self, mut source: AudioSource) -> u32 {
421 let id = self.next_id;
422 source.id = id;
423 self.next_id += 1;
424 self.sources.push(source);
425 id
426 }
427
428 pub fn remove(&mut self, id: u32) -> bool {
430 if let Some(pos) = self.sources.iter().position(|s| s.id == id) {
431 self.sources.remove(pos);
432 true
433 } else {
434 false
435 }
436 }
437
438 pub fn get(&self, id: u32) -> Option<&AudioSource> {
440 self.sources.iter().find(|s| s.id == id)
441 }
442
443 pub fn get_mut(&mut self, id: u32) -> Option<&mut AudioSource> {
445 self.sources.iter_mut().find(|s| s.id == id)
446 }
447
448 pub fn iter(&self) -> impl Iterator<Item = &AudioSource> {
450 self.sources.iter()
451 }
452
453 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut AudioSource> {
455 self.sources.iter_mut()
456 }
457
458 pub fn len(&self) -> usize {
460 self.sources.len()
461 }
462
463 pub fn is_empty(&self) -> bool {
465 self.sources.is_empty()
466 }
467
468 pub fn reset_all(&mut self) {
470 for source in &mut self.sources {
471 source.reset();
472 }
473 }
474
475 pub fn cleanup_finished(&mut self) {
477 self.sources.retain(|s| !s.is_finished() || s.active);
478 }
479}
480
481#[cfg(test)]
482mod tests {
483 use super::*;
484
485 #[test]
486 fn test_impulse_source() {
487 let mut source = AudioSource::impulse(0, Position3D::origin(), 1.0);
488 let dt = 0.001;
489
490 assert_eq!(source.next_sample(dt), 1.0);
492
493 assert_eq!(source.next_sample(dt), 0.0);
495 assert_eq!(source.next_sample(dt), 0.0);
496
497 source.reset();
499 assert_eq!(source.next_sample(dt), 1.0);
500 }
501
502 #[test]
503 fn test_tone_source() {
504 let mut source = AudioSource::tone(0, Position3D::origin(), 440.0, 1.0);
505 let dt = 0.001;
506
507 let mut samples = Vec::new();
509 for _ in 0..100 {
510 samples.push(source.next_sample(dt));
511 }
512
513 let max = samples.iter().fold(0.0_f32, |a, &b| a.max(b));
515 let min = samples.iter().fold(0.0_f32, |a, &b| a.min(b));
516
517 assert!(max > 0.5, "Max should be positive: {}", max);
518 assert!(min < -0.5, "Min should be negative: {}", min);
519 }
520
521 #[test]
522 fn test_chirp_source() {
523 let mut source = AudioSource::chirp(0, Position3D::origin(), 100.0, 1000.0, 0.01, 1.0);
524 let dt = 0.0001;
525
526 let mut count = 0;
527 while source.next_sample(dt) != 0.0 || count < 10 {
528 count += 1;
529 if count > 1000 {
530 break;
531 }
532 }
533
534 assert!(source.is_finished());
536 }
537
538 #[test]
539 fn test_source_manager() {
540 let mut manager = SourceManager::new();
541
542 let id1 = manager.add(AudioSource::impulse(0, Position3D::origin(), 1.0));
543 let id2 = manager.add(AudioSource::tone(
544 0,
545 Position3D::new(1.0, 0.0, 0.0),
546 440.0,
547 0.5,
548 ));
549
550 assert_eq!(manager.len(), 2);
551
552 assert_ne!(id1, id2);
554
555 assert!(manager.get(id1).is_some());
557 assert!(manager.get(id2).is_some());
558
559 assert!(manager.remove(id1));
561 assert_eq!(manager.len(), 1);
562 assert!(manager.get(id1).is_none());
563 }
564
565 #[test]
566 fn test_gaussian_pulse() {
567 let mut source = AudioSource::gaussian_pulse(0, Position3D::origin(), 0.005, 0.001, 1.0);
568 let dt = 0.0001;
569
570 let mut max_val = 0.0_f32;
571 for _ in 0..200 {
572 let sample = source.next_sample(dt);
573 max_val = max_val.max(sample.abs());
574 }
575
576 assert!(max_val > 0.5, "Peak should be significant: {}", max_val);
578 }
579}