use sim_kernel::{DatumStore, Ref, Symbol};
use sim_lib_stream_core::ClockDomain;
use crate::{
Clock, ClockChart, ClockIndex, Instant, StreamClockDescriptor, TempoMap, TempoSegment,
};
use sim_kernel::testing::bare_cx as cx;
#[test]
fn frame_indexes_round_trip_for_exact_second_multiples() {
let clock = Clock::frame(Symbol::qualified("clock", "sample"), 48_000).unwrap();
let conversion = clock.index_for_instant(Instant::seconds(2)).unwrap();
assert!(conversion.is_exact());
assert_eq!(clock.domain(), ClockDomain::Sample);
assert_eq!(conversion.index().value(), 96_000);
assert_eq!(
clock.instant_for_index(conversion.index()).unwrap(),
Instant::seconds(2)
);
}
#[test]
fn non_multiple_conversion_emits_diagnostic() {
let clock = Clock::frame(Symbol::qualified("clock", "sample"), 48_000).unwrap();
let instant = Instant::new(1, 96_000).unwrap();
let conversion = clock.index_for_instant(instant).unwrap();
assert_eq!(conversion.index().value(), 0);
assert_eq!(conversion.diagnostics().len(), 1);
assert!(
conversion.diagnostics()[0]
.message
.contains("not an exact frame")
);
}
#[test]
fn negative_denominator_does_not_bypass_non_negative_instants() {
let err = Instant::new(1, -2).unwrap_err();
assert!(format!("{err}").contains("non-negative"));
}
#[test]
fn single_tempo_midi_ticks_match_hand_computation() {
let tempo_map = TempoMap::single(500_000).unwrap();
let clock = Clock::midi(Symbol::qualified("clock", "midi"), 480, tempo_map).unwrap();
let conversion = clock.index_for_instant(Instant::seconds(1)).unwrap();
assert!(conversion.is_exact());
assert_eq!(conversion.index().value(), 960);
assert_eq!(
clock.instant_for_index(conversion.index()).unwrap(),
Instant::seconds(1)
);
}
#[test]
fn tempo_map_requires_segment_at_tick_zero() {
let err = TempoMap::new(vec![TempoSegment::new(10, 500_000).unwrap()]).unwrap_err();
assert!(format!("{err}").contains("tick 0"));
}
#[test]
fn increasing_instants_produce_non_decreasing_indexes() {
let clock = Clock::frame(Symbol::qualified("clock", "sample"), 44_100).unwrap();
let instants = [
Instant::new(0, 1).unwrap(),
Instant::new(1, 88_200).unwrap(),
Instant::new(1, 44_100).unwrap(),
Instant::new(1, 1_000).unwrap(),
Instant::seconds(1),
];
let indexes = instants
.into_iter()
.map(|instant| clock.index_for_instant(instant).unwrap().index().value())
.collect::<Vec<_>>();
assert!(indexes.windows(2).all(|pair| pair[0] <= pair[1]));
}
#[test]
fn clock_index_interns_as_kernel_tick_content_ref() {
let mut cx = cx();
let clock = Clock::frame(Symbol::qualified("clock", "sample"), 48_000).unwrap();
let tick = clock
.tick_for_index(&mut cx, ClockIndex::new(24_000))
.unwrap();
assert_eq!(tick.clock, Symbol::qualified("clock", "sample"));
let Ref::Content(id) = tick.index else {
panic!("clock index should be interned as content ref");
};
assert!(cx.datum_store().contains(&id));
}
#[test]
fn citizen_clock_descriptors_round_trip_and_fail_closed() {
let frame = StreamClockDescriptor::frame(Symbol::qualified("clock", "sample"), 48_000)
.expect("frame descriptor");
let rebuilt = frame.clock().expect("frame clock");
assert_eq!(rebuilt.id(), &Symbol::qualified("clock", "sample"));
assert_eq!(rebuilt.domain(), ClockDomain::Sample);
assert_eq!(
rebuilt.chart(),
&ClockChart::Frames {
frames_per_second: 48_000
}
);
let midi = StreamClockDescriptor::midi(
Symbol::qualified("clock", "midi"),
480,
TempoMap::single(500_000).unwrap(),
)
.expect("midi descriptor");
let rebuilt = midi.clock().expect("midi clock");
assert_eq!(rebuilt.id(), &Symbol::qualified("clock", "midi"));
assert_eq!(rebuilt.domain(), ClockDomain::MidiTick);
assert!(matches!(rebuilt.chart(), ClockChart::Midi { tpq: 480, .. }));
let err = StreamClockDescriptor::frame(Symbol::qualified("clock", "bad"), 0).unwrap_err();
assert!(format!("{err}").contains("non-zero"));
}