use core::{num::NonZeroU32, time::Duration};
use std::borrow::Cow;
use mediatime::{TimeRange, Timebase};
use smol_str::SmolStr;
use crate::{
core::AlignmentResult,
runner::aligner::{
algorithm::trellis_beam::WordSegment,
emissions_api::{SpeechCoverage, SpeechSpans},
},
time::SAMPLE_RATE_HZ,
types::Word,
};
pub const DEFAULT_MIN_SPEECH_COVERAGE: f32 = 0.5;
pub const DEFAULT_MAX_INTRA_SILENT_RUN: Duration = Duration::from_millis(80);
pub fn effective_samples_per_frame(n_samples: u64, total_frames: usize, hop_samples: u32) -> f64 {
if total_frames >= 2 {
(n_samples as f64) / ((total_frames - 1) as f64)
} else {
hop_samples as f64
}
}
const fn ceil_half(x: i64) -> i64 {
x / 2 + x % 2
}
const fn saturating_extent_i64(extent: u64) -> i64 {
if extent > i64::MAX as u64 {
i64::MAX
} else {
extent as i64
}
}
pub fn build_speech_frames(
n_frames: usize,
samples_per_frame: f64,
n_samples: u64,
real_n_samples: u64,
speech: &SpeechSpans,
) -> Vec<bool> {
if !samples_per_frame.is_finite() || samples_per_frame <= 0.0 {
return vec![false; n_frames];
}
let spf_int = samples_per_frame.floor() as i64;
let min_overlap_samples = ceil_half(spf_int).max(1);
let real_n_samples_i64 = saturating_extent_i64(real_n_samples.min(n_samples));
let merged_segs: Vec<(i64, i64)> = speech
.as_slice()
.iter()
.map(|s| {
(
(s.start() as i64).clamp(0, real_n_samples_i64),
(s.end() as i64).clamp(0, real_n_samples_i64),
)
})
.filter(|(s, e)| e > s)
.collect();
let mut overlap_per_frame = vec![0_i64; n_frames];
for &(seg_start, seg_end) in &merged_segs {
let frame_start = ((seg_start as f64) / samples_per_frame).floor() as i64;
let frame_start = frame_start.max(0) as usize;
let frame_end = ((seg_end as f64) / samples_per_frame).ceil() as i64;
let frame_end = frame_end.max(0) as usize;
let upper = frame_end.min(n_frames);
if frame_start >= upper {
continue;
}
for f in frame_start..upper {
let frame_lo = ((f as f64) * samples_per_frame).round() as i64;
let frame_hi = (((f + 1) as f64) * samples_per_frame).round() as i64;
let overlap = seg_end.min(frame_hi) - seg_start.max(frame_lo);
if overlap > 0 {
overlap_per_frame[f] = overlap_per_frame[f].saturating_add(overlap);
}
}
}
overlap_per_frame
.into_iter()
.enumerate()
.map(|(f, o)| {
let frame_lo_i = ((f as f64) * samples_per_frame).round() as i64;
let frame_hi_i = (((f + 1) as f64) * samples_per_frame).round() as i64;
let real_lo = frame_lo_i.clamp(0, real_n_samples_i64);
let real_hi = frame_hi_i.clamp(0, real_n_samples_i64);
let real_width = (real_hi - real_lo).max(0);
let frame_thr = if real_width == 0 {
min_overlap_samples
} else {
ceil_half(real_width).max(1).min(min_overlap_samples)
};
o >= frame_thr
})
.collect()
}
#[allow(
clippy::too_many_arguments,
reason = "10 args carry the per-chunk composition contract \
(raw word segments, original surface forms, speech mask, \
chunk anchor, hop, sample count, output bridge closure, \
speech-coverage threshold, intra-silence run threshold, \
language-aware policy); each is a distinct semantic axis \
from upstream passes — bundling them adds indirection"
)]
pub fn compose_words<F>(
word_segments: &[WordSegment],
original_words: &[Cow<'_, str>],
speech_frames: &[bool],
chunk_first_sample_in_stream: u64,
hop_samples: u32,
encoder_n_samples: u64,
real_n_samples: u64,
total_frames: usize,
samples_to_output_range: F,
min_speech_coverage: SpeechCoverage,
max_intra_silent_run: Duration,
) -> AlignmentResult
where
F: Fn(u64, u64) -> TimeRange,
{
let frame_tb = Timebase::new(hop_samples, NonZeroU32::new(SAMPLE_RATE_HZ).unwrap());
let max_silent_run_frames = frame_tb.duration_to_pts(max_intra_silent_run) as usize;
let samples_per_frame = effective_samples_per_frame(encoder_n_samples, total_frames, hop_samples);
let chunk_end_sample = chunk_first_sample_in_stream.saturating_add(real_n_samples);
let mut words: Vec<Word> = Vec::with_capacity(word_segments.len());
for seg in word_segments {
let Some(surface) = original_words.get(seg.word_index()) else {
continue;
};
if seg.end_frame() <= seg.start_frame() {
continue;
}
let span_start = seg.start_frame().min(speech_frames.len());
let span_end = seg.end_frame().min(speech_frames.len());
let span_len = span_end.saturating_sub(span_start);
if span_len == 0 {
continue;
}
let mut speech_count = 0_usize;
let mut max_run = 0_usize;
let mut current_run = 0_usize;
for f in span_start..span_end {
if speech_frames[f] {
speech_count += 1;
current_run = 0;
} else {
current_run += 1;
if current_run > max_run {
max_run = current_run;
}
}
}
if speech_count == 0 {
continue;
}
let coverage = (speech_count as f32) / (span_len as f32);
if coverage < min_speech_coverage.get() {
continue;
}
if max_run > max_silent_run_frames {
continue;
}
let start_offset = (seg.start_frame() as f64 * samples_per_frame).round() as u64;
let end_offset = (seg.end_frame() as f64 * samples_per_frame).round() as u64;
let raw_start = chunk_first_sample_in_stream.saturating_add(start_offset);
let raw_end = chunk_first_sample_in_stream.saturating_add(end_offset);
if raw_start >= chunk_end_sample || raw_end <= raw_start {
continue;
}
let clamped_end = raw_end.min(chunk_end_sample);
if clamped_end <= raw_start {
continue;
}
let range = samples_to_output_range(raw_start, clamped_end);
let raw_score = seg.score();
let score = if raw_score.is_nan() {
0.0
} else {
raw_score.clamp(0.0, 1.0)
};
words.push(Word::new(SmolStr::new(surface.as_ref()), range, score));
}
AlignmentResult::new(words)
}
#[cfg(test)]
mod tests {
use super::*;
fn sp(ranges: Vec<TimeRange>) -> SpeechSpans {
SpeechSpans::from_time_ranges(&ranges).expect("test ranges use the analysis timebase")
}
fn no_speech() -> SpeechSpans {
SpeechSpans::new([])
}
use core::num::NonZeroU32;
use mediatime::Timebase;
fn tb_ms() -> Timebase {
Timebase::new(1, NonZeroU32::new(1000).unwrap())
}
fn fake_samples_to_output_range(start: u64, end: u64) -> TimeRange {
let clamp = |x: u64| i64::try_from(x).unwrap_or(i64::MAX);
TimeRange::new(clamp(start), clamp(end), tb_ms())
}
fn one_word(start: usize, end: usize, score: f32, idx: usize) -> WordSegment {
WordSegment::new(idx, start, end, score)
}
#[test]
fn near_u64_max_chunk_anchor_does_not_overflow() {
let anchor = u64::MAX - 100;
let original = vec![Cow::Borrowed("hi")];
let speech_frames = vec![true; 5];
let result = compose_words(
&[one_word(0, 3, 0.8, 0)],
&original,
&speech_frames,
anchor,
320,
5 * 320,
5 * 320,
5,
fake_samples_to_output_range,
SpeechCoverage::DEFAULT,
DEFAULT_MAX_INTRA_SILENT_RUN,
);
for w in result.words() {
assert!(
w.text() == "hi" || w.text().is_empty(),
"unexpected surface form on overflow recovery: {:?}",
w.text(),
);
}
}
#[test]
fn compose_words_is_total_over_its_degenerate_argument_corners() {
let anchors = [0_u64, 1, u64::MAX / 2, u64::MAX - 1, u64::MAX];
let hops = [0_u32, 1, 320, u32::MAX];
let extents = [0_u64, 1, 480_000, u64::MAX];
let frame_counts = [0_usize, 1, 2, usize::MAX];
let spans = [(0_usize, 0_usize), (0, 1), (0, usize::MAX), (usize::MAX, 0)];
let scores = [
0.0_f32,
1.0,
-1.0,
2.0,
f32::NAN,
f32::INFINITY,
f32::NEG_INFINITY,
];
let original = vec![Cow::Borrowed("w")];
for speech_frames in [vec![], vec![true], vec![true, false, true]] {
for &anchor in &anchors {
for &hop in &hops {
for &encoder_n in &extents {
for &real_n in &extents {
for &total_frames in &frame_counts {
for &(start, end) in &spans {
for &score in &scores {
for word_index in [0_usize, usize::MAX] {
let seg = one_word(start, end, score, word_index);
let chunk_end = anchor.saturating_add(real_n);
let seen = core::cell::RefCell::new(Vec::new());
let result = compose_words(
&[seg],
&original,
&speech_frames,
anchor,
hop,
encoder_n,
real_n,
total_frames,
|s, e| {
seen.borrow_mut().push((s, e));
fake_samples_to_output_range(s, e)
},
SpeechCoverage::DEFAULT,
DEFAULT_MAX_INTRA_SILENT_RUN,
);
let ctx = format!(
"anchor={anchor}, hop={hop}, encoder_n={encoder_n}, \
real_n={real_n}, total_frames={total_frames}, span={start}..{end}, \
raw_score={score}, word_index={word_index}"
);
for &(s, e) in seen.borrow().iter() {
assert!(s < e, "bridge got an inverted range {s}..{e} ({ctx})");
assert!(s >= anchor, "bridge got {s} before the anchor ({ctx})");
assert!(
e <= chunk_end,
"bridge got {e} past the chunk end {chunk_end} ({ctx})"
);
}
for w in result.words() {
let s = w.score();
assert!(
!s.is_nan() && (0.0..=1.0).contains(&s),
"score contract violated: {s} ({ctx})"
);
}
}
}
}
}
}
}
}
}
}
}
#[test]
fn empty_word_segments_yields_empty_alignment() {
let original = vec![Cow::Borrowed("hello")];
let speech_frames = vec![true; 5];
let result = compose_words(
&[],
&original,
&speech_frames,
0,
320,
5 * 320,
5 * 320,
5,
fake_samples_to_output_range,
SpeechCoverage::DEFAULT,
DEFAULT_MAX_INTRA_SILENT_RUN,
);
assert!(result.words().is_empty());
}
#[test]
fn surface_form_preserved_not_normalized() {
let original = vec![Cow::Borrowed("Hello!")];
let speech_frames = vec![true; 3];
let result = compose_words(
&[one_word(0, 3, 0.8, 0)],
&original,
&speech_frames,
0,
320,
3 * 320,
3 * 320,
3,
fake_samples_to_output_range,
SpeechCoverage::DEFAULT,
DEFAULT_MAX_INTRA_SILENT_RUN,
);
assert_eq!(result.words()[0].text(), "Hello!");
}
#[test]
fn compose_words_sanitizes_nan_segment_score() {
let original = vec![Cow::Borrowed("hi")];
let speech_frames = vec![true; 3];
let result = compose_words(
&[one_word(0, 3, f32::NAN, 0)],
&original,
&speech_frames,
0,
320,
3 * 320,
3 * 320,
3,
fake_samples_to_output_range,
SpeechCoverage::DEFAULT,
DEFAULT_MAX_INTRA_SILENT_RUN,
);
assert_eq!(
result.words().len(),
1,
"the word should survive composition"
);
let score = result.words()[0].score();
assert!(
!score.is_nan(),
"a NaN segment score must be sanitized, got {score}"
);
assert!(
(0.0..=1.0).contains(&score),
"score must be in [0, 1], got {score}"
);
assert_eq!(score, 0.0, "a NaN score maps to 0.0 (lowest confidence)");
}
#[test]
fn out_of_range_word_index_is_dropped() {
let original = vec![Cow::Borrowed("hi")];
let speech_frames = vec![true; 3];
let result = compose_words(
&[one_word(0, 3, 0.5, 5)],
&original,
&speech_frames,
0,
320,
3 * 320,
3 * 320,
3,
fake_samples_to_output_range,
SpeechCoverage::DEFAULT,
DEFAULT_MAX_INTRA_SILENT_RUN,
);
assert!(result.words().is_empty());
}
#[test]
fn frame_to_sample_uses_effective_ratio_not_nominal_hop() {
let original = vec![Cow::Borrowed("ratio")];
let speech_frames = vec![true; 1500];
let result = compose_words(
&[one_word(100, 110, 0.9, 0)],
&original,
&speech_frames,
0,
320,
480_000,
480_000,
1500,
fake_samples_to_output_range,
SpeechCoverage::DEFAULT,
DEFAULT_MAX_INTRA_SILENT_RUN,
);
assert_eq!(result.words().len(), 1);
let r = result.words()[0].range();
let start = r.start_pts();
let expected = 32_021_i64;
assert!(
(start - expected).abs() <= 1,
"expected {expected}, got {start}"
);
}
#[test]
fn word_in_silence_drops() {
let original = vec![Cow::Borrowed("hi")];
let speech_frames = vec![false; 5];
let result = compose_words(
&[one_word(0, 5, 0.9, 0)],
&original,
&speech_frames,
0,
320,
5 * 320,
5 * 320,
5,
fake_samples_to_output_range,
SpeechCoverage::DEFAULT,
DEFAULT_MAX_INTRA_SILENT_RUN,
);
assert!(result.words().is_empty());
}
#[test]
fn word_with_brief_silent_gap_is_kept() {
let original = vec![Cow::Borrowed("hello")];
let speech_frames = vec![true, true, false, true, true];
let result = compose_words(
&[one_word(0, 5, 0.9, 0)],
&original,
&speech_frames,
0,
320,
5 * 320,
5 * 320,
5,
fake_samples_to_output_range,
SpeechCoverage::DEFAULT,
DEFAULT_MAX_INTRA_SILENT_RUN,
);
assert_eq!(result.words().len(), 1);
}
#[test]
fn word_spanning_long_silent_gap_drops() {
let original = vec![Cow::Borrowed("split")];
let mut speech_frames = vec![false; 21];
speech_frames[0] = true;
speech_frames[20] = true;
let result = compose_words(
&[one_word(0, 21, 0.9, 0)],
&original,
&speech_frames,
0,
320,
21 * 320,
21 * 320,
21,
fake_samples_to_output_range,
SpeechCoverage::DEFAULT,
DEFAULT_MAX_INTRA_SILENT_RUN,
);
assert!(result.words().is_empty());
}
#[test]
fn fragmented_word_with_minority_speech_drops() {
let original = vec![Cow::Borrowed("missed")];
let speech_frames = vec![true, false, false, false, false];
let result = compose_words(
&[one_word(0, 5, 0.9, 0)],
&original,
&speech_frames,
0,
320,
5 * 320,
5 * 320,
5,
fake_samples_to_output_range,
SpeechCoverage::DEFAULT,
DEFAULT_MAX_INTRA_SILENT_RUN,
);
assert!(result.words().is_empty());
}
#[test]
fn ranges_clamped_to_chunk_bounds() {
let original = vec![Cow::Borrowed("ok")];
let speech_frames = vec![true; 4];
let result = compose_words(
&[one_word(0, 4, 0.9, 0)],
&original,
&speech_frames,
0,
320,
1_000,
1_000,
4,
fake_samples_to_output_range,
SpeechCoverage::DEFAULT,
DEFAULT_MAX_INTRA_SILENT_RUN,
);
assert_eq!(result.words().len(), 1);
assert_eq!(result.words()[0].range().end_pts(), 1_000);
}
#[test]
fn word_entirely_in_overshoot_drops() {
let original = vec![Cow::Borrowed("late")];
let speech_frames = vec![true; 4];
let result = compose_words(
&[one_word(3, 4, 0.9, 0)],
&original,
&speech_frames,
0,
320,
900,
900,
4,
fake_samples_to_output_range,
SpeechCoverage::DEFAULT,
DEFAULT_MAX_INTRA_SILENT_RUN,
);
assert!(result.words().is_empty());
}
#[test]
fn build_speech_frames_marks_overlapping_segments() {
use core::num::NonZeroU32;
use mediatime::{TimeRange, Timebase};
let tb_16k = Timebase::new(1, NonZeroU32::new(16_000).unwrap());
let segs = sp(vec![TimeRange::new(320, 960, tb_16k)]);
let mask = build_speech_frames(
5, 320.0, 1600,
1600, &segs,
);
assert_eq!(mask, vec![false, true, true, false, false]);
}
#[test]
fn build_speech_frames_handles_no_segments() {
let mask = build_speech_frames(4, 320.0, 1280, 1280, &no_speech());
assert_eq!(mask, vec![false; 4]);
}
#[test]
fn build_speech_frames_saturates_u64_max_sample_extents() {
let n = u64::MAX;
let spf = effective_samples_per_frame(n, 2, 320);
let mask = build_speech_frames(2, spf, n, n, &no_speech());
assert_eq!(
mask,
vec![false, false],
"a u64::MAX sample extent must yield a defined all-false mask, never a panic",
);
}
#[test]
fn build_speech_frames_saturates_u64_max_extents_with_segments() {
use core::num::NonZeroU32;
use mediatime::{TimeRange, Timebase};
let tb_16k = Timebase::new(1, NonZeroU32::new(16_000).unwrap());
let n = u64::MAX;
let spf = effective_samples_per_frame(n, 2, 320);
let segs = sp(vec![TimeRange::new(0, 16_000, tb_16k)]);
let mask = build_speech_frames(2, spf, n, n, &segs);
assert_eq!(mask.len(), 2);
assert_eq!(mask, vec![false, false]);
}
#[test]
fn saturating_extent_and_ceil_half_match_the_naive_arithmetic_below_the_boundary() {
for x in [0_i64, 1, 2, 3, 4, 5, 159, 160, 319, 320, 321, 480_000] {
assert_eq!(ceil_half(x), (x + 1) / 2, "ceil_half diverged at {x}");
}
for x in [0_u64, 1, 320, 480_000, i64::MAX as u64] {
assert_eq!(saturating_extent_i64(x), x as i64, "fold diverged at {x}");
}
assert_eq!(saturating_extent_i64(u64::MAX), i64::MAX);
assert_eq!(saturating_extent_i64(i64::MAX as u64 + 1), i64::MAX);
assert_eq!(ceil_half(i64::MAX), 4_611_686_018_427_387_904);
}
#[test]
fn build_speech_frames_hop_one_with_no_segments_is_all_silence() {
let mask = build_speech_frames(8, 1.0, 8, 8, &no_speech());
assert_eq!(mask, vec![false; 8]);
}
#[test]
fn build_speech_frames_short_padded_run_marks_real_speech() {
use core::num::NonZeroU32;
let tb_16k = mediatime::Timebase::new(1, NonZeroU32::new(16_000).unwrap());
let segs = sp(vec![mediatime::TimeRange::new(0, 100, tb_16k)]);
let mask = build_speech_frames(
1, 320.0, 400,
100, &segs,
);
assert_eq!(
mask,
vec![true],
"all-speech 100-sample run padded to 400 must classify frame 0 as speech",
);
}
#[test]
fn build_speech_frames_clamps_subsegments_to_real_audio_when_padded() {
use core::num::NonZeroU32;
let tb_16k = mediatime::Timebase::new(1, NonZeroU32::new(16_000).unwrap());
let segs = sp(vec![mediatime::TimeRange::new(0, 600, tb_16k)]);
let mask = build_speech_frames(
3, 320.0, 960,
100, &segs,
);
assert_eq!(
mask,
vec![true, false, false],
"overshooting VAD must clamp to real audio; only frame 0 should be speech",
);
}
#[test]
fn build_speech_frames_partial_overshoot_clamps_to_real_audio() {
use core::num::NonZeroU32;
let tb_16k = mediatime::Timebase::new(1, NonZeroU32::new(16_000).unwrap());
let segs = sp(vec![mediatime::TimeRange::new(40, 320, tb_16k)]);
let mask = build_speech_frames(
2, 320.0, 640,
50, &segs,
);
assert_eq!(
mask,
vec![false, false],
"partial overshoot must not credit padded samples; \
real overlap (10) is below real-window threshold (25)",
);
}
#[test]
fn build_speech_frames_padding_only_frame_stays_silent() {
use core::num::NonZeroU32;
let tb_16k = mediatime::Timebase::new(1, NonZeroU32::new(16_000).unwrap());
let segs = sp(vec![mediatime::TimeRange::new(0, 100, tb_16k)]);
let mask = build_speech_frames(
2, 320.0, 640,
100, &segs,
);
assert_eq!(
mask,
vec![true, false],
"frame 1 covers only padding; even with a sub-segment in [0,100] only frame 0 should be speech",
);
}
#[test]
fn build_speech_frames_odd_hop_requires_strict_majority() {
use core::num::NonZeroU32;
use mediatime::{TimeRange, Timebase};
let tb_16k = Timebase::new(1, NonZeroU32::new(16_000).unwrap());
let segs = sp(vec![TimeRange::new(0, 1, tb_16k)]);
let mask = build_speech_frames(4, 3.0, 12, 12, &segs);
assert_eq!(mask, vec![false; 4]);
let segs_at = sp(vec![TimeRange::new(0, 2, tb_16k)]);
let mask_at = build_speech_frames(4, 3.0, 12, 12, &segs_at);
assert!(mask_at[0]);
}
#[test]
fn build_speech_frames_threshold_is_inclusive() {
use core::num::NonZeroU32;
use mediatime::{TimeRange, Timebase};
let tb_16k = Timebase::new(1, NonZeroU32::new(16_000).unwrap());
let segs_at = sp(vec![TimeRange::new(0, 160, tb_16k)]);
assert_eq!(
build_speech_frames(2, 320.0, 640, 640, &segs_at),
vec![true, false]
);
let segs_under = sp(vec![TimeRange::new(0, 159, tb_16k)]);
assert_eq!(
build_speech_frames(2, 320.0, 640, 640, &segs_under),
vec![false, false]
);
}
#[test]
fn build_speech_frames_accumulates_overlap_across_adjacent_segments() {
use core::num::NonZeroU32;
use mediatime::{TimeRange, Timebase};
let tb_16k = Timebase::new(1, NonZeroU32::new(16_000).unwrap());
let segs = sp(vec![
TimeRange::new(0, 80, tb_16k),
TimeRange::new(160, 240, tb_16k),
]);
assert_eq!(
build_speech_frames(2, 320.0, 640, 640, &segs),
vec![true, false]
);
}
#[test]
fn build_speech_frames_clamps_overshoot_seg_to_chunk_end() {
use core::num::NonZeroU32;
use mediatime::{TimeRange, Timebase};
let tb_16k = Timebase::new(1, NonZeroU32::new(16_000).unwrap());
let segs = sp(vec![TimeRange::new(320, 480, tb_16k)]);
let mask = build_speech_frames(
2, 320.0, 320, 320,
&segs,
);
assert_eq!(
mask,
vec![false, false],
"out-of-range seg must not credit phantom samples"
);
let partial = sp(vec![TimeRange::new(200, 480, tb_16k)]);
let mask_partial = build_speech_frames(2, 320.0, 320, 320, &partial);
assert!(
!mask_partial[1],
"frame 1 must not be speech (no real audio)"
);
}
#[test]
fn build_speech_frames_uses_union_not_sum_for_overlapping_segments() {
use core::num::NonZeroU32;
use mediatime::{TimeRange, Timebase};
let tb_16k = Timebase::new(1, NonZeroU32::new(16_000).unwrap());
let overlapping = sp(vec![
TimeRange::new(0, 100, tb_16k),
TimeRange::new(50, 150, tb_16k),
]);
let mask = build_speech_frames(
1,
320.0,
320,
320,
&overlapping,
);
assert_eq!(
mask,
vec![false],
"overlapping segs must be coalesced to their union; sum 200 vs union 150 (< 160 threshold)"
);
let union_speech = sp(vec![
TimeRange::new(0, 100, tb_16k),
TimeRange::new(80, 200, tb_16k), ]);
let mask_speech = build_speech_frames(1, 320.0, 320, 320, &union_speech);
assert_eq!(mask_speech, vec![true]);
let triple = sp(vec![
TimeRange::new(0, 80, tb_16k),
TimeRange::new(20, 100, tb_16k),
TimeRange::new(40, 120, tb_16k),
]);
let mask_triple = build_speech_frames(1, 320.0, 320, 320, &triple);
assert_eq!(
mask_triple,
vec![false],
"triple-overlap union (120) < threshold (160) must classify silent regardless of summed sum"
);
}
#[test]
fn build_speech_frames_treats_adjacent_segments_as_contiguous() {
use core::num::NonZeroU32;
use mediatime::{TimeRange, Timebase};
let tb_16k = Timebase::new(1, NonZeroU32::new(16_000).unwrap());
let touching = sp(vec![
TimeRange::new(0, 80, tb_16k),
TimeRange::new(80, 160, tb_16k),
]);
let mask = build_speech_frames(1, 320.0, 320, 320, &touching);
assert_eq!(mask, vec![true]);
}
#[test]
fn effective_samples_per_frame_falls_back_to_nominal_for_short_chunks() {
assert_eq!(effective_samples_per_frame(0, 0, 320), 320.0);
assert_eq!(effective_samples_per_frame(0, 1, 320), 320.0);
assert!((effective_samples_per_frame(480_000, 1499, 320) - (480_000.0 / 1498.0)).abs() < 1e-9);
}
#[test]
fn build_speech_frames_uses_effective_ratio_not_nominal_hop() {
use core::num::NonZeroU32;
use mediatime::{TimeRange, Timebase};
let tb_16k = Timebase::new(1, NonZeroU32::new(16_000).unwrap());
let n_samples: u64 = 480_000;
let total_frames: usize = 1499;
let samples_per_frame =
effective_samples_per_frame(n_samples, total_frames, 320);
let mid_segment = sp(vec![TimeRange::new(240_000, 240_640, tb_16k)]);
let mask_eff = build_speech_frames(
total_frames,
samples_per_frame,
n_samples,
n_samples,
&mid_segment,
);
let mask_nom = build_speech_frames(total_frames, 320.0, n_samples, n_samples, &mid_segment);
let any_disagreement = (740..=760).any(|f| {
mask_eff.get(f).copied().unwrap_or(false) != mask_nom.get(f).copied().unwrap_or(false)
});
assert!(
any_disagreement,
"effective vs nominal mappings must disagree on at least one frame in [740, 760] \
— that's the asymmetry the unified `samples_per_frame` parameter is meant to eliminate. \
eff[740..=760] = {:?}, nom[740..=760] = {:?}",
&mask_eff[740..=760],
&mask_nom[740..=760]
);
assert!(
(samples_per_frame - 320.4272).abs() < 0.01,
"effective ratio for the 30 s edge case should be ~320.43; got {samples_per_frame}"
);
}
#[test]
fn padded_short_slice_clamps_to_real_n_samples_not_encoder_n_samples() {
let original = vec![Cow::Borrowed("hi")];
let speech_frames = vec![true; 3];
let result = compose_words(
&[one_word(0, 3, 0.9, 0)],
&original,
&speech_frames,
0,
320,
400, 200, 3,
fake_samples_to_output_range,
SpeechCoverage::DEFAULT,
DEFAULT_MAX_INTRA_SILENT_RUN,
);
assert_eq!(result.words().len(), 1);
let r = result.words()[0].range();
assert!(
r.end_pts() <= 200,
"word end {} must not exceed real_n_samples (200); padded \
boundary (400) would overlap adjacent runs",
r.end_pts()
);
}
#[test]
fn score_is_clamped_to_unit_interval() {
let original = vec![Cow::Borrowed("hi")];
let speech_frames = vec![true; 3];
let result = compose_words(
&[one_word(0, 3, 1.5, 0)], &original,
&speech_frames,
0,
320,
3 * 320,
3 * 320,
3,
fake_samples_to_output_range,
SpeechCoverage::DEFAULT,
DEFAULT_MAX_INTRA_SILENT_RUN,
);
let s = result.words()[0].score();
assert!((0.0..=1.0).contains(&s));
}
#[test]
fn longer_max_intra_silent_run_keeps_word_default_would_drop() {
let original = vec![Cow::Borrowed("ok")];
let speech_frames = vec![
true, true, false, false, false, false, false, true, true, true, true, true,
];
let default_result = compose_words(
&[one_word(0, 12, 0.9, 0)],
&original,
&speech_frames,
0,
320,
12 * 320,
12 * 320,
12,
fake_samples_to_output_range,
SpeechCoverage::DEFAULT,
DEFAULT_MAX_INTRA_SILENT_RUN,
);
assert!(
default_result.words().is_empty(),
"5-frame silent run > 80ms threshold (4 frames); must drop. got {:?}",
default_result.words()
);
let permissive = compose_words(
&[one_word(0, 12, 0.9, 0)],
&original,
&speech_frames,
0,
320,
12 * 320,
12 * 320,
12,
fake_samples_to_output_range,
SpeechCoverage::DEFAULT,
Duration::from_millis(200),
);
assert_eq!(permissive.words().len(), 1);
}
#[test]
fn stricter_min_speech_coverage_drops_word_default_would_keep() {
let original = vec![Cow::Borrowed("ok")];
let speech_frames = vec![true, true, false, true, true];
let default_result = compose_words(
&[one_word(0, 5, 0.9, 0)],
&original,
&speech_frames,
0,
320,
5 * 320,
5 * 320,
5,
fake_samples_to_output_range,
SpeechCoverage::DEFAULT,
DEFAULT_MAX_INTRA_SILENT_RUN,
);
assert_eq!(default_result.words().len(), 1);
let strict = compose_words(
&[one_word(0, 5, 0.9, 0)],
&original,
&speech_frames,
0,
320,
5 * 320,
5 * 320,
5,
fake_samples_to_output_range,
SpeechCoverage::clamped(0.9),
DEFAULT_MAX_INTRA_SILENT_RUN,
);
assert!(strict.words().is_empty());
}
}