1use std::f32::consts::PI;
15
16#[derive(Debug, Clone)]
18pub enum AudioSource {
19 SineWave {
21 frequency: f32,
23 amplitude: f32,
25 },
26
27 SpeechPattern {
30 fundamental_hz: f32,
32 harmonics: Vec<f32>,
34 variation_hz: f32,
36 },
37
38 Silence {
40 noise_floor_db: f32,
42 },
43
44 WhiteNoise {
46 amplitude: f32,
48 },
49
50 Samples {
52 data: Vec<f32>,
54 sample_rate: u32,
56 loop_playback: bool,
58 },
59}
60
61impl Default for AudioSource {
62 fn default() -> Self {
63 Self::Silence {
64 noise_floor_db: -60.0,
65 }
66 }
67}
68
69#[derive(Debug, Clone)]
71pub struct AudioEmulatorConfig {
72 pub sample_rate: u32,
74 pub channels: u8,
76 pub buffer_size: usize,
78}
79
80impl Default for AudioEmulatorConfig {
81 fn default() -> Self {
82 Self {
83 sample_rate: 16000, channels: 1, buffer_size: 1024,
86 }
87 }
88}
89
90#[derive(Debug, Clone)]
102pub struct AudioEmulator {
103 source: AudioSource,
104 config: AudioEmulatorConfig,
105 phase: f32,
107 sample_count: u64,
109 rng_state: u64,
111}
112
113impl AudioEmulator {
114 #[must_use]
116 pub fn new(source: AudioSource) -> Self {
117 Self::with_config(source, AudioEmulatorConfig::default())
118 }
119
120 #[must_use]
122 pub fn with_config(source: AudioSource, config: AudioEmulatorConfig) -> Self {
123 Self {
124 source,
125 config,
126 phase: 0.0,
127 sample_count: 0,
128 rng_state: 0x853c_49e6_748f_ea9b, }
130 }
131
132 #[must_use]
134 pub fn sample_rate(&self) -> u32 {
135 self.config.sample_rate
136 }
137
138 #[must_use]
140 pub fn samples_generated(&self) -> u64 {
141 self.sample_count
142 }
143
144 #[must_use]
146 pub fn generate_samples(&mut self, duration_seconds: f32) -> Vec<f32> {
147 let num_samples = (duration_seconds * self.config.sample_rate as f32) as usize;
148 self.generate_n_samples(num_samples)
149 }
150
151 #[must_use]
153 pub fn generate_n_samples(&mut self, num_samples: usize) -> Vec<f32> {
154 let mut samples = Vec::with_capacity(num_samples);
155 let sample_rate = self.config.sample_rate as f32;
156
157 for _ in 0..num_samples {
158 let sample = self.generate_single_sample(sample_rate);
159 samples.push(sample);
160 self.sample_count += 1;
161 }
162
163 samples
164 }
165
166 fn generate_single_sample(&mut self, sample_rate: f32) -> f32 {
168 match &self.source {
169 AudioSource::SineWave {
170 frequency,
171 amplitude,
172 } => {
173 let freq = frequency.clamp(0.001, sample_rate / 2.0);
174 let amp = amplitude.clamp(0.0, 1.0);
175 let sample = (self.phase * 2.0 * PI).sin() * amp;
176 self.phase += freq / sample_rate;
177 if self.phase >= 1.0 {
178 self.phase -= 1.0;
179 }
180 sample
181 }
182
183 AudioSource::SpeechPattern {
184 fundamental_hz,
185 harmonics,
186 variation_hz,
187 } => {
188 let freq = fundamental_hz.clamp(20.0, sample_rate / 2.0);
189 let var = variation_hz.clamp(0.0, freq / 2.0);
190
191 let time = self.sample_count as f32 / sample_rate;
193 let freq_with_variation = freq + var * (time * 5.0).sin();
194
195 let mut sample = (self.phase * 2.0 * PI).sin();
197
198 for (i, &harmonic_amp) in harmonics.iter().enumerate() {
200 let harmonic_num = (i + 2) as f32;
201 let harmonic_freq = freq_with_variation * harmonic_num;
202 if harmonic_freq < sample_rate / 2.0 {
203 let harmonic_phase = self.phase * harmonic_num;
204 sample += (harmonic_phase * 2.0 * PI).sin() * harmonic_amp.clamp(0.0, 1.0);
205 }
206 }
207
208 let total_amp = 1.0 + harmonics.iter().sum::<f32>();
210 sample /= total_amp.max(1.0);
211
212 self.phase += freq_with_variation / sample_rate;
214 if self.phase >= 1.0 {
215 self.phase -= 1.0;
216 }
217
218 sample.clamp(-1.0, 1.0)
219 }
220
221 AudioSource::Silence { noise_floor_db } => {
222 let amp = 10.0_f32.powf(noise_floor_db.clamp(-100.0, 0.0) / 20.0);
224 let noise = self.next_random_f32() * 2.0 - 1.0;
226 noise * amp
227 }
228
229 AudioSource::WhiteNoise { amplitude } => {
230 let amp = amplitude.clamp(0.0, 1.0);
231 let noise = self.next_random_f32() * 2.0 - 1.0;
232 noise * amp
233 }
234
235 AudioSource::Samples {
236 data,
237 sample_rate: _src_rate,
238 loop_playback,
239 } => {
240 if data.is_empty() {
241 return 0.0;
242 }
243 let idx = self.sample_count as usize;
244 if idx < data.len() {
245 data[idx].clamp(-1.0, 1.0)
246 } else if *loop_playback {
247 data[idx % data.len()].clamp(-1.0, 1.0)
248 } else {
249 0.0
250 }
251 }
252 }
253 }
254
255 fn next_random_f32(&mut self) -> f32 {
257 self.rng_state ^= self.rng_state << 13;
258 self.rng_state ^= self.rng_state >> 7;
259 self.rng_state ^= self.rng_state << 17;
260 (self.rng_state as f32) / (u64::MAX as f32)
262 }
263
264 pub fn reset(&mut self) {
266 self.phase = 0.0;
267 self.sample_count = 0;
268 self.rng_state = 0x853c_49e6_748f_ea9b;
269 }
270
271 #[must_use]
273 pub fn generate_mock_js(&self, samples: &[f32]) -> String {
274 let samples_json: String = samples
276 .iter()
277 .map(|s| format!("{s:.6}"))
278 .collect::<Vec<_>>()
279 .join(",");
280
281 format!(
282 r#"
283(function() {{
284 const mockSamples = new Float32Array([{samples_json}]);
285 const sampleRate = {sample_rate};
286 let sampleIndex = 0;
287
288 // Create mock MediaStream
289 const audioContext = new AudioContext({{ sampleRate: sampleRate }});
290 const bufferSize = 1024;
291 const scriptNode = audioContext.createScriptProcessor(bufferSize, 1, 1);
292
293 scriptNode.onaudioprocess = function(e) {{
294 const output = e.outputBuffer.getChannelData(0);
295 for (let i = 0; i < bufferSize; i++) {{
296 if (sampleIndex < mockSamples.length) {{
297 output[i] = mockSamples[sampleIndex++];
298 }} else {{
299 output[i] = 0;
300 }}
301 }}
302 }};
303
304 const dest = audioContext.createMediaStreamDestination();
305 scriptNode.connect(dest);
306 scriptNode.connect(audioContext.destination);
307
308 // Override getUserMedia
309 const originalGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);
310 navigator.mediaDevices.getUserMedia = async function(constraints) {{
311 if (constraints.audio) {{
312 return dest.stream;
313 }}
314 return originalGetUserMedia(constraints);
315 }};
316
317 window.__PROBAR_AUDIO_EMULATOR__ = {{
318 sampleIndex: () => sampleIndex,
319 reset: () => {{ sampleIndex = 0; }},
320 context: audioContext
321 }};
322}})();
323"#,
324 samples_json = samples_json,
325 sample_rate = self.config.sample_rate
326 )
327 }
328
329 #[cfg(feature = "browser")]
349 pub async fn inject_cdp(
350 &mut self,
351 page: &chromiumoxide::Page,
352 duration_seconds: f32,
353 ) -> Result<(), AudioEmulatorError> {
354 let samples = self.generate_samples(duration_seconds);
356
357 let js = self.generate_mock_js(&samples);
359 page.evaluate(js.as_str()).await.map_err(|e| {
360 AudioEmulatorError::InjectionFailed(format!("CDP injection failed: {e}"))
361 })?;
362
363 Ok(())
364 }
365
366 #[cfg(feature = "browser")]
368 pub async fn is_active_cdp(page: &chromiumoxide::Page) -> Result<bool, AudioEmulatorError> {
369 let result: bool = page
370 .evaluate("typeof window.__PROBAR_AUDIO_EMULATOR__ !== 'undefined'")
371 .await
372 .map_err(|e| AudioEmulatorError::InjectionFailed(format!("CDP check failed: {e}")))?
373 .into_value()
374 .unwrap_or(false);
375
376 Ok(result)
377 }
378
379 #[cfg(feature = "browser")]
381 pub async fn get_sample_index_cdp(
382 page: &chromiumoxide::Page,
383 ) -> Result<u64, AudioEmulatorError> {
384 let result: f64 = page
385 .evaluate("window.__PROBAR_AUDIO_EMULATOR__?.sampleIndex() ?? 0")
386 .await
387 .map_err(|e| AudioEmulatorError::InjectionFailed(format!("CDP query failed: {e}")))?
388 .into_value()
389 .unwrap_or(0.0);
390
391 Ok(result as u64)
392 }
393}
394
395#[derive(Debug, Clone)]
397pub enum AudioEmulatorError {
398 InjectionFailed(String),
400 ContextNotAvailable,
402 InvalidConfig(String),
404}
405
406impl std::fmt::Display for AudioEmulatorError {
407 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
408 match self {
409 Self::InjectionFailed(msg) => write!(f, "Audio injection failed: {msg}"),
410 Self::ContextNotAvailable => write!(f, "Audio context not available"),
411 Self::InvalidConfig(msg) => write!(f, "Invalid audio config: {msg}"),
412 }
413 }
414}
415
416impl std::error::Error for AudioEmulatorError {}
417
418#[cfg(test)]
419#[allow(clippy::unwrap_used, clippy::expect_used, clippy::float_cmp)]
420mod tests {
421 use super::*;
422
423 #[test]
428 fn f016_zero_length_audio_no_crash() {
429 let mut emulator = AudioEmulator::new(AudioSource::Silence {
431 noise_floor_db: -60.0,
432 });
433 let samples = emulator.generate_samples(0.0);
434 assert!(samples.is_empty());
435 }
436
437 #[test]
438 fn f017_context_suspended_handling() {
439 let mut emulator = AudioEmulator::new(AudioSource::SineWave {
441 frequency: 440.0,
442 amplitude: 0.5,
443 });
444 let samples = emulator.generate_samples(0.1);
446 assert!(!samples.is_empty());
447 emulator.reset();
449 let samples_after = emulator.generate_samples(0.1);
450 assert!(!samples_after.is_empty());
451 }
452
453 #[test]
454 fn f018_sample_rate_mismatch() {
455 let mut emulator_16k = AudioEmulator::with_config(
457 AudioSource::SineWave {
458 frequency: 440.0,
459 amplitude: 1.0,
460 },
461 AudioEmulatorConfig {
462 sample_rate: 16000,
463 ..Default::default()
464 },
465 );
466 let mut emulator_48k = AudioEmulator::with_config(
467 AudioSource::SineWave {
468 frequency: 440.0,
469 amplitude: 1.0,
470 },
471 AudioEmulatorConfig {
472 sample_rate: 48000,
473 ..Default::default()
474 },
475 );
476 let samples_16k = emulator_16k.generate_samples(0.1);
477 let samples_48k = emulator_48k.generate_samples(0.1);
478 assert!(samples_48k.len() >= samples_16k.len() * 2);
480 }
481
482 #[test]
483 fn f019_permission_denied_mock() {
484 let emulator = AudioEmulator::new(AudioSource::Silence {
487 noise_floor_db: -100.0,
488 });
489 let mock_js = emulator.generate_mock_js(&[]);
491 assert!(mock_js.contains("getUserMedia"));
492 assert!(mock_js.contains("audio"));
493 }
494
495 #[test]
496 fn f020_ultrasonic_filtered() {
497 let mut emulator = AudioEmulator::with_config(
499 AudioSource::SineWave {
500 frequency: 50000.0, amplitude: 1.0,
502 },
503 AudioEmulatorConfig {
504 sample_rate: 16000,
505 ..Default::default()
506 },
507 );
508 let samples = emulator.generate_samples(0.1);
509 assert!(!samples.is_empty());
511 assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
512 }
513
514 #[test]
519 fn f021_zero_hz_sine() {
520 let mut emulator = AudioEmulator::new(AudioSource::SineWave {
522 frequency: 0.0,
523 amplitude: 1.0,
524 });
525 let samples = emulator.generate_samples(0.1);
526 assert!(!samples.is_empty());
527 }
529
530 #[test]
531 fn f022_negative_amplitude_handled() {
532 let mut emulator = AudioEmulator::new(AudioSource::SineWave {
534 frequency: 440.0,
535 amplitude: -1.0,
536 });
537 let samples = emulator.generate_samples(0.1);
538 assert!(samples.iter().all(|&s| s.abs() < 0.001));
540 }
541
542 #[test]
543 fn f023_amplitude_clamped() {
544 let mut emulator = AudioEmulator::new(AudioSource::SineWave {
546 frequency: 440.0,
547 amplitude: 5.0,
548 });
549 let samples = emulator.generate_samples(0.1);
550 assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
552 }
553
554 #[test]
555 fn f024_speech_pattern_no_harmonics() {
556 let mut emulator = AudioEmulator::new(AudioSource::SpeechPattern {
558 fundamental_hz: 150.0,
559 harmonics: vec![],
560 variation_hz: 20.0,
561 });
562 let samples = emulator.generate_samples(0.1);
563 assert!(!samples.is_empty());
564 assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
566 }
567
568 #[test]
569 fn f025_samples_callback_empty() {
570 let mut emulator = AudioEmulator::new(AudioSource::Samples {
572 data: vec![],
573 sample_rate: 16000,
574 loop_playback: false,
575 });
576 let samples = emulator.generate_samples(0.1);
577 assert!(samples.iter().all(|&s| s.abs() < f32::EPSILON));
578 }
579
580 #[test]
585 fn f026_pure_silence() {
586 let mut emulator = AudioEmulator::new(AudioSource::Silence {
588 noise_floor_db: -100.0,
589 });
590 let samples = emulator.generate_samples(0.1);
591 let rms = calculate_rms(&samples);
592 assert!(rms < 0.0001, "Silence RMS too high: {rms}");
593 }
594
595 #[test]
596 fn f027_white_noise_not_silent() {
597 let mut emulator = AudioEmulator::new(AudioSource::WhiteNoise { amplitude: 0.5 });
599 let samples = emulator.generate_samples(0.1);
600 let rms = calculate_rms(&samples);
601 assert!(rms > 0.1, "White noise RMS too low: {rms}");
602 }
603
604 #[test]
605 fn f028_speech_threshold_boundary() {
606 let mut emulator = AudioEmulator::new(AudioSource::SpeechPattern {
608 fundamental_hz: 150.0,
609 harmonics: vec![0.5, 0.3, 0.2],
610 variation_hz: 10.0,
611 });
612 let samples = emulator.generate_samples(0.5);
613 let rms = calculate_rms(&samples);
614 assert!(rms > 0.1 && rms < 1.0, "Speech RMS out of range: {rms}");
616 }
617
618 #[test]
623 fn test_sine_wave_generation() {
624 let mut emulator = AudioEmulator::with_config(
625 AudioSource::SineWave {
626 frequency: 440.0,
627 amplitude: 1.0,
628 },
629 AudioEmulatorConfig {
630 sample_rate: 44100,
631 ..Default::default()
632 },
633 );
634
635 let samples = emulator.generate_samples(0.01); assert_eq!(samples.len(), 441); for &sample in &samples {
640 assert!((-1.0..=1.0).contains(&sample));
641 }
642
643 let zero_crossings: usize = samples.windows(2).filter(|w| w[0] * w[1] < 0.0).count();
645 assert!((7..=11).contains(&zero_crossings));
646 }
647
648 #[test]
649 fn test_speech_pattern_generation() {
650 let mut emulator = AudioEmulator::new(AudioSource::SpeechPattern {
651 fundamental_hz: 150.0,
652 harmonics: vec![0.5, 0.3, 0.2, 0.1],
653 variation_hz: 20.0,
654 });
655
656 let samples = emulator.generate_samples(1.0);
657 assert_eq!(samples.len(), 16000); let zero_crossings: usize = samples.windows(2).filter(|w| w[0] * w[1] < 0.0).count();
661 assert!(zero_crossings > 200, "Too few zero crossings for speech");
662 }
663
664 #[test]
665 fn test_deterministic_noise() {
666 let mut emulator1 = AudioEmulator::new(AudioSource::WhiteNoise { amplitude: 1.0 });
668 let mut emulator2 = AudioEmulator::new(AudioSource::WhiteNoise { amplitude: 1.0 });
669
670 let samples1 = emulator1.generate_samples(0.1);
671 let samples2 = emulator2.generate_samples(0.1);
672
673 assert_eq!(samples1, samples2);
674 }
675
676 #[test]
677 fn test_reset() {
678 let mut emulator = AudioEmulator::new(AudioSource::SineWave {
679 frequency: 440.0,
680 amplitude: 1.0,
681 });
682
683 let samples1 = emulator.generate_samples(0.1);
684 emulator.reset();
685 let samples2 = emulator.generate_samples(0.1);
686
687 assert_eq!(samples1, samples2);
689 }
690
691 #[test]
692 fn test_sample_counter() {
693 let mut emulator = AudioEmulator::new(AudioSource::Silence {
694 noise_floor_db: -60.0,
695 });
696 assert_eq!(emulator.samples_generated(), 0);
697
698 let _ = emulator.generate_n_samples(1000);
699 assert_eq!(emulator.samples_generated(), 1000);
700
701 let _ = emulator.generate_n_samples(500);
702 assert_eq!(emulator.samples_generated(), 1500);
703 }
704
705 #[test]
706 fn test_samples_source_with_loop() {
707 let data = vec![0.1, 0.2, 0.3, 0.4];
708 let mut emulator = AudioEmulator::new(AudioSource::Samples {
709 data,
710 sample_rate: 16000,
711 loop_playback: true,
712 });
713
714 let samples = emulator.generate_n_samples(10);
715 assert_eq!(samples[0], 0.1);
716 assert_eq!(samples[3], 0.4);
717 assert_eq!(samples[4], 0.1); assert_eq!(samples[7], 0.4);
719 }
720
721 #[test]
722 fn test_samples_source_without_loop() {
723 let data = vec![0.1, 0.2, 0.3];
724 let mut emulator = AudioEmulator::new(AudioSource::Samples {
725 data,
726 sample_rate: 16000,
727 loop_playback: false,
728 });
729
730 let samples = emulator.generate_n_samples(6);
731 assert_eq!(samples[0], 0.1);
732 assert_eq!(samples[2], 0.3);
733 assert!(samples[3].abs() < f32::EPSILON); }
735
736 #[test]
737 fn test_mock_js_generation() {
738 let emulator = AudioEmulator::new(AudioSource::Silence {
739 noise_floor_db: -60.0,
740 });
741 let samples = vec![0.1, 0.2, 0.3];
742 let js = emulator.generate_mock_js(&samples);
743
744 assert!(js.contains("mockSamples"));
745 assert!(js.contains("sampleRate"));
746 assert!(js.contains("getUserMedia"));
747 assert!(js.contains("__PROBAR_AUDIO_EMULATOR__"));
748 }
749
750 #[test]
751 fn test_default_config() {
752 let config = AudioEmulatorConfig::default();
753 assert_eq!(config.sample_rate, 16000);
754 assert_eq!(config.channels, 1);
755 assert_eq!(config.buffer_size, 1024);
756 }
757
758 fn calculate_rms(samples: &[f32]) -> f32 {
760 if samples.is_empty() {
761 return 0.0;
762 }
763 let sum_squares: f32 = samples.iter().map(|&s| s * s).sum();
764 (sum_squares / samples.len() as f32).sqrt()
765 }
766
767 #[test]
772 fn test_audio_source_default() {
773 let source = AudioSource::default();
775 match source {
776 AudioSource::Silence { noise_floor_db } => {
777 assert!((noise_floor_db - (-60.0)).abs() < f32::EPSILON);
778 }
779 _ => panic!("Default should be Silence variant"),
780 }
781 }
782
783 #[test]
784 fn test_audio_emulator_error_display_injection_failed() {
785 let error = AudioEmulatorError::InjectionFailed("test error".to_string());
787 let display = format!("{error}");
788 assert_eq!(display, "Audio injection failed: test error");
789 }
790
791 #[test]
792 fn test_audio_emulator_error_display_context_not_available() {
793 let error = AudioEmulatorError::ContextNotAvailable;
795 let display = format!("{error}");
796 assert_eq!(display, "Audio context not available");
797 }
798
799 #[test]
800 fn test_audio_emulator_error_display_invalid_config() {
801 let error = AudioEmulatorError::InvalidConfig("bad config".to_string());
803 let display = format!("{error}");
804 assert_eq!(display, "Invalid audio config: bad config");
805 }
806
807 #[test]
808 fn test_audio_emulator_error_is_error_trait() {
809 let error: Box<dyn std::error::Error> = Box::new(AudioEmulatorError::ContextNotAvailable);
811 assert!(error.to_string().contains("context"));
813 }
814
815 #[test]
816 fn test_sample_rate_accessor() {
817 let emulator = AudioEmulator::with_config(
819 AudioSource::Silence {
820 noise_floor_db: -60.0,
821 },
822 AudioEmulatorConfig {
823 sample_rate: 44100,
824 channels: 2,
825 buffer_size: 512,
826 },
827 );
828 assert_eq!(emulator.sample_rate(), 44100);
829 }
830
831 #[test]
832 fn test_sine_wave_phase_wrap() {
833 let mut emulator = AudioEmulator::with_config(
836 AudioSource::SineWave {
837 frequency: 1000.0, amplitude: 1.0,
839 },
840 AudioEmulatorConfig {
841 sample_rate: 8000, ..Default::default()
843 },
844 );
845 let samples = emulator.generate_n_samples(100);
848 assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
850 }
851
852 #[test]
853 fn test_speech_pattern_harmonic_exceeds_nyquist() {
854 let mut emulator = AudioEmulator::with_config(
856 AudioSource::SpeechPattern {
857 fundamental_hz: 3000.0, harmonics: vec![0.5, 0.3, 0.2, 0.1, 0.1, 0.1], variation_hz: 0.0,
860 },
861 AudioEmulatorConfig {
862 sample_rate: 16000, ..Default::default()
864 },
865 );
866 let samples = emulator.generate_n_samples(1000);
868 assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
870 }
871
872 #[test]
873 fn test_speech_pattern_phase_wrap() {
874 let mut emulator = AudioEmulator::with_config(
876 AudioSource::SpeechPattern {
877 fundamental_hz: 2000.0,
878 harmonics: vec![0.3],
879 variation_hz: 10.0,
880 },
881 AudioEmulatorConfig {
882 sample_rate: 8000,
883 ..Default::default()
884 },
885 );
886 let samples = emulator.generate_n_samples(500);
888 assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
889 }
890
891 #[test]
892 fn test_speech_pattern_variation_clamping() {
893 let mut emulator = AudioEmulator::new(AudioSource::SpeechPattern {
895 fundamental_hz: 100.0,
896 harmonics: vec![0.5],
897 variation_hz: 1000.0, });
899 let samples = emulator.generate_n_samples(1000);
900 assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
902 }
903
904 #[test]
905 fn test_speech_pattern_low_fundamental_clamped() {
906 let mut emulator = AudioEmulator::new(AudioSource::SpeechPattern {
908 fundamental_hz: 5.0, harmonics: vec![0.5, 0.3],
910 variation_hz: 2.0,
911 });
912 let samples = emulator.generate_n_samples(1000);
913 assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
914 }
915
916 #[test]
917 fn test_speech_pattern_high_fundamental_clamped() {
918 let mut emulator = AudioEmulator::with_config(
920 AudioSource::SpeechPattern {
921 fundamental_hz: 20000.0, harmonics: vec![0.5],
923 variation_hz: 10.0,
924 },
925 AudioEmulatorConfig {
926 sample_rate: 16000,
927 ..Default::default()
928 },
929 );
930 let samples = emulator.generate_n_samples(1000);
931 assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
932 }
933
934 #[test]
935 fn test_speech_pattern_harmonic_amplitude_clamping() {
936 let mut emulator = AudioEmulator::new(AudioSource::SpeechPattern {
938 fundamental_hz: 150.0,
939 harmonics: vec![2.0, -0.5, 1.5], variation_hz: 10.0,
941 });
942 let samples = emulator.generate_n_samples(1000);
943 assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
945 }
946
947 #[test]
948 fn test_silence_noise_floor_clamping_high() {
949 let mut emulator = AudioEmulator::new(AudioSource::Silence {
951 noise_floor_db: 10.0, });
953 let samples = emulator.generate_n_samples(1000);
954 assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
956 }
957
958 #[test]
959 fn test_silence_noise_floor_clamping_low() {
960 let mut emulator = AudioEmulator::new(AudioSource::Silence {
962 noise_floor_db: -200.0, });
964 let samples = emulator.generate_n_samples(1000);
965 let rms = calculate_rms(&samples);
966 assert!(rms < 0.0001);
968 }
969
970 #[test]
971 fn test_samples_source_clamping_positive() {
972 let data = vec![1.5, 2.0, 0.5, -0.5];
974 let mut emulator = AudioEmulator::new(AudioSource::Samples {
975 data,
976 sample_rate: 16000,
977 loop_playback: false,
978 });
979 let samples = emulator.generate_n_samples(4);
980 assert_eq!(samples[0], 1.0); assert_eq!(samples[1], 1.0); assert_eq!(samples[2], 0.5); assert_eq!(samples[3], -0.5); }
985
986 #[test]
987 fn test_samples_source_clamping_negative() {
988 let data = vec![-1.5, -2.0, 0.5];
990 let mut emulator = AudioEmulator::new(AudioSource::Samples {
991 data,
992 sample_rate: 16000,
993 loop_playback: false,
994 });
995 let samples = emulator.generate_n_samples(3);
996 assert_eq!(samples[0], -1.0); assert_eq!(samples[1], -1.0); assert_eq!(samples[2], 0.5); }
1000
1001 #[test]
1002 fn test_samples_source_loop_with_clamping() {
1003 let data = vec![1.5, -1.5]; let mut emulator = AudioEmulator::new(AudioSource::Samples {
1006 data,
1007 sample_rate: 16000,
1008 loop_playback: true,
1009 });
1010 let samples = emulator.generate_n_samples(6);
1011 assert_eq!(samples[0], 1.0); assert_eq!(samples[1], -1.0); assert_eq!(samples[2], 1.0); assert_eq!(samples[3], -1.0); }
1016
1017 #[test]
1018 fn test_white_noise_zero_amplitude() {
1019 let mut emulator = AudioEmulator::new(AudioSource::WhiteNoise { amplitude: 0.0 });
1021 let samples = emulator.generate_n_samples(1000);
1022 assert!(samples.iter().all(|&s| s.abs() < f32::EPSILON));
1024 }
1025
1026 #[test]
1027 fn test_white_noise_negative_amplitude_clamped() {
1028 let mut emulator = AudioEmulator::new(AudioSource::WhiteNoise { amplitude: -0.5 });
1030 let samples = emulator.generate_n_samples(1000);
1031 assert!(samples.iter().all(|&s| s.abs() < f32::EPSILON));
1033 }
1034
1035 #[test]
1036 fn test_white_noise_high_amplitude_clamped() {
1037 let mut emulator = AudioEmulator::new(AudioSource::WhiteNoise { amplitude: 5.0 });
1039 let samples = emulator.generate_n_samples(1000);
1040 assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
1042 }
1043
1044 #[test]
1045 fn test_rng_determinism_after_reset() {
1046 let mut emulator = AudioEmulator::new(AudioSource::WhiteNoise { amplitude: 1.0 });
1048 let samples1 = emulator.generate_n_samples(100);
1049 emulator.reset();
1050 let samples2 = emulator.generate_n_samples(100);
1051 assert_eq!(samples1, samples2);
1053 }
1054
1055 #[test]
1056 fn test_generate_mock_js_with_many_samples() {
1057 let emulator = AudioEmulator::new(AudioSource::Silence {
1059 noise_floor_db: -60.0,
1060 });
1061 let samples: Vec<f32> = (0..100).map(|i| (i as f32) * 0.01).collect();
1062 let js = emulator.generate_mock_js(&samples);
1063
1064 assert!(js.contains("0.990000")); assert!(js.contains("Float32Array"));
1067 }
1068
1069 #[test]
1070 fn test_config_custom_channels_and_buffer() {
1071 let config = AudioEmulatorConfig {
1073 sample_rate: 48000,
1074 channels: 2,
1075 buffer_size: 2048,
1076 };
1077 assert_eq!(config.sample_rate, 48000);
1078 assert_eq!(config.channels, 2);
1079 assert_eq!(config.buffer_size, 2048);
1080 }
1081
1082 #[test]
1083 fn test_audio_source_clone() {
1084 let source = AudioSource::SpeechPattern {
1086 fundamental_hz: 150.0,
1087 harmonics: vec![0.5, 0.3],
1088 variation_hz: 20.0,
1089 };
1090 let cloned = source;
1091 match cloned {
1092 AudioSource::SpeechPattern {
1093 fundamental_hz,
1094 harmonics,
1095 variation_hz,
1096 } => {
1097 assert!((fundamental_hz - 150.0).abs() < f32::EPSILON);
1098 assert_eq!(harmonics, vec![0.5, 0.3]);
1099 assert!((variation_hz - 20.0).abs() < f32::EPSILON);
1100 }
1101 _ => panic!("Clone should preserve variant"),
1102 }
1103 }
1104
1105 #[test]
1106 fn test_audio_emulator_config_clone() {
1107 let config = AudioEmulatorConfig {
1109 sample_rate: 22050,
1110 channels: 2,
1111 buffer_size: 512,
1112 };
1113 let cloned = config;
1114 assert_eq!(cloned.sample_rate, 22050);
1115 assert_eq!(cloned.channels, 2);
1116 assert_eq!(cloned.buffer_size, 512);
1117 }
1118
1119 #[test]
1120 fn test_audio_emulator_clone() {
1121 let mut emulator = AudioEmulator::new(AudioSource::SineWave {
1123 frequency: 440.0,
1124 amplitude: 0.8,
1125 });
1126 let _ = emulator.generate_n_samples(100); let cloned = emulator.clone();
1129 assert_eq!(cloned.samples_generated(), 100);
1130 assert_eq!(cloned.sample_rate(), 16000);
1131 }
1132
1133 #[test]
1134 fn test_audio_emulator_error_clone() {
1135 let error = AudioEmulatorError::InjectionFailed("test".to_string());
1137 let cloned = error;
1138 match cloned {
1139 AudioEmulatorError::InjectionFailed(msg) => assert_eq!(msg, "test"),
1140 _ => panic!("Clone should preserve variant"),
1141 }
1142 }
1143
1144 #[test]
1145 fn test_audio_source_debug() {
1146 let source = AudioSource::SineWave {
1148 frequency: 440.0,
1149 amplitude: 1.0,
1150 };
1151 let debug_str = format!("{source:?}");
1152 assert!(debug_str.contains("SineWave"));
1153 assert!(debug_str.contains("440"));
1154 }
1155
1156 #[test]
1157 fn test_audio_emulator_config_debug() {
1158 let config = AudioEmulatorConfig::default();
1160 let debug_str = format!("{config:?}");
1161 assert!(debug_str.contains("16000"));
1162 }
1163
1164 #[test]
1165 fn test_audio_emulator_debug() {
1166 let emulator = AudioEmulator::new(AudioSource::default());
1168 let debug_str = format!("{emulator:?}");
1169 assert!(debug_str.contains("AudioEmulator"));
1170 }
1171
1172 #[test]
1173 fn test_audio_emulator_error_debug() {
1174 let error = AudioEmulatorError::ContextNotAvailable;
1176 let debug_str = format!("{error:?}");
1177 assert!(debug_str.contains("ContextNotAvailable"));
1178 }
1179
1180 #[test]
1181 fn test_speech_pattern_normalization() {
1182 let mut emulator = AudioEmulator::new(AudioSource::SpeechPattern {
1185 fundamental_hz: 150.0,
1186 harmonics: vec![0.8, 0.8, 0.8, 0.8], variation_hz: 0.0,
1188 });
1189 let samples = emulator.generate_n_samples(1000);
1190 assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
1192 }
1193
1194 #[test]
1195 fn test_sine_wave_very_low_frequency() {
1196 let mut emulator = AudioEmulator::new(AudioSource::SineWave {
1198 frequency: 0.0001, amplitude: 1.0,
1200 });
1201 let samples = emulator.generate_n_samples(100);
1202 assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
1204 }
1205
1206 #[test]
1207 fn test_generate_samples_fractional_duration() {
1208 let mut emulator = AudioEmulator::with_config(
1210 AudioSource::Silence {
1211 noise_floor_db: -60.0,
1212 },
1213 AudioEmulatorConfig {
1214 sample_rate: 1000, ..Default::default()
1216 },
1217 );
1218 let samples = emulator.generate_samples(0.0015);
1220 assert_eq!(samples.len(), 1);
1221 }
1222}