This repository has been archived on 2026-10-10. You can view files and clone it, but cannot push or open issues or pull requests.
molotov/tests/ci_audio_loopback.rs

117 lines
4.4 KiB
Rust

// 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::<f32>() / (duration_frames as f32);
let mean_cap = captured.iter().sum::<f32>() / (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
);
}
}