use super::ctx::Ctx;
use super::duration::parse_duration;
use crate::runtime::audio::{self, ToneAnalysis};
use crate::runtime::report::Event;
use std::sync::Arc;
use std::time::Duration;
const VERIFY_AUDIO_ATTEMPTS: u32 = 5;
const AUDIO_SWITCH_GAP: Duration = Duration::from_millis(250);
const DEFAULT_TONE: u32 = 1000;
const DEFAULT_WINDOW: Duration = Duration::from_secs(2);
#[derive(Clone)]
pub enum AudioSpec {
Tone(u32),
File(String),
Silent,
}
impl AudioSpec {
fn parts(&self) -> (String, String) {
match self {
AudioSpec::Tone(f) => (format!("ausine,{f}"), format!("tone {f} Hz")),
AudioSpec::File(p) => (format!("aufile,{p}"), format!("file {p}")),
AudioSpec::Silent => ("aubridge,default".to_string(), "silence".to_string()),
}
}
}
pub fn send_audio(ctx: &Arc<Ctx>, name: &str, spec: AudioSpec) -> Result<(), String> {
let (ausrc, detail) = spec.parts();
ctx.set_audio_source(name, &ausrc)?;
ctx.emit_action(name, "send-audio", Some(&detail));
Ok(())
}
fn detect(
ctx: &Arc<Ctx>,
name: &str,
freq: u32,
window: Duration,
) -> Result<(bool, String), String> {
let dir = ctx.recording_dir(name)?;
let mut last = ToneAnalysis::default();
for _ in 0..VERIFY_AUDIO_ATTEMPTS {
std::thread::sleep(window);
if let Some(a) = audio::latest_received_wav(&dir)
.and_then(|wav| audio::analyze_tone(&wav, freq, window).ok())
{
last = a;
if last.score >= audio::TONE_THRESHOLD {
return Ok((true, fmt_analysis(&last)));
}
}
}
Ok((false, fmt_analysis(&last)))
}
fn fmt_analysis(a: &ToneAnalysis) -> String {
format!(
"score {:.2}, rms {:.0}, {} samples",
a.score, a.rms, a.samples
)
}
pub fn verify_audio(ctx: &Arc<Ctx>, name: &str, freq: i64, within: &str) -> Result<(), String> {
let window = parse_duration(within)?;
let freq = freq.max(0) as u32;
let (ok, actual) = detect(ctx, name, freq, window)?;
ctx.emit(&Event::Assertion {
label: Some(name),
expect: format!("audio tone {freq} Hz"),
ok,
actual: Some(actual.clone()),
});
if ok {
Ok(())
} else {
Err(format!(
"verify_audio on `{name}`: tone {freq} Hz not detected ({actual})"
))
}
}
fn direction(
ctx: &Arc<Ctx>,
from: &str,
to: &str,
freq: u32,
window: Duration,
) -> Result<(), String> {
send_audio(ctx, from, AudioSpec::Tone(freq))?;
let (ok, actual) = detect(ctx, to, freq, window)?;
ctx.emit(&Event::Assertion {
label: Some(to),
expect: format!("audio {from} → {to} {freq} Hz"),
ok,
actual: Some(actual.clone()),
});
send_audio(ctx, from, AudioSpec::Silent)?;
if ok {
Ok(())
} else {
Err(format!(
"verify_audio_connection {from} → {to}: tone {freq} Hz not detected ({actual})"
))
}
}
pub fn verify_audio_connection(ctx: &Arc<Ctx>, a: &str, b: &str) -> Result<(), String> {
direction(ctx, a, b, DEFAULT_TONE, DEFAULT_WINDOW)?;
std::thread::sleep(AUDIO_SWITCH_GAP);
direction(ctx, b, a, DEFAULT_TONE, DEFAULT_WINDOW)?;
Ok(())
}