use std::f32::consts::PI;
#[derive(Debug, Clone)]
pub enum AudioSource {
SineWave {
frequency: f32,
amplitude: f32,
},
SpeechPattern {
fundamental_hz: f32,
harmonics: Vec<f32>,
variation_hz: f32,
},
Silence {
noise_floor_db: f32,
},
WhiteNoise {
amplitude: f32,
},
Samples {
data: Vec<f32>,
sample_rate: u32,
loop_playback: bool,
},
}
impl Default for AudioSource {
fn default() -> Self {
Self::Silence {
noise_floor_db: -60.0,
}
}
}
#[derive(Debug, Clone)]
pub struct AudioEmulatorConfig {
pub sample_rate: u32,
pub channels: u8,
pub buffer_size: usize,
}
impl Default for AudioEmulatorConfig {
fn default() -> Self {
Self {
sample_rate: 16000, channels: 1, buffer_size: 1024,
}
}
}
#[derive(Debug, Clone)]
pub struct AudioEmulator {
source: AudioSource,
config: AudioEmulatorConfig,
phase: f32,
sample_count: u64,
rng_state: u64,
}
impl AudioEmulator {
#[must_use]
pub fn new(source: AudioSource) -> Self {
Self::with_config(source, AudioEmulatorConfig::default())
}
#[must_use]
pub fn with_config(source: AudioSource, config: AudioEmulatorConfig) -> Self {
Self {
source,
config,
phase: 0.0,
sample_count: 0,
rng_state: 0x853c_49e6_748f_ea9b, }
}
#[must_use]
pub fn sample_rate(&self) -> u32 {
self.config.sample_rate
}
#[must_use]
pub fn samples_generated(&self) -> u64 {
self.sample_count
}
#[must_use]
pub fn generate_samples(&mut self, duration_seconds: f32) -> Vec<f32> {
let num_samples = (duration_seconds * self.config.sample_rate as f32) as usize;
self.generate_n_samples(num_samples)
}
#[must_use]
pub fn generate_n_samples(&mut self, num_samples: usize) -> Vec<f32> {
let mut samples = Vec::with_capacity(num_samples);
let sample_rate = self.config.sample_rate as f32;
for _ in 0..num_samples {
let sample = self.generate_single_sample(sample_rate);
samples.push(sample);
self.sample_count += 1;
}
samples
}
fn generate_single_sample(&mut self, sample_rate: f32) -> f32 {
match &self.source {
AudioSource::SineWave {
frequency,
amplitude,
} => {
let freq = frequency.clamp(0.001, sample_rate / 2.0);
let amp = amplitude.clamp(0.0, 1.0);
let sample = (self.phase * 2.0 * PI).sin() * amp;
self.phase += freq / sample_rate;
if self.phase >= 1.0 {
self.phase -= 1.0;
}
sample
}
AudioSource::SpeechPattern {
fundamental_hz,
harmonics,
variation_hz,
} => {
let freq = fundamental_hz.clamp(20.0, sample_rate / 2.0);
let var = variation_hz.clamp(0.0, freq / 2.0);
let time = self.sample_count as f32 / sample_rate;
let freq_with_variation = freq + var * (time * 5.0).sin();
let mut sample = (self.phase * 2.0 * PI).sin();
for (i, &harmonic_amp) in harmonics.iter().enumerate() {
let harmonic_num = (i + 2) as f32;
let harmonic_freq = freq_with_variation * harmonic_num;
if harmonic_freq < sample_rate / 2.0 {
let harmonic_phase = self.phase * harmonic_num;
sample += (harmonic_phase * 2.0 * PI).sin() * harmonic_amp.clamp(0.0, 1.0);
}
}
let total_amp = 1.0 + harmonics.iter().sum::<f32>();
sample /= total_amp.max(1.0);
self.phase += freq_with_variation / sample_rate;
if self.phase >= 1.0 {
self.phase -= 1.0;
}
sample.clamp(-1.0, 1.0)
}
AudioSource::Silence { noise_floor_db } => {
let amp = 10.0_f32.powf(noise_floor_db.clamp(-100.0, 0.0) / 20.0);
let noise = self.next_random_f32() * 2.0 - 1.0;
noise * amp
}
AudioSource::WhiteNoise { amplitude } => {
let amp = amplitude.clamp(0.0, 1.0);
let noise = self.next_random_f32() * 2.0 - 1.0;
noise * amp
}
AudioSource::Samples {
data,
sample_rate: _src_rate,
loop_playback,
} => {
if data.is_empty() {
return 0.0;
}
let idx = self.sample_count as usize;
if idx < data.len() {
data[idx].clamp(-1.0, 1.0)
} else if *loop_playback {
data[idx % data.len()].clamp(-1.0, 1.0)
} else {
0.0
}
}
}
}
fn next_random_f32(&mut self) -> f32 {
self.rng_state ^= self.rng_state << 13;
self.rng_state ^= self.rng_state >> 7;
self.rng_state ^= self.rng_state << 17;
(self.rng_state as f32) / (u64::MAX as f32)
}
pub fn reset(&mut self) {
self.phase = 0.0;
self.sample_count = 0;
self.rng_state = 0x853c_49e6_748f_ea9b;
}
#[must_use]
pub fn generate_mock_js(&self, samples: &[f32]) -> String {
let samples_json: String = samples
.iter()
.map(|s| format!("{s:.6}"))
.collect::<Vec<_>>()
.join(",");
format!(
r#"
(function() {{
const mockSamples = new Float32Array([{samples_json}]);
const sampleRate = {sample_rate};
let sampleIndex = 0;
// Create mock MediaStream
const audioContext = new AudioContext({{ sampleRate: sampleRate }});
const bufferSize = 1024;
const scriptNode = audioContext.createScriptProcessor(bufferSize, 1, 1);
scriptNode.onaudioprocess = function(e) {{
const output = e.outputBuffer.getChannelData(0);
for (let i = 0; i < bufferSize; i++) {{
if (sampleIndex < mockSamples.length) {{
output[i] = mockSamples[sampleIndex++];
}} else {{
output[i] = 0;
}}
}}
}};
const dest = audioContext.createMediaStreamDestination();
scriptNode.connect(dest);
scriptNode.connect(audioContext.destination);
// Override getUserMedia
const originalGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);
navigator.mediaDevices.getUserMedia = async function(constraints) {{
if (constraints.audio) {{
return dest.stream;
}}
return originalGetUserMedia(constraints);
}};
window.__PROBAR_AUDIO_EMULATOR__ = {{
sampleIndex: () => sampleIndex,
reset: () => {{ sampleIndex = 0; }},
context: audioContext
}};
}})();
"#,
samples_json = samples_json,
sample_rate = self.config.sample_rate
)
}
#[cfg(feature = "browser")]
pub async fn inject_cdp(
&mut self,
page: &chromiumoxide::Page,
duration_seconds: f32,
) -> Result<(), AudioEmulatorError> {
let samples = self.generate_samples(duration_seconds);
let js = self.generate_mock_js(&samples);
page.evaluate(js.as_str()).await.map_err(|e| {
AudioEmulatorError::InjectionFailed(format!("CDP injection failed: {e}"))
})?;
Ok(())
}
#[cfg(feature = "browser")]
pub async fn is_active_cdp(page: &chromiumoxide::Page) -> Result<bool, AudioEmulatorError> {
let result: bool = page
.evaluate("typeof window.__PROBAR_AUDIO_EMULATOR__ !== 'undefined'")
.await
.map_err(|e| AudioEmulatorError::InjectionFailed(format!("CDP check failed: {e}")))?
.into_value()
.unwrap_or(false);
Ok(result)
}
#[cfg(feature = "browser")]
pub async fn get_sample_index_cdp(
page: &chromiumoxide::Page,
) -> Result<u64, AudioEmulatorError> {
let result: f64 = page
.evaluate("window.__PROBAR_AUDIO_EMULATOR__?.sampleIndex() ?? 0")
.await
.map_err(|e| AudioEmulatorError::InjectionFailed(format!("CDP query failed: {e}")))?
.into_value()
.unwrap_or(0.0);
Ok(result as u64)
}
}
#[derive(Debug, Clone)]
pub enum AudioEmulatorError {
InjectionFailed(String),
ContextNotAvailable,
InvalidConfig(String),
}
impl std::fmt::Display for AudioEmulatorError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InjectionFailed(msg) => write!(f, "Audio injection failed: {msg}"),
Self::ContextNotAvailable => write!(f, "Audio context not available"),
Self::InvalidConfig(msg) => write!(f, "Invalid audio config: {msg}"),
}
}
}
impl std::error::Error for AudioEmulatorError {}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::float_cmp)]
mod tests {
use super::*;
#[test]
fn f016_zero_length_audio_no_crash() {
let mut emulator = AudioEmulator::new(AudioSource::Silence {
noise_floor_db: -60.0,
});
let samples = emulator.generate_samples(0.0);
assert!(samples.is_empty());
}
#[test]
fn f017_context_suspended_handling() {
let mut emulator = AudioEmulator::new(AudioSource::SineWave {
frequency: 440.0,
amplitude: 0.5,
});
let samples = emulator.generate_samples(0.1);
assert!(!samples.is_empty());
emulator.reset();
let samples_after = emulator.generate_samples(0.1);
assert!(!samples_after.is_empty());
}
#[test]
fn f018_sample_rate_mismatch() {
let mut emulator_16k = AudioEmulator::with_config(
AudioSource::SineWave {
frequency: 440.0,
amplitude: 1.0,
},
AudioEmulatorConfig {
sample_rate: 16000,
..Default::default()
},
);
let mut emulator_48k = AudioEmulator::with_config(
AudioSource::SineWave {
frequency: 440.0,
amplitude: 1.0,
},
AudioEmulatorConfig {
sample_rate: 48000,
..Default::default()
},
);
let samples_16k = emulator_16k.generate_samples(0.1);
let samples_48k = emulator_48k.generate_samples(0.1);
assert!(samples_48k.len() >= samples_16k.len() * 2);
}
#[test]
fn f019_permission_denied_mock() {
let emulator = AudioEmulator::new(AudioSource::Silence {
noise_floor_db: -100.0,
});
let mock_js = emulator.generate_mock_js(&[]);
assert!(mock_js.contains("getUserMedia"));
assert!(mock_js.contains("audio"));
}
#[test]
fn f020_ultrasonic_filtered() {
let mut emulator = AudioEmulator::with_config(
AudioSource::SineWave {
frequency: 50000.0, amplitude: 1.0,
},
AudioEmulatorConfig {
sample_rate: 16000,
..Default::default()
},
);
let samples = emulator.generate_samples(0.1);
assert!(!samples.is_empty());
assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
}
#[test]
fn f021_zero_hz_sine() {
let mut emulator = AudioEmulator::new(AudioSource::SineWave {
frequency: 0.0,
amplitude: 1.0,
});
let samples = emulator.generate_samples(0.1);
assert!(!samples.is_empty());
}
#[test]
fn f022_negative_amplitude_handled() {
let mut emulator = AudioEmulator::new(AudioSource::SineWave {
frequency: 440.0,
amplitude: -1.0,
});
let samples = emulator.generate_samples(0.1);
assert!(samples.iter().all(|&s| s.abs() < 0.001));
}
#[test]
fn f023_amplitude_clamped() {
let mut emulator = AudioEmulator::new(AudioSource::SineWave {
frequency: 440.0,
amplitude: 5.0,
});
let samples = emulator.generate_samples(0.1);
assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
}
#[test]
fn f024_speech_pattern_no_harmonics() {
let mut emulator = AudioEmulator::new(AudioSource::SpeechPattern {
fundamental_hz: 150.0,
harmonics: vec![],
variation_hz: 20.0,
});
let samples = emulator.generate_samples(0.1);
assert!(!samples.is_empty());
assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
}
#[test]
fn f025_samples_callback_empty() {
let mut emulator = AudioEmulator::new(AudioSource::Samples {
data: vec![],
sample_rate: 16000,
loop_playback: false,
});
let samples = emulator.generate_samples(0.1);
assert!(samples.iter().all(|&s| s.abs() < f32::EPSILON));
}
#[test]
fn f026_pure_silence() {
let mut emulator = AudioEmulator::new(AudioSource::Silence {
noise_floor_db: -100.0,
});
let samples = emulator.generate_samples(0.1);
let rms = calculate_rms(&samples);
assert!(rms < 0.0001, "Silence RMS too high: {rms}");
}
#[test]
fn f027_white_noise_not_silent() {
let mut emulator = AudioEmulator::new(AudioSource::WhiteNoise { amplitude: 0.5 });
let samples = emulator.generate_samples(0.1);
let rms = calculate_rms(&samples);
assert!(rms > 0.1, "White noise RMS too low: {rms}");
}
#[test]
fn f028_speech_threshold_boundary() {
let mut emulator = AudioEmulator::new(AudioSource::SpeechPattern {
fundamental_hz: 150.0,
harmonics: vec![0.5, 0.3, 0.2],
variation_hz: 10.0,
});
let samples = emulator.generate_samples(0.5);
let rms = calculate_rms(&samples);
assert!(rms > 0.1 && rms < 1.0, "Speech RMS out of range: {rms}");
}
#[test]
fn test_sine_wave_generation() {
let mut emulator = AudioEmulator::with_config(
AudioSource::SineWave {
frequency: 440.0,
amplitude: 1.0,
},
AudioEmulatorConfig {
sample_rate: 44100,
..Default::default()
},
);
let samples = emulator.generate_samples(0.01); assert_eq!(samples.len(), 441);
for &sample in &samples {
assert!((-1.0..=1.0).contains(&sample));
}
let zero_crossings: usize = samples.windows(2).filter(|w| w[0] * w[1] < 0.0).count();
assert!((7..=11).contains(&zero_crossings));
}
#[test]
fn test_speech_pattern_generation() {
let mut emulator = AudioEmulator::new(AudioSource::SpeechPattern {
fundamental_hz: 150.0,
harmonics: vec![0.5, 0.3, 0.2, 0.1],
variation_hz: 20.0,
});
let samples = emulator.generate_samples(1.0);
assert_eq!(samples.len(), 16000);
let zero_crossings: usize = samples.windows(2).filter(|w| w[0] * w[1] < 0.0).count();
assert!(zero_crossings > 200, "Too few zero crossings for speech");
}
#[test]
fn test_deterministic_noise() {
let mut emulator1 = AudioEmulator::new(AudioSource::WhiteNoise { amplitude: 1.0 });
let mut emulator2 = AudioEmulator::new(AudioSource::WhiteNoise { amplitude: 1.0 });
let samples1 = emulator1.generate_samples(0.1);
let samples2 = emulator2.generate_samples(0.1);
assert_eq!(samples1, samples2);
}
#[test]
fn test_reset() {
let mut emulator = AudioEmulator::new(AudioSource::SineWave {
frequency: 440.0,
amplitude: 1.0,
});
let samples1 = emulator.generate_samples(0.1);
emulator.reset();
let samples2 = emulator.generate_samples(0.1);
assert_eq!(samples1, samples2);
}
#[test]
fn test_sample_counter() {
let mut emulator = AudioEmulator::new(AudioSource::Silence {
noise_floor_db: -60.0,
});
assert_eq!(emulator.samples_generated(), 0);
let _ = emulator.generate_n_samples(1000);
assert_eq!(emulator.samples_generated(), 1000);
let _ = emulator.generate_n_samples(500);
assert_eq!(emulator.samples_generated(), 1500);
}
#[test]
fn test_samples_source_with_loop() {
let data = vec![0.1, 0.2, 0.3, 0.4];
let mut emulator = AudioEmulator::new(AudioSource::Samples {
data,
sample_rate: 16000,
loop_playback: true,
});
let samples = emulator.generate_n_samples(10);
assert_eq!(samples[0], 0.1);
assert_eq!(samples[3], 0.4);
assert_eq!(samples[4], 0.1); assert_eq!(samples[7], 0.4);
}
#[test]
fn test_samples_source_without_loop() {
let data = vec![0.1, 0.2, 0.3];
let mut emulator = AudioEmulator::new(AudioSource::Samples {
data,
sample_rate: 16000,
loop_playback: false,
});
let samples = emulator.generate_n_samples(6);
assert_eq!(samples[0], 0.1);
assert_eq!(samples[2], 0.3);
assert!(samples[3].abs() < f32::EPSILON); }
#[test]
fn test_mock_js_generation() {
let emulator = AudioEmulator::new(AudioSource::Silence {
noise_floor_db: -60.0,
});
let samples = vec![0.1, 0.2, 0.3];
let js = emulator.generate_mock_js(&samples);
assert!(js.contains("mockSamples"));
assert!(js.contains("sampleRate"));
assert!(js.contains("getUserMedia"));
assert!(js.contains("__PROBAR_AUDIO_EMULATOR__"));
}
#[test]
fn test_default_config() {
let config = AudioEmulatorConfig::default();
assert_eq!(config.sample_rate, 16000);
assert_eq!(config.channels, 1);
assert_eq!(config.buffer_size, 1024);
}
fn calculate_rms(samples: &[f32]) -> f32 {
if samples.is_empty() {
return 0.0;
}
let sum_squares: f32 = samples.iter().map(|&s| s * s).sum();
(sum_squares / samples.len() as f32).sqrt()
}
#[test]
fn test_audio_source_default() {
let source = AudioSource::default();
match source {
AudioSource::Silence { noise_floor_db } => {
assert!((noise_floor_db - (-60.0)).abs() < f32::EPSILON);
}
_ => panic!("Default should be Silence variant"),
}
}
#[test]
fn test_audio_emulator_error_display_injection_failed() {
let error = AudioEmulatorError::InjectionFailed("test error".to_string());
let display = format!("{error}");
assert_eq!(display, "Audio injection failed: test error");
}
#[test]
fn test_audio_emulator_error_display_context_not_available() {
let error = AudioEmulatorError::ContextNotAvailable;
let display = format!("{error}");
assert_eq!(display, "Audio context not available");
}
#[test]
fn test_audio_emulator_error_display_invalid_config() {
let error = AudioEmulatorError::InvalidConfig("bad config".to_string());
let display = format!("{error}");
assert_eq!(display, "Invalid audio config: bad config");
}
#[test]
fn test_audio_emulator_error_is_error_trait() {
let error: Box<dyn std::error::Error> = Box::new(AudioEmulatorError::ContextNotAvailable);
assert!(error.to_string().contains("context"));
}
#[test]
fn test_sample_rate_accessor() {
let emulator = AudioEmulator::with_config(
AudioSource::Silence {
noise_floor_db: -60.0,
},
AudioEmulatorConfig {
sample_rate: 44100,
channels: 2,
buffer_size: 512,
},
);
assert_eq!(emulator.sample_rate(), 44100);
}
#[test]
fn test_sine_wave_phase_wrap() {
let mut emulator = AudioEmulator::with_config(
AudioSource::SineWave {
frequency: 1000.0, amplitude: 1.0,
},
AudioEmulatorConfig {
sample_rate: 8000, ..Default::default()
},
);
let samples = emulator.generate_n_samples(100);
assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
}
#[test]
fn test_speech_pattern_harmonic_exceeds_nyquist() {
let mut emulator = AudioEmulator::with_config(
AudioSource::SpeechPattern {
fundamental_hz: 3000.0, harmonics: vec![0.5, 0.3, 0.2, 0.1, 0.1, 0.1], variation_hz: 0.0,
},
AudioEmulatorConfig {
sample_rate: 16000, ..Default::default()
},
);
let samples = emulator.generate_n_samples(1000);
assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
}
#[test]
fn test_speech_pattern_phase_wrap() {
let mut emulator = AudioEmulator::with_config(
AudioSource::SpeechPattern {
fundamental_hz: 2000.0,
harmonics: vec![0.3],
variation_hz: 10.0,
},
AudioEmulatorConfig {
sample_rate: 8000,
..Default::default()
},
);
let samples = emulator.generate_n_samples(500);
assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
}
#[test]
fn test_speech_pattern_variation_clamping() {
let mut emulator = AudioEmulator::new(AudioSource::SpeechPattern {
fundamental_hz: 100.0,
harmonics: vec![0.5],
variation_hz: 1000.0, });
let samples = emulator.generate_n_samples(1000);
assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
}
#[test]
fn test_speech_pattern_low_fundamental_clamped() {
let mut emulator = AudioEmulator::new(AudioSource::SpeechPattern {
fundamental_hz: 5.0, harmonics: vec![0.5, 0.3],
variation_hz: 2.0,
});
let samples = emulator.generate_n_samples(1000);
assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
}
#[test]
fn test_speech_pattern_high_fundamental_clamped() {
let mut emulator = AudioEmulator::with_config(
AudioSource::SpeechPattern {
fundamental_hz: 20000.0, harmonics: vec![0.5],
variation_hz: 10.0,
},
AudioEmulatorConfig {
sample_rate: 16000,
..Default::default()
},
);
let samples = emulator.generate_n_samples(1000);
assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
}
#[test]
fn test_speech_pattern_harmonic_amplitude_clamping() {
let mut emulator = AudioEmulator::new(AudioSource::SpeechPattern {
fundamental_hz: 150.0,
harmonics: vec![2.0, -0.5, 1.5], variation_hz: 10.0,
});
let samples = emulator.generate_n_samples(1000);
assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
}
#[test]
fn test_silence_noise_floor_clamping_high() {
let mut emulator = AudioEmulator::new(AudioSource::Silence {
noise_floor_db: 10.0, });
let samples = emulator.generate_n_samples(1000);
assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
}
#[test]
fn test_silence_noise_floor_clamping_low() {
let mut emulator = AudioEmulator::new(AudioSource::Silence {
noise_floor_db: -200.0, });
let samples = emulator.generate_n_samples(1000);
let rms = calculate_rms(&samples);
assert!(rms < 0.0001);
}
#[test]
fn test_samples_source_clamping_positive() {
let data = vec![1.5, 2.0, 0.5, -0.5];
let mut emulator = AudioEmulator::new(AudioSource::Samples {
data,
sample_rate: 16000,
loop_playback: false,
});
let samples = emulator.generate_n_samples(4);
assert_eq!(samples[0], 1.0); assert_eq!(samples[1], 1.0); assert_eq!(samples[2], 0.5); assert_eq!(samples[3], -0.5); }
#[test]
fn test_samples_source_clamping_negative() {
let data = vec![-1.5, -2.0, 0.5];
let mut emulator = AudioEmulator::new(AudioSource::Samples {
data,
sample_rate: 16000,
loop_playback: false,
});
let samples = emulator.generate_n_samples(3);
assert_eq!(samples[0], -1.0); assert_eq!(samples[1], -1.0); assert_eq!(samples[2], 0.5); }
#[test]
fn test_samples_source_loop_with_clamping() {
let data = vec![1.5, -1.5]; let mut emulator = AudioEmulator::new(AudioSource::Samples {
data,
sample_rate: 16000,
loop_playback: true,
});
let samples = emulator.generate_n_samples(6);
assert_eq!(samples[0], 1.0); assert_eq!(samples[1], -1.0); assert_eq!(samples[2], 1.0); assert_eq!(samples[3], -1.0); }
#[test]
fn test_white_noise_zero_amplitude() {
let mut emulator = AudioEmulator::new(AudioSource::WhiteNoise { amplitude: 0.0 });
let samples = emulator.generate_n_samples(1000);
assert!(samples.iter().all(|&s| s.abs() < f32::EPSILON));
}
#[test]
fn test_white_noise_negative_amplitude_clamped() {
let mut emulator = AudioEmulator::new(AudioSource::WhiteNoise { amplitude: -0.5 });
let samples = emulator.generate_n_samples(1000);
assert!(samples.iter().all(|&s| s.abs() < f32::EPSILON));
}
#[test]
fn test_white_noise_high_amplitude_clamped() {
let mut emulator = AudioEmulator::new(AudioSource::WhiteNoise { amplitude: 5.0 });
let samples = emulator.generate_n_samples(1000);
assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
}
#[test]
fn test_rng_determinism_after_reset() {
let mut emulator = AudioEmulator::new(AudioSource::WhiteNoise { amplitude: 1.0 });
let samples1 = emulator.generate_n_samples(100);
emulator.reset();
let samples2 = emulator.generate_n_samples(100);
assert_eq!(samples1, samples2);
}
#[test]
fn test_generate_mock_js_with_many_samples() {
let emulator = AudioEmulator::new(AudioSource::Silence {
noise_floor_db: -60.0,
});
let samples: Vec<f32> = (0..100).map(|i| (i as f32) * 0.01).collect();
let js = emulator.generate_mock_js(&samples);
assert!(js.contains("0.990000")); assert!(js.contains("Float32Array"));
}
#[test]
fn test_config_custom_channels_and_buffer() {
let config = AudioEmulatorConfig {
sample_rate: 48000,
channels: 2,
buffer_size: 2048,
};
assert_eq!(config.sample_rate, 48000);
assert_eq!(config.channels, 2);
assert_eq!(config.buffer_size, 2048);
}
#[test]
fn test_audio_source_clone() {
let source = AudioSource::SpeechPattern {
fundamental_hz: 150.0,
harmonics: vec![0.5, 0.3],
variation_hz: 20.0,
};
let cloned = source;
match cloned {
AudioSource::SpeechPattern {
fundamental_hz,
harmonics,
variation_hz,
} => {
assert!((fundamental_hz - 150.0).abs() < f32::EPSILON);
assert_eq!(harmonics, vec![0.5, 0.3]);
assert!((variation_hz - 20.0).abs() < f32::EPSILON);
}
_ => panic!("Clone should preserve variant"),
}
}
#[test]
fn test_audio_emulator_config_clone() {
let config = AudioEmulatorConfig {
sample_rate: 22050,
channels: 2,
buffer_size: 512,
};
let cloned = config;
assert_eq!(cloned.sample_rate, 22050);
assert_eq!(cloned.channels, 2);
assert_eq!(cloned.buffer_size, 512);
}
#[test]
fn test_audio_emulator_clone() {
let mut emulator = AudioEmulator::new(AudioSource::SineWave {
frequency: 440.0,
amplitude: 0.8,
});
let _ = emulator.generate_n_samples(100);
let cloned = emulator.clone();
assert_eq!(cloned.samples_generated(), 100);
assert_eq!(cloned.sample_rate(), 16000);
}
#[test]
fn test_audio_emulator_error_clone() {
let error = AudioEmulatorError::InjectionFailed("test".to_string());
let cloned = error;
match cloned {
AudioEmulatorError::InjectionFailed(msg) => assert_eq!(msg, "test"),
_ => panic!("Clone should preserve variant"),
}
}
#[test]
fn test_audio_source_debug() {
let source = AudioSource::SineWave {
frequency: 440.0,
amplitude: 1.0,
};
let debug_str = format!("{source:?}");
assert!(debug_str.contains("SineWave"));
assert!(debug_str.contains("440"));
}
#[test]
fn test_audio_emulator_config_debug() {
let config = AudioEmulatorConfig::default();
let debug_str = format!("{config:?}");
assert!(debug_str.contains("16000"));
}
#[test]
fn test_audio_emulator_debug() {
let emulator = AudioEmulator::new(AudioSource::default());
let debug_str = format!("{emulator:?}");
assert!(debug_str.contains("AudioEmulator"));
}
#[test]
fn test_audio_emulator_error_debug() {
let error = AudioEmulatorError::ContextNotAvailable;
let debug_str = format!("{error:?}");
assert!(debug_str.contains("ContextNotAvailable"));
}
#[test]
fn test_speech_pattern_normalization() {
let mut emulator = AudioEmulator::new(AudioSource::SpeechPattern {
fundamental_hz: 150.0,
harmonics: vec![0.8, 0.8, 0.8, 0.8], variation_hz: 0.0,
});
let samples = emulator.generate_n_samples(1000);
assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
}
#[test]
fn test_sine_wave_very_low_frequency() {
let mut emulator = AudioEmulator::new(AudioSource::SineWave {
frequency: 0.0001, amplitude: 1.0,
});
let samples = emulator.generate_n_samples(100);
assert!(samples.iter().all(|&s| (-1.0..=1.0).contains(&s)));
}
#[test]
fn test_generate_samples_fractional_duration() {
let mut emulator = AudioEmulator::with_config(
AudioSource::Silence {
noise_floor_db: -60.0,
},
AudioEmulatorConfig {
sample_rate: 1000, ..Default::default()
},
);
let samples = emulator.generate_samples(0.0015);
assert_eq!(samples.len(), 1);
}
}