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