use crate::analyse::rms_db;
pub const DEFAULT_TOLERANCE_DB: f32 = 2.0;
#[derive(Debug, Clone)]
pub struct ConsistencyReport {
pub episode_rms_db: Vec<f32>,
pub mean_rms_db: f32,
pub max_deviation_db: f32,
pub compliant: bool,
}
pub fn consistency_check(episodes: &[&[i16]], tolerance_db: f32) -> ConsistencyReport {
if episodes.is_empty() {
return ConsistencyReport {
episode_rms_db: Vec::new(),
mean_rms_db: -144.0,
max_deviation_db: 0.0,
compliant: true,
};
}
let rms_values: Vec<f32> = episodes.iter().map(|e| rms_db(e)).collect();
let mean = rms_values.iter().sum::<f32>() / rms_values.len() as f32;
let max_dev = rms_values
.iter()
.map(|&r| (r - mean).abs())
.fold(0f32, f32::max);
ConsistencyReport {
episode_rms_db: rms_values,
mean_rms_db: mean,
max_deviation_db: max_dev,
compliant: max_dev <= tolerance_db,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identical_episodes_are_compliant() {
let ep: Vec<i16> = (0..24_000).map(|i| ((i % 100) as i16) * 300).collect();
let report = consistency_check(&[&ep, &ep, &ep], DEFAULT_TOLERANCE_DB);
assert!(report.compliant);
assert!(
report.max_deviation_db < 0.001,
"max deviation should be near zero, got {}",
report.max_deviation_db
);
}
#[test]
fn large_rms_difference_is_flagged() {
let loud: Vec<i16> = vec![10_000i16; 24_000];
let quiet: Vec<i16> = vec![1_000i16; 24_000];
let report = consistency_check(&[&loud, &quiet], DEFAULT_TOLERANCE_DB);
assert!(!report.compliant, "expected non-compliant for 20 dB spread");
assert!(report.max_deviation_db > DEFAULT_TOLERANCE_DB);
}
#[test]
fn empty_episode_list_is_compliant() {
let report = consistency_check(&[], DEFAULT_TOLERANCE_DB);
assert!(report.compliant);
}
#[test]
fn single_episode_is_always_compliant() {
let ep: Vec<i16> = vec![5_000i16; 24_000];
let report = consistency_check(&[&ep], DEFAULT_TOLERANCE_DB);
assert!(report.compliant);
assert_eq!(report.max_deviation_db, 0.0);
}
}