use super::*;
use crate::{DetectorConfig, SpeakerChange};
fn feed(d: &mut ActiveSpeakerDetector, p: u64, lvl: u8, from_ms: u64, ms: u64) {
let mut t = from_ms;
let end = from_ms + ms;
while t < end {
d.record_level(p, lvl, t);
t += 20;
}
}
fn assert_speaker(change: Option<SpeakerChange>, expected_peer: u64) {
assert_eq!(
change.map(|c| c.peer_id),
Some(expected_peer),
"expected dominant speaker {expected_peer}"
);
}
#[test]
fn single_speaker() {
let mut d = ActiveSpeakerDetector::new();
d.add_peer(1, 0);
assert_speaker(d.tick(300), 1);
assert_eq!(d.tick(600), None);
}
#[test]
fn silence_then_speech_switches() {
let mut d = ActiveSpeakerDetector::new();
d.add_peer(1, 0);
d.add_peer(2, 0);
feed(&mut d, 1, 5, 0, 2000);
feed(&mut d, 2, 127, 0, 2000);
assert_speaker(d.tick(2050), 1);
}
#[test]
fn hysteresis_prevents_brief_flap() {
let mut d = ActiveSpeakerDetector::new();
d.add_peer(1, 0);
d.add_peer(2, 0);
feed(&mut d, 1, 5, 0, 2000);
feed(&mut d, 2, 127, 0, 2000);
assert_speaker(d.tick(2050), 1);
let t1: u64 = 2050;
feed(&mut d, 1, 127, t1, 400);
feed(&mut d, 2, 5, t1, 400);
assert_eq!(d.tick(t1 + 450), None);
}
#[test]
fn detector_with_custom_constants_differs_from_default() {
let config = DetectorConfig {
c1: 5.0,
c2: 4.0,
c3: 1.0,
n1: 10,
n2: 4,
n3: 8,
..DetectorConfig::default()
};
let detector: ActiveSpeakerDetector<u64> = ActiveSpeakerDetector::with_config(config.clone());
assert!((detector.config().c1 - 5.0).abs() < f64::EPSILON);
assert!((detector.config().c2 - 4.0).abs() < f64::EPSILON);
assert!((detector.config().c3 - 1.0).abs() < f64::EPSILON);
assert_eq!(detector.config().n1, 10);
assert_eq!(detector.config().n2, 4);
assert_eq!(detector.config().n3, 8);
let default_detector: ActiveSpeakerDetector<u64> = ActiveSpeakerDetector::new();
assert!((default_detector.config().c1 - 3.0).abs() < f64::EPSILON);
assert!((default_detector.config().c2 - 2.0).abs() < f64::EPSILON);
assert_eq!(default_detector.config().n1, 13);
}
#[test]
fn idle_removal_clears_dominance() {
let mut d = ActiveSpeakerDetector::new();
d.add_peer(1, 0);
assert_speaker(d.tick(300), 1);
d.remove_peer(&1);
assert_eq!(d.tick(600), None);
}
#[test]
fn custom_n1_elects_louder_peer() {
let config = DetectorConfig {
n1: 10,
n2: 4,
n3: 8,
..DetectorConfig::default()
};
let mut d = ActiveSpeakerDetector::with_config(config);
d.add_peer(1, 0);
d.add_peer(2, 0);
feed(&mut d, 1, 5, 0, 2000);
feed(&mut d, 2, 127, 0, 2000);
assert_speaker(d.tick(2050), 1);
}
#[test]
fn speaker_change_has_nonnegative_margin() {
let mut d = ActiveSpeakerDetector::new();
d.add_peer(1, 0);
d.add_peer(2, 0);
feed(&mut d, 1, 5, 0, 2000);
feed(&mut d, 2, 127, 0, 2000);
let change = d.tick(2050).expect("should elect");
assert_eq!(change.peer_id, 1);
assert!(
change.c2_margin >= 0.0,
"margin must be non-negative, got {}",
change.c2_margin
);
}
#[test]
fn top_k_returns_loudest_first() {
let mut d = ActiveSpeakerDetector::new();
d.add_peer(1, 0);
d.add_peer(2, 0);
d.add_peer(3, 0);
feed(&mut d, 1, 5, 0, 2000); feed(&mut d, 2, 50, 0, 2000); feed(&mut d, 3, 127, 0, 2000); d.tick(2050);
d.tick(2350);
let top2 = d.current_top_k(2);
assert_eq!(top2.len(), 2);
assert_eq!(top2[0], 1, "loudest peer should be first");
assert_eq!(top2[1], 2, "moderate peer should be second");
}
#[test]
fn top_k_larger_than_peers_returns_all() {
let mut d = ActiveSpeakerDetector::new();
d.add_peer(1, 0);
d.add_peer(2, 0);
d.tick(300);
let top = d.current_top_k(10);
assert_eq!(top.len(), 2);
}
#[test]
fn peer_scores_returns_all_peers() {
let mut d = ActiveSpeakerDetector::new();
d.add_peer(1, 0);
d.add_peer(2, 0);
feed(&mut d, 1, 5, 0, 1000);
d.tick(1050);
let scores = d.peer_scores();
assert_eq!(scores.len(), 2);
for (_, imm, med, lng) in &scores {
assert!(imm.is_finite() && *imm >= 0.0);
assert!(med.is_finite() && *med >= 0.0);
assert!(lng.is_finite() && *lng >= 0.0);
}
}
#[test]
fn remove_dominant_clears_dominance_for_next_tick() {
let mut d = ActiveSpeakerDetector::new();
d.add_peer(1, 0);
d.add_peer(2, 0);
feed(&mut d, 1, 5, 0, 2000);
feed(&mut d, 2, 127, 0, 2000);
assert_speaker(d.tick(2050), 1);
assert_eq!(d.current_dominant(), Some(&1));
d.remove_peer(&1);
assert_eq!(d.current_dominant(), None, "dominance must clear on remove");
assert_speaker(d.tick(2400), 2);
}
#[test]
fn idle_peer_gets_paused_and_excluded_from_election() {
let mut d = ActiveSpeakerDetector::new();
d.add_peer(1, 0);
d.add_peer(2, 0);
feed(&mut d, 2, 5, 0, 2000);
feed(&mut d, 1, 127, 0, 2000);
if let Some(sp) = d.speakers_mut().get_mut(&2) {
sp.paused = true;
}
assert_speaker(d.tick(2050), 1);
}
#[test]
fn tick_with_no_peers_returns_none() {
let mut d: ActiveSpeakerDetector = ActiveSpeakerDetector::new();
assert_eq!(d.tick(300), None);
assert_eq!(d.current_dominant(), None);
}