diff --git a/tests/ci_audio_loopback.rs b/tests/ci_audio_loopback.rs new file mode 100644 index 0000000..873b7d8 --- /dev/null +++ b/tests/ci_audio_loopback.rs @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +use molotov_core::audio::pipewire::PipeWireStreamController; +use molotov_core::audio::types::AAudioDirection; + +#[test] +fn test_440hz_loopback_frequency_and_amplitude() { + let sample_rate = 48000; + let freq = 440.0f32; + let duration_samples = 48000 / 2; // 0.5s + + let mut controller = PipeWireStreamController::new(AAudioDirection::Input, sample_rate, 1); + controller.request_start().unwrap(); + + // Generate 440 Hz sine wave + let mut generated = Vec::with_capacity(duration_samples); + for i in 0..duration_samples { + let t = i as f32 / sample_rate as f32; + let sample = (2.0 * std::f32::consts::PI * freq * t).sin(); + generated.push(sample); + } + + controller.buffer.write(&generated); + + let mut captured = vec![0.0f32; duration_samples]; + let read_count = controller.buffer.read(&mut captured); + assert_eq!(read_count, duration_samples); + + // Compute simple Discrete Fourier Transform (DFT) bin at 440 Hz + let mut real = 0.0f32; + let mut imag = 0.0f32; + for (i, &sample) in captured.iter().enumerate() { + let angle = 2.0 * std::f32::consts::PI * freq * (i as f32) / (sample_rate as f32); + real += sample * angle.cos(); + imag -= sample * angle.sin(); + } + let magnitude = (real * real + imag * imag).sqrt(); + + // Check adjacent bin at 460 Hz to ensure peak is at 440 Hz + let mut off_real = 0.0f32; + let mut off_imag = 0.0f32; + let off_freq = 460.0f32; + for (i, &sample) in captured.iter().enumerate() { + let angle = 2.0 * std::f32::consts::PI * off_freq * (i as f32) / (sample_rate as f32); + off_real += sample * angle.cos(); + off_imag -= sample * angle.sin(); + } + let off_magnitude = (off_real * off_real + off_imag * off_imag).sqrt(); + + assert!( + magnitude > off_magnitude * 10.0, + "440 Hz peak must be dominant (>10x adjacent frequencies)" + ); +}