diff --git a/crates/time/src/tempo.rs b/crates/time/src/tempo.rs index 24a8f49a..6aaa50b8 100644 --- a/crates/time/src/tempo.rs +++ b/crates/time/src/tempo.rs @@ -268,9 +268,12 @@ impl TempoMap { let first = self.meter_at_tick(0); if tick < 0 { let tpb = first.ticks_per_bar(); - let bars_back = (-tick + tpb - 1) / tpb; - let bar_tick = -bars_back * tpb; - let within = tick - bar_tick; + let abs = tick.unsigned_abs(); + let tpb_u = tpb.unsigned_abs(); + let bars_back_u = abs / tpb_u + u64::from(!abs.is_multiple_of(tpb_u)); + let bars_back = i64::try_from(bars_back_u).unwrap_or(i64::MAX); + // Offset of `tick` inside its bar, without negating `i64::MIN`. + let within = ((tpb_u - abs % tpb_u) % tpb_u) as i64; let beat_t = first.ticks_per_beat(); return BarBeat { bar: 1 - bars_back, beat: within / beat_t + 1, tick: within % beat_t }; } @@ -341,6 +344,15 @@ mod tests { assert_eq!(m.bar_start_tick(0), -TICKS_PER_QUARTER * 4); } + #[test] + fn bar_beat_at_min_tick_does_not_overflow() { + let m = TempoMap::default(); + let bb = m.bar_beat_at_tick(i64::MIN); + assert!(bb.bar < 1); + assert!(bb.beat >= 1 && bb.tick >= 0); + assert_eq!(m.bar_beat_at_tick(-1).bar, 0); + } + #[test] fn tempo_change_is_integrated() { let mut m = TempoMap::default();