use super::VadConfig;
pub fn regions_from_probs(
probs: &[f32],
frame_samples: usize,
total_samples: usize,
cfg: &VadConfig,
) -> Vec<(usize, usize)> {
if probs.is_empty() || total_samples == 0 {
return Vec::new();
}
let min_silence = VadConfig::ms_to_samples(cfg.min_silence_ms);
let min_speech = VadConfig::ms_to_samples(cfg.min_speech_ms);
let pad = VadConfig::ms_to_samples(cfg.speech_pad_ms);
let mut regions: Vec<(usize, usize)> = Vec::new();
let mut run_start: Option<usize> = None;
for (i, &p) in probs.iter().enumerate() {
let speech = p >= cfg.threshold;
if speech && run_start.is_none() {
run_start = Some(i * frame_samples);
} else if !speech && let Some(s) = run_start.take() {
regions.push((s, i * frame_samples));
}
}
if let Some(s) = run_start.take() {
regions.push((s, total_samples));
}
if regions.is_empty() {
return regions;
}
let mut merged: Vec<(usize, usize)> = Vec::with_capacity(regions.len());
for (s, e) in regions {
match merged.last_mut() {
Some(last) if s.saturating_sub(last.1) < min_silence => last.1 = e,
_ => merged.push((s, e)),
}
}
merged.retain(|(s, e)| e - s >= min_speech);
if merged.is_empty() {
return merged;
}
let mut padded: Vec<(usize, usize)> = Vec::with_capacity(merged.len());
for (s, e) in merged {
let ps = s.saturating_sub(pad);
let pe = (e + pad).min(total_samples);
match padded.last_mut() {
Some(last) if ps <= last.1 => last.1 = last.1.max(pe),
_ => padded.push((ps, pe)),
}
}
padded
}
pub fn remap_compressed_seconds(
t_compressed_s: f64,
regions: &[(usize, usize)],
sample_rate: f64,
) -> f64 {
if regions.is_empty() {
return t_compressed_s;
}
let target = (t_compressed_s * sample_rate).max(0.0);
let mut acc = 0.0f64; for &(s, e) in regions {
let len = (e - s) as f64;
if target <= acc + len {
let into = (target - acc).max(0.0);
return (s as f64 + into) / sample_rate;
}
acc += len;
}
let &(_, end) = regions.last().expect("non-empty checked above");
end as f64 / sample_rate
}