// SPDX-License-Identifier: GPL-3.0-or-later use molotov_core::audio::c_api::*; use molotov_core::audio::types::{AAudioDirection, AAudioFormat, AAudioResult}; use std::ffi::c_void; #[test] fn test_440hz_loopback_frequency_and_amplitude() { let sample_rate = 48000; let target_freq = 440.0f32; let duration_frames = 24000; // 0.5s at 48kHz // 1. Setup Stream via AAudio C-ABI unsafe { let mut builder_ptr = std::ptr::null_mut(); assert_eq!(AAudio_createStreamBuilder(&mut builder_ptr), AAudioResult::Ok as i32); AAudioStreamBuilder_setDirection(builder_ptr, AAudioDirection::Input as i32); AAudioStreamBuilder_setSampleRate(builder_ptr, sample_rate); AAudioStreamBuilder_setChannelCount(builder_ptr, 1); AAudioStreamBuilder_setFormat(builder_ptr, AAudioFormat::PcmFloat as i32); AAudioStreamBuilder_setBufferCapacityInFrames(builder_ptr, duration_frames); let mut stream_ptr = std::ptr::null_mut(); assert_eq!(AAudioStreamBuilder_openStream(builder_ptr, &mut stream_ptr), AAudioResult::Ok as i32); assert!(!stream_ptr.is_null()); assert_eq!(AAudioStream_requestStart(stream_ptr), AAudioResult::Ok as i32); // 2. Synthesize 440 Hz pure tone into playback let mut generated = Vec::with_capacity(duration_frames as usize); for i in 0..duration_frames { let t = i as f32 / sample_rate as f32; let sample = (2.0 * std::f32::consts::PI * target_freq * t).sin(); generated.push(sample); } let written = AAudioStream_write( stream_ptr, generated.as_ptr() as *const c_void, duration_frames, 0, ); assert_eq!(written, duration_frames); // 3. Capture audio from stream let mut captured = vec![0.0f32; duration_frames as usize]; let read_frames = AAudioStream_read( stream_ptr, captured.as_mut_ptr() as *mut c_void, duration_frames, 0, ); assert_eq!(read_frames, duration_frames); // 4. Teardown stream via C-ABI assert_eq!(AAudioStream_requestPause(stream_ptr), AAudioResult::Ok as i32); assert_eq!(AAudioStream_close(stream_ptr), AAudioResult::Ok as i32); assert_eq!(AAudioStreamBuilder_delete(builder_ptr), AAudioResult::Ok as i32); // 5. Rigorous Spectral Scan across 430 Hz to 450 Hz in 0.5 Hz steps let mut max_magnitude = 0.0f32; let mut peak_freq = 0.0f32; let start_freq = 430.0f32; let end_freq = 450.0f32; let step = 0.5f32; let steps = ((end_freq - start_freq) / step) as usize; for s in 0..=steps { let f = start_freq + (s as f32) * step; 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 * f * (i as f32) / (sample_rate as f32); real += sample * angle.cos(); imag -= sample * angle.sin(); } let mag = (real * real + imag * imag).sqrt(); if mag > max_magnitude { max_magnitude = mag; peak_freq = f; } } // Assert peak frequency is detected within ±1.0 Hz tolerance let freq_delta = (peak_freq - target_freq).abs(); assert!( freq_delta <= 1.0, "Peak frequency {} Hz exceeds ±1.0 Hz tolerance around {} Hz", peak_freq, target_freq ); // 6. Compute Normalized Cross-Correlation (rho) between generated and captured waveforms let mean_gen = generated.iter().sum::() / (duration_frames as f32); let mean_cap = captured.iter().sum::() / (duration_frames as f32); let mut numerator = 0.0f32; let mut denom_gen = 0.0f32; let mut denom_cap = 0.0f32; for i in 0..duration_frames as usize { let dg = generated[i] - mean_gen; let dc = captured[i] - mean_cap; numerator += dg * dc; denom_gen += dg * dg; denom_cap += dc * dc; } let rho = numerator / (denom_gen.sqrt() * denom_cap.sqrt()); assert!( rho >= 0.95, "Waveform fidelity rho = {:.4} is below required 95% threshold", rho ); } }