//! Stage 2 scheduler timing tests — deterministic, no hardware. Asserts the click //! timeline math (subdivision spacing, swing ratio, polymeter, accents, mute). use track_format::parse; use track_format::schedule::{lane_durs, master_bar_ns, render}; const S: i64 = 1_000_000_000; // one second in ns #[test] fn quarter_notes_at_120() { let t = parse("t120;kick:4=Xxxx"); let cl = render(&t, 1); assert_eq!(cl.iter().map(|c| c.time_ns).collect::>(), vec![0, S / 2, S, 3 * S / 2]); assert_eq!(cl.iter().map(|c| c.level).collect::>(), vec![2, 1, 1, 1]); // accent on beat 1 } #[test] fn eighth_subdivision_spacing() { // hats on the quarters only (X.x.x.x.) over an 8th grid → 0, 0.5, 1.0, 1.5 s let t = parse("t120;hatClosed:4/2=X.x.x.x."); let cl = render(&t, 1); assert_eq!(cl.iter().map(|c| c.time_ns).collect::>(), vec![0, S / 2, S, 3 * S / 2]); assert_eq!(cl[0].level, 2); } #[test] fn swing_splits_two_thirds_one_third() { let t = parse("t120;ride:4/2s=Xxxxxxxx"); // all 8ths sounding, swung let durs = lane_durs(&t.lanes[0], 120, master_bar_ns(&t)); let beat = 500_000_000; assert_eq!(durs[0], beat * 2 / 3); // on-beat = long assert_eq!(durs[1], beat / 3); // off-beat = short assert!(durs[0] > durs[1]); // a long+short pair is one beat (within integer-division rounding) assert!((durs[0] + durs[1] - beat).abs() <= 1); } #[test] fn polymeter_five_over_four() { let t = parse("t100;kick:4=Xxxx;claves:5=Xxxxx~"); let mbar = master_bar_ns(&t); assert_eq!(mbar, 2_400_000_000); // 4 beats at 100 bpm // claves (lane 1) is poly: 5 steps spread evenly across the master bar assert_eq!(lane_durs(&t.lanes[1], 100, mbar), vec![480_000_000; 5]); let claves: Vec = render(&t, 1).into_iter().filter(|c| c.lane == 1).map(|c| c.time_ns).collect(); assert_eq!(claves, vec![0, 480_000_000, 960_000_000, 1_440_000_000, 1_920_000_000]); } #[test] fn accents_and_ghosts_carry_through() { let t = parse("t120;snare:4/3=..gg.gX.gg.g"); // ghosts (3) + an accent (2) let cl = render(&t, 1); assert!(cl.iter().any(|c| c.level == 3)); // ghost present assert!(cl.iter().any(|c| c.level == 2)); // accent present } #[test] fn muted_lane_is_silent() { let t = parse("t120;kick:4=Xxxx;snare:4=Xxxx!"); let cl = render(&t, 1); assert!(cl.iter().all(|c| c.lane == 0)); // the muted snare produces no clicks } #[test] fn loops_over_multiple_bars() { let t = parse("t120;kick:4=Xxxx"); assert_eq!(render(&t, 2).len(), 8); // 4 quarter-note clicks per bar × 2 bars }