use super::*;
#[test]
fn sliding_window_accessors() {
let sw = SlidingWindow::new(1.0, 2.0, 0.5);
assert_eq!(sw.start(), 1.0);
assert_eq!(sw.duration(), 2.0);
assert_eq!(sw.step(), 0.5);
}
#[test]
fn sliding_window_builders_replace_fields() {
let sw = SlidingWindow::new(0.0, 0.0, 0.0)
.with_start(1.0)
.with_duration(2.0)
.with_step(3.0);
assert_eq!(sw, SlidingWindow::new(1.0, 2.0, 3.0));
}
#[test]
fn sliding_window_is_copy() {
let sw = SlidingWindow::new(1.0, 2.0, 3.0);
let copy = sw;
assert_eq!(sw, copy);
}
#[test]
fn sliding_window_round_trips_into_dia_and_back() {
let ours = SlidingWindow::new(0.25, 4.0, 1.5);
let theirs: diaric::reconstruct::SlidingWindow = ours.into();
assert_eq!(theirs.start(), 0.25);
assert_eq!(theirs.duration(), 4.0);
assert_eq!(theirs.step(), 1.5);
let back: SlidingWindow = theirs.into();
assert_eq!(back, ours);
}
#[test]
fn options_new_matches_dia_defaults() {
let o = WindowOptions::new();
assert_eq!(o.step_samples(), 16_000);
assert_eq!(o.onset(), 0.5);
}
#[test]
fn options_default_matches_new() {
assert_eq!(WindowOptions::default(), WindowOptions::new());
}
#[test]
fn options_with_step_samples_overrides() {
let o = WindowOptions::new().with_step_samples(40_000);
assert_eq!(o.step_samples(), 40_000);
}
#[test]
fn options_set_step_samples_in_place() {
let mut o = WindowOptions::new();
o.set_step_samples(80_000);
assert_eq!(o.step_samples(), 80_000);
}
#[test]
fn options_with_onset_overrides() {
let o = WindowOptions::new().with_onset(0.7);
assert_eq!(o.onset(), 0.7);
}
#[test]
fn options_set_onset_in_place() {
let mut o = WindowOptions::new();
o.set_onset(0.3);
assert_eq!(o.onset(), 0.3);
}
#[test]
#[should_panic(expected = "step_samples must be > 0")]
fn options_with_step_samples_zero_panics() {
let _ = WindowOptions::new().with_step_samples(0);
}
#[test]
#[should_panic(expected = "step_samples must be > 0")]
fn options_set_step_samples_zero_panics() {
let mut o = WindowOptions::new();
o.set_step_samples(0);
}
#[test]
#[should_panic(expected = "step_samples must be <= SEG_CHUNK_SAMPLES")]
fn options_with_step_samples_above_chunk_panics() {
let _ = WindowOptions::new().with_step_samples(SEG_CHUNK_SAMPLES as u32 + 1);
}
#[test]
fn options_with_step_samples_equal_to_chunk_ok() {
let o = WindowOptions::new().with_step_samples(SEG_CHUNK_SAMPLES as u32);
assert_eq!(o.step_samples(), SEG_CHUNK_SAMPLES as u32);
}
#[test]
#[should_panic(expected = "onset must be finite in (0.0, 1.0]")]
fn options_with_onset_zero_panics() {
let _ = WindowOptions::new().with_onset(0.0);
}
#[test]
#[should_panic(expected = "onset must be finite in (0.0, 1.0]")]
fn options_with_onset_negative_panics() {
let _ = WindowOptions::new().with_onset(-0.1);
}
#[test]
#[should_panic(expected = "onset must be finite in (0.0, 1.0]")]
fn options_with_onset_above_one_panics() {
let _ = WindowOptions::new().with_onset(1.01);
}
#[test]
#[should_panic(expected = "onset must be finite in (0.0, 1.0]")]
fn options_with_onset_nan_panics() {
let _ = WindowOptions::new().with_onset(f32::NAN);
}
#[test]
#[should_panic(expected = "onset must be finite in (0.0, 1.0]")]
fn options_with_onset_infinity_panics() {
let _ = WindowOptions::new().with_onset(f32::INFINITY);
}
#[test]
#[should_panic(expected = "onset must be finite in (0.0, 1.0]")]
fn options_with_onset_neg_infinity_panics() {
let _ = WindowOptions::new().with_onset(f32::NEG_INFINITY);
}
#[test]
fn options_with_onset_one_ok() {
let o = WindowOptions::new().with_onset(1.0);
assert_eq!(o.onset(), 1.0);
}
#[cfg(feature = "serde")]
#[test]
fn options_serde_missing_fields_default() {
let o: WindowOptions = serde_json::from_str("{}").unwrap();
assert_eq!(o, WindowOptions::new());
}
#[cfg(feature = "serde")]
#[test]
fn options_serde_round_trips_explicit_values() {
let o: WindowOptions = serde_json::from_str(r#"{"step_samples":40000,"onset":0.7}"#).unwrap();
assert_eq!(o.step_samples(), 40_000);
assert_eq!(o.onset(), 0.7);
let json = serde_json::to_string(&o).unwrap();
assert!(json.contains("40000"), "round-tripped json: {json}");
assert!(json.contains("0.7"), "round-tripped json: {json}");
}
#[cfg(feature = "serde")]
#[test]
fn options_serde_partial_fields_default_the_rest() {
let o: WindowOptions = serde_json::from_str(r#"{"onset":0.3}"#).unwrap();
assert_eq!(o.step_samples(), 16_000);
assert_eq!(o.onset(), 0.3);
}
#[cfg(feature = "serde")]
#[test]
fn options_serde_rejects_step_samples_exceeding_window() {
let r: Result<WindowOptions, _> = serde_json::from_str(r#"{"step_samples":200000,"onset":0.5}"#);
assert!(
r.is_err(),
"step_samples 200000 > SEG_CHUNK_SAMPLES must fail to deserialize, got {r:?}"
);
}
#[cfg(feature = "serde")]
#[test]
fn options_serde_rejects_zero_step_samples() {
let r: Result<WindowOptions, _> = serde_json::from_str(r#"{"step_samples":0}"#);
assert!(
r.is_err(),
"step_samples 0 must fail to deserialize, got {r:?}"
);
}
#[cfg(feature = "serde")]
#[test]
fn options_serde_rejects_out_of_range_onset() {
for bad in [r#"{"onset":0.0}"#, r#"{"onset":1.5}"#, r#"{"onset":-0.1}"#] {
let r: Result<WindowOptions, _> = serde_json::from_str(bad);
assert!(r.is_err(), "{bad} must fail to deserialize, got {r:?}");
}
}
#[cfg(feature = "serde")]
#[test]
fn options_serde_accepts_valid_boundary_values() {
let o: WindowOptions = serde_json::from_str(r#"{"step_samples":160000,"onset":1.0}"#).unwrap();
assert_eq!(o.step_samples(), 160_000);
assert_eq!(o.onset(), 1.0);
}
#[test]
fn window_options_display_pins_the_spelling() {
assert_eq!(
WindowOptions::new().to_string(),
"step_samples=16000,onset=0.5"
);
assert_eq!(
WindowOptions::new()
.with_step_samples(8_000)
.with_onset(0.75)
.to_string(),
"step_samples=8000,onset=0.75"
);
}
#[test]
fn chunk_starts_zero_samples_yields_single_zero_padded_chunk() {
let starts = chunk_starts(0, &WindowOptions::new());
assert_eq!(starts, vec![0]);
}
#[test]
fn chunk_starts_shorter_than_one_chunk_yields_single_start() {
let starts = chunk_starts(50_000, &WindowOptions::new());
assert_eq!(starts, vec![0]);
}
#[test]
fn chunk_starts_exactly_one_chunk_yields_single_start() {
let starts = chunk_starts(SEG_CHUNK_SAMPLES, &WindowOptions::new());
assert_eq!(starts, vec![0]);
}
#[test]
fn chunk_starts_chunk_plus_one_sample_yields_two_chunks() {
let starts = chunk_starts(SEG_CHUNK_SAMPLES + 1, &WindowOptions::new());
assert_eq!(starts, vec![0, 16_000]);
}
#[test]
fn chunk_starts_chunk_plus_step_yields_two_chunks_exact_fit() {
let starts = chunk_starts(SEG_CHUNK_SAMPLES + 16_000, &WindowOptions::new());
assert_eq!(starts, vec![0, 16_000]);
}
#[test]
fn chunk_starts_chunk_plus_step_plus_one_yields_three_chunks() {
let starts = chunk_starts(SEG_CHUNK_SAMPLES + 16_000 + 1, &WindowOptions::new());
assert_eq!(starts, vec![0, 16_000, 32_000]);
}
#[test]
fn chunk_starts_multi_chunk_regular_grid() {
let starts = chunk_starts(SEG_CHUNK_SAMPLES + 3 * 16_000, &WindowOptions::new());
assert_eq!(starts, vec![0, 16_000, 32_000, 48_000]);
}
#[test]
fn chunk_starts_custom_step() {
let options = WindowOptions::new().with_step_samples(40_000);
let starts = chunk_starts(200_000, &options);
assert_eq!(starts, vec![0, 40_000]);
}
#[test]
fn chunk_starts_step_equal_to_chunk_no_overlap() {
let options = WindowOptions::new().with_step_samples(SEG_CHUNK_SAMPLES as u32);
let starts = chunk_starts(2 * SEG_CHUNK_SAMPLES, &options);
assert_eq!(starts, vec![0, SEG_CHUNK_SAMPLES]);
}
#[test]
#[should_panic(expected = "step_samples must be > 0")]
fn chunk_starts_panics_on_zero_step_via_bypassed_options() {
let options = WindowOptions {
step_samples: 0,
onset: DEFAULT_ONSET,
};
let _ = chunk_starts(1_000_000, &options);
}
#[test]
fn chunk_sliding_window_matches_dia_defaults() {
let sw = chunk_sliding_window(&WindowOptions::new());
assert_eq!(sw.start(), 0.0);
assert_eq!(sw.duration(), 10.0);
assert_eq!(sw.step(), 1.0); }
#[test]
fn chunk_sliding_window_reflects_custom_step() {
let options = WindowOptions::new().with_step_samples(40_000);
let sw = chunk_sliding_window(&options);
assert_eq!(sw.step(), 2.5); }
#[test]
fn frame_sliding_window_matches_dia_constants() {
let sw = frame_sliding_window();
assert_eq!(sw.start(), 0.0);
assert_eq!(sw.duration(), 0.0619375);
assert_eq!(sw.step(), 0.016875);
}
fn three_chunk_overlap_segmentations() -> Vec<f64> {
#[rustfmt::skip]
let segs = vec![
1.0, 0.0, 1.0, 1.0, 0.5, 0.0, 0.0, 0.0, 1.0, 1.0, 0.4, 0.6, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, ];
segs
}
#[test]
fn count_from_segmentations_single_chunk_no_overlap() {
#[rustfmt::skip]
let segmentations = vec![
1.0, 0.0, 1.0, 1.0, 0.0, 0.0, ];
let chunks_sw = SlidingWindow::new(0.0, 3.0, 1.0);
let frames_sw = SlidingWindow::new(0.0, 1.0, 1.0);
let got = count_from_segmentations(&segmentations, 1, 3, 2, 0.5, chunks_sw, frames_sw);
assert_eq!(got, vec![1, 2, 0, 0]);
}
#[test]
fn count_from_segmentations_hand_computed_3_chunk_overlap() {
let segmentations = three_chunk_overlap_segmentations();
let chunks_sw = SlidingWindow::new(0.0, 4.0, 2.0);
let frames_sw = SlidingWindow::new(0.0, 1.0, 1.0);
let got = count_from_segmentations(&segmentations, 3, 4, 2, 0.5, chunks_sw, frames_sw);
assert_eq!(got, vec![1, 2, 2, 0, 1, 2, 1, 0, 0]);
}
#[test]
fn try_aggregate_output_frame_count_reproduces_the_hand_computed_overlap() {
#[rustfmt::skip]
let chunk_count = [
1.0, 2.0, 1.0, 0.0, 2.0, 1.0, 1.0, 2.0, 1.0, 2.0, 1.0, 0.0, ];
let got = try_aggregate_output_frame_count(
&chunk_count,
3,
4,
SlidingWindow::new(0.0, 4.0, 2.0),
SlidingWindow::new(0.0, 1.0, 1.0),
)
.expect("valid geometry: num_output_frames fits usize");
assert_eq!(got, vec![1, 2, 2, 0, 1, 2, 1, 0, 0]);
}
#[test]
fn count_from_segmentations_matches_dia_oracle_3_chunk_overlap() {
let segmentations = three_chunk_overlap_segmentations();
let ours = count_from_segmentations(
&segmentations,
3,
4,
2,
0.5,
SlidingWindow::new(0.0, 4.0, 2.0),
SlidingWindow::new(0.0, 1.0, 1.0),
);
let golden = diaric::aggregate::try_count_pyannote(
&segmentations,
3,
4,
2,
0.5_f64,
diaric::reconstruct::SlidingWindow::new(0.0, 4.0, 2.0),
diaric::reconstruct::SlidingWindow::new(0.0, 1.0, 1.0),
&diaric::spill::SpillOptions::default(),
)
.expect("dia try_count_pyannote on synthetic 3-chunk overlap");
assert_eq!(ours.as_slice(), golden.count_slice());
assert_eq!(ours, vec![1, 2, 2, 0, 1, 2, 1, 0, 0]);
}
const DEFAULT_10X_NUM_CHUNKS: usize = 15;
const DEFAULT_10X_NUM_FRAMES_PER_CHUNK: usize = 589;
const DEFAULT_10X_NUM_SPEAKERS: usize = 2;
fn default_geometry_10x_overlap_segmentations() -> Vec<f64> {
let mut segs =
vec![
0.0_f64;
DEFAULT_10X_NUM_CHUNKS * DEFAULT_10X_NUM_FRAMES_PER_CHUNK * DEFAULT_10X_NUM_SPEAKERS
];
for c in 0..DEFAULT_10X_NUM_CHUNKS {
for f in 0..DEFAULT_10X_NUM_FRAMES_PER_CHUNK {
for s in 0..DEFAULT_10X_NUM_SPEAKERS {
let idx = (c * DEFAULT_10X_NUM_FRAMES_PER_CHUNK + f) * DEFAULT_10X_NUM_SPEAKERS + s;
segs[idx] = ((c * 37 + f * 17 + s * 11) % 10) as f64 / 10.0;
}
}
}
let overrides: [(usize, usize, bool); 10] = [
(3, 533, true),
(4, 474, true),
(5, 415, true),
(6, 355, true),
(7, 296, true),
(8, 237, false),
(9, 178, false),
(10, 118, false),
(11, 59, false),
(12, 0, false),
];
for (c, f, active) in overrides {
let base = (c * DEFAULT_10X_NUM_FRAMES_PER_CHUNK + f) * DEFAULT_10X_NUM_SPEAKERS;
segs[base] = if active { 1.0 } else { 0.0 }; segs[base + 1] = 0.0; }
segs
}
#[test]
fn count_from_segmentations_matches_dia_oracle_default_geometry_10x_overlap() {
let segmentations = default_geometry_10x_overlap_segmentations();
let chunks_sw = chunk_sliding_window(&WindowOptions::new());
let frames_sw = frame_sliding_window();
let ours = count_from_segmentations(
&segmentations,
DEFAULT_10X_NUM_CHUNKS,
DEFAULT_10X_NUM_FRAMES_PER_CHUNK,
DEFAULT_10X_NUM_SPEAKERS,
0.5,
chunks_sw,
frames_sw,
);
let golden = diaric::aggregate::try_count_pyannote(
&segmentations,
DEFAULT_10X_NUM_CHUNKS,
DEFAULT_10X_NUM_FRAMES_PER_CHUNK,
DEFAULT_10X_NUM_SPEAKERS,
0.5_f64,
chunks_sw.into(),
frames_sw.into(),
&diaric::spill::SpillOptions::default(),
)
.expect("dia try_count_pyannote on default-geometry 10x-overlap synthetic fixture");
assert_eq!(ours.as_slice(), golden.count_slice());
assert_eq!(ours[711], 0);
}
#[test]
#[should_panic(expected = "num_chunks must be at least 1")]
fn count_from_segmentations_panics_on_zero_num_chunks() {
let _ = count_from_segmentations(
&[],
0,
4,
2,
0.5,
SlidingWindow::new(0.0, 10.0, 1.0),
frame_sliding_window(),
);
}
#[test]
#[should_panic(expected = "num_frames_per_chunk must be at least 1")]
fn count_from_segmentations_panics_on_zero_num_frames_per_chunk() {
let _ = count_from_segmentations(
&[],
3,
0,
2,
0.5,
SlidingWindow::new(0.0, 10.0, 1.0),
frame_sliding_window(),
);
}
#[test]
#[should_panic(expected = "num_speakers must be at least 1")]
fn count_from_segmentations_panics_on_zero_num_speakers() {
let _ = count_from_segmentations(
&[],
3,
4,
0,
0.5,
SlidingWindow::new(0.0, 10.0, 1.0),
frame_sliding_window(),
);
}
#[test]
#[should_panic(expected = "chunks_sw.duration() must be a positive finite scalar")]
fn count_from_segmentations_panics_on_zero_chunk_duration() {
let segs = vec![0.0; 3 * 4 * 2];
let _ = count_from_segmentations(
&segs,
3,
4,
2,
0.5,
SlidingWindow::new(0.0, 0.0, 1.0),
frame_sliding_window(),
);
}
#[test]
#[should_panic(expected = "chunks_sw.step() must be a positive finite scalar")]
fn count_from_segmentations_panics_on_negative_chunk_step() {
let segs = vec![0.0; 3 * 4 * 2];
let _ = count_from_segmentations(
&segs,
3,
4,
2,
0.5,
SlidingWindow::new(0.0, 10.0, -1.0),
frame_sliding_window(),
);
}
#[test]
#[should_panic(expected = "frames_sw.duration() must be a positive finite scalar")]
fn count_from_segmentations_panics_on_non_finite_frame_duration() {
let segs = vec![0.0; 3 * 4 * 2];
let _ = count_from_segmentations(
&segs,
3,
4,
2,
0.5,
SlidingWindow::new(0.0, 10.0, 1.0),
SlidingWindow::new(0.0, f64::NAN, 0.016875),
);
}
#[test]
#[should_panic(expected = "frames_sw.step() must be a positive finite scalar")]
fn count_from_segmentations_panics_on_non_finite_frame_step() {
let segs = vec![0.0; 3 * 4 * 2];
let _ = count_from_segmentations(
&segs,
3,
4,
2,
0.5,
SlidingWindow::new(0.0, 10.0, 1.0),
SlidingWindow::new(0.0, 0.0619375, f64::INFINITY),
);
}
#[test]
#[should_panic(expected = "onset must be finite")]
fn count_from_segmentations_panics_on_non_finite_onset() {
let segs = vec![0.0; 3 * 4 * 2];
let _ = count_from_segmentations(
&segs,
3,
4,
2,
f32::NAN,
SlidingWindow::new(0.0, 10.0, 1.0),
frame_sliding_window(),
);
}
#[test]
#[should_panic(
expected = "segmentations.len() must equal num_chunks * num_frames_per_chunk * num_speakers"
)]
fn count_from_segmentations_panics_on_length_mismatch() {
let segs = vec![0.0; 23]; let _ = count_from_segmentations(
&segs,
3,
4,
2,
0.5,
SlidingWindow::new(0.0, 10.0, 1.0),
frame_sliding_window(),
);
}
#[test]
#[should_panic(expected = "segmentations must not contain NaN/infinite values")]
fn count_from_segmentations_panics_on_non_finite_segmentation_value() {
let mut segs = vec![0.0; 24];
segs[5] = f64::NAN;
let _ = count_from_segmentations(
&segs,
3,
4,
2,
0.5,
SlidingWindow::new(0.0, 10.0, 1.0),
frame_sliding_window(),
);
}
#[test]
fn try_num_output_frames_accepts_valid_geometry() {
assert_eq!(try_num_output_frames(8.0, 1.0), Ok(9));
}
#[test]
fn try_num_output_frames_rejects_infinite_division() {
assert_eq!(
try_num_output_frames(1e300, 1e-300),
Err(WindowError::OutputFrameCountOverflow)
);
}
#[test]
fn try_num_output_frames_rejects_finite_but_saturating_division() {
assert_eq!(
try_num_output_frames(1e20, 1.0),
Err(WindowError::OutputFrameCountOverflow)
);
}
#[test]
#[should_panic(expected = "num_output_frames must fit in usize")]
fn count_from_segmentations_panics_on_output_frame_count_overflow() {
let segs = vec![0.0; 1]; let chunks_sw = SlidingWindow::new(0.0, 1e300, 1.0);
let frames_sw = SlidingWindow::new(0.0, 0.0619375, 1e-300);
let _ = count_from_segmentations(&segs, 1, 1, 1, 0.5, chunks_sw, frames_sw);
}
fn diaric_required_output_frames(
num_chunks: usize,
num_frames_per_chunk: usize,
chunks_sw: SlidingWindow,
frames_sw: SlidingWindow,
) -> usize {
const SPEAKERS: usize = 3;
let segmentations = vec![0.0f64; num_chunks * num_frames_per_chunk * SPEAKERS];
let hard: Vec<diaric::pipeline::ChunkAssignment> = vec![[0, -2, -2]; num_chunks];
let count = vec![0u8; 1];
let input = diaric::reconstruct::ReconstructInput::new(
&segmentations,
num_chunks,
num_frames_per_chunk,
SPEAKERS,
&hard,
&count,
1,
chunks_sw.into(),
frames_sw.into(),
);
match diaric::reconstruct::reconstruct(&input) {
Err(diaric::reconstruct::Error::Shape(
diaric::reconstruct::ShapeError::OutputFrameCountTooSmall { required, .. },
)) => required,
other => panic!("expected OutputFrameCountTooSmall to report the placement, got {other:?}"),
}
}
fn diaric_last_chunk_start_frame(
num_chunks: usize,
num_frames_per_chunk: usize,
chunks_sw: SlidingWindow,
frames_sw: SlidingWindow,
) -> i64 {
(diaric_required_output_frames(num_chunks, num_frames_per_chunk, chunks_sw, frames_sw)
- num_frames_per_chunk) as i64
}
#[test]
fn reconstruct_chunk_start_frame_mirrors_diarics_own_placement() {
let community1_frames = frame_sliding_window();
let cases: [(usize, usize, SlidingWindow, SlidingWindow); 5] = [
(
4,
589,
chunk_sliding_window(&WindowOptions::new()),
community1_frames,
),
(
3,
2,
SlidingWindow::new(0.0, 1.0, 1.0),
SlidingWindow::new(0.0, 1.0, 1.0),
),
(
3,
2,
SlidingWindow::new(1.0, 1.0, 1.0),
SlidingWindow::new(1.0, 1.0, 1.0),
),
(
2,
2,
SlidingWindow::new(-1.0, 1.0, 1.0),
SlidingWindow::new(0.0, 1.0, 1.0),
),
(
2,
1,
SlidingWindow::new(0.0, 0.04218750000000001, 0.04218750000000001),
SlidingWindow::new(0.0, 0.0619375, 0.016875),
),
];
for (num_chunks, num_frames_per_chunk, chunks_sw, frames_sw) in cases {
let mirrored = reconstruct_chunk_start_frame(num_chunks - 1, chunks_sw, frames_sw);
let actual =
diaric_last_chunk_start_frame(num_chunks, num_frames_per_chunk, chunks_sw, frames_sw);
assert_eq!(
mirrored, actual,
"mirror drifted from diaric for chunks_sw={chunks_sw:?} frames_sw={frames_sw:?}"
);
}
}
#[test]
fn the_two_chunk_mappings_agree_on_the_production_grid_and_split_on_the_finding_b_grid() {
let chunks_sw = chunk_sliding_window(&WindowOptions::new());
let frames_sw = frame_sliding_window();
for c in 0..43_200 {
assert_eq!(
aggregate_chunk_start_frame(c, chunks_sw.step(), frames_sw.step()),
reconstruct_chunk_start_frame(c, chunks_sw, frames_sw),
"production grid, chunk {c}"
);
}
let d = 0.04218750000000001_f64;
let chunks_sw = SlidingWindow::new(0.0, d, d);
let frames_sw = SlidingWindow::new(0.0, 0.0619375, 0.016875);
assert_eq!(
aggregate_chunk_start_frame(1, chunks_sw.step(), frames_sw.step()),
3
);
assert_eq!(reconstruct_chunk_start_frame(1, chunks_sw, frames_sw), 2);
let sw = SlidingWindow::new(1.0, 1.0, 1.0);
for c in 0..8 {
assert_eq!(
aggregate_chunk_start_frame(c, sw.step(), sw.step()),
reconstruct_chunk_start_frame(c, sw, sw),
"cancelling origins, chunk {c}"
);
}
}
#[test]
fn uncovered_last_chunks_requirement_is_diarics_own_required_measured_not_copied() {
let community1_frames = frame_sliding_window();
let cases: [(usize, usize, SlidingWindow, SlidingWindow); 6] = [
(
4,
589,
chunk_sliding_window(&WindowOptions::new()),
community1_frames,
),
(
3,
594,
chunk_sliding_window(&WindowOptions::new()),
community1_frames,
),
(
1,
595,
chunk_sliding_window(&WindowOptions::new()),
community1_frames,
),
(
3,
2,
SlidingWindow::new(0.0, 1.0, 1.0),
SlidingWindow::new(0.0, 1.0, 1.0),
),
(
3,
2,
SlidingWindow::new(1.0, 1.0, 1.0),
SlidingWindow::new(1.0, 1.0, 1.0),
),
(
2,
1,
SlidingWindow::new(0.0, 0.04218750000000001, 0.04218750000000001),
SlidingWindow::new(0.0, 0.0619375, 0.016875),
),
];
for (num_chunks, num_frames_per_chunk, chunks_sw, frames_sw) in cases {
let actual =
diaric_required_output_frames(num_chunks, num_frames_per_chunk, chunks_sw, frames_sw);
let u = uncovered_last_chunk(
num_chunks,
num_frames_per_chunk,
actual - 1,
chunks_sw,
frames_sw,
)
.expect("a grid one frame short of diaric's requirement must be refused");
assert_eq!(
u.required(),
actual,
"predicted requirement drifted from diaric for {chunks_sw:?} / {frames_sw:?}"
);
assert_eq!(u.got(), actual - 1);
assert_eq!(
u.start_frame(),
reconstruct_chunk_start_frame(num_chunks - 1, chunks_sw, frames_sw)
);
assert_eq!(
uncovered_last_chunk(
num_chunks,
num_frames_per_chunk,
actual,
chunks_sw,
frames_sw
),
None,
"diaric's own requirement must be admitted"
);
}
}
#[test]
fn uncovered_last_chunk_is_silent_below_frame_zero() {
let chunks_sw = SlidingWindow::new(-1.0, 1.0, 1.0);
let frames_sw = SlidingWindow::new(0.0, 1.0, 1.0);
assert_eq!(reconstruct_chunk_start_frame(0, chunks_sw, frames_sw), -1);
assert_eq!(
uncovered_last_chunk(1, 9_999, 1, chunks_sw, frames_sw),
None,
"a negative placement must not be refused here"
);
}
fn diaric_span_start_and_duration(
t_start: usize,
t_end: usize,
num_frames: usize,
frames_sw: SlidingWindow,
) -> (f64, f64) {
assert!(t_end < num_frames, "the run must CLOSE inside the grid");
let mut grid = vec![0.0f32; num_frames];
grid[t_start..t_end].fill(1.0);
let spans = diaric::reconstruct::discrete_to_spans(&grid, num_frames, 1, frames_sw.into(), 0.0);
assert_eq!(spans.len(), 1, "one contiguous run is one span");
(spans[0].start(), spans[0].duration())
}
#[test]
fn frame_center_mirrors_diarics_own_span_conversion() {
let cases: [(usize, usize, usize, SlidingWindow); 5] = [
(0, 1, 8, frame_sliding_window()),
(13, 37, 64, frame_sliding_window()),
(0, 1, 4, SlidingWindow::new(0.0, 1.0, 1.0)),
(2, 5, 8, SlidingWindow::new(-1.25, 0.1, 0.3)),
(1, 3, 6, SlidingWindow::new(1e9, 1e-6, 1e-6)),
];
for (t_start, t_end, num_frames, frames_sw) in cases {
let (start, duration) = diaric_span_start_and_duration(t_start, t_end, num_frames, frames_sw);
let (lo, hi) = (
frame_center(t_start, frames_sw),
frame_center(t_end, frames_sw),
);
assert_eq!(
(lo, hi - lo),
(start, duration),
"mirror drifted from diaric's span conversion for frames_sw={frames_sw:?}"
);
}
}
#[test]
fn first_collapsed_frame_center_finds_the_first_repeated_center() {
assert_eq!(
first_collapsed_frame_center(
crate::audio::speaker::extract::MAX_OUTPUT_FRAMES,
frame_sliding_window()
),
None,
"the only frame grid this crate's models produce must never collapse"
);
let collapsing = SlidingWindow::new(1e9, 1e-8, 1e-8);
assert_eq!(
f64::from_bits(1e9f64.to_bits() + 1) - 1e9,
1.1920928955078125e-7
);
assert_eq!(1e9f64 + 1e-8, 1e9);
let c = first_collapsed_frame_center(4, collapsing).expect("this grid collapses");
assert_eq!((c.frame(), c.center(), c.previous()), (1, 1e9, 1e9));
assert_eq!(first_collapsed_frame_center(1, collapsing), None);
assert_eq!(first_collapsed_frame_center(0, collapsing), None);
let half_ulp = SlidingWindow::new(0.0, 0.0, f64::EPSILON / 2.0).with_start(1.0);
let centers: Vec<f64> = (0..4).map(|t| frame_center(t, half_ulp)).collect();
assert_eq!(centers[0], centers[1], "ties-to-even keeps frame 1 at 1.0");
assert!(centers[2] > centers[1], "frame 2 does separate");
assert_eq!(
first_collapsed_frame_center(4, half_ulp).map(|c| c.frame()),
Some(1)
);
assert_eq!(
first_collapsed_frame_center(4, half_ulp.with_start(1.0 + f64::EPSILON)).map(|c| c.frame()),
Some(2),
"shifting the origin by one ULP moves WHICH pair collapses — the reason \
this is an adjacent-pair scan and not an endpoint test"
);
let overflowing = SlidingWindow::new(f64::MAX, f64::MAX, 1.0);
let c = first_collapsed_frame_center(2, overflowing).expect("start + duration/2 overflows");
assert_eq!((c.frame(), c.previous()), (0, f64::NEG_INFINITY));
assert!(!c.center().is_finite());
}