use super::{TickSchedule, interval_ns_from_bpm};
#[derive(Debug)]
pub(crate) struct MasterClock {
tempo_bpm: f64,
interval_ns: u64,
next_tick_ns: u64,
subdivision: u8,
beat: u64,
}
impl MasterClock {
pub(crate) fn new(tempo_bpm: f64, start_ns: u64) -> Self {
let interval_ns = interval_ns_from_bpm(tempo_bpm);
Self {
tempo_bpm,
interval_ns,
next_tick_ns: start_ns,
subdivision: 0,
beat: 0,
}
}
pub(crate) fn next_tick(&self) -> TickSchedule {
TickSchedule {
next_tick_ns: self.next_tick_ns,
interval_ns: self.interval_ns,
subdivision: self.subdivision,
beat: self.beat,
}
}
pub(crate) fn advance(&mut self) {
self.next_tick_ns += self.interval_ns;
self.subdivision += 1;
if self.subdivision >= 24 {
self.subdivision = 0;
self.beat += 1;
}
}
pub(crate) fn set_tempo(&mut self, bpm: f64) {
self.tempo_bpm = bpm;
self.interval_ns = interval_ns_from_bpm(bpm);
}
pub(crate) fn reset(&mut self) {
self.beat = 0;
self.subdivision = 0;
}
pub(crate) fn tempo(&self) -> f64 {
self.tempo_bpm
}
#[cfg(test)]
pub(crate) fn beat(&self) -> u64 {
self.beat
}
#[cfg(test)]
pub(crate) fn subdivision(&self) -> u8 {
self.subdivision
}
pub(crate) fn set_position_from_spp(&mut self, midi_beats: u16) {
self.beat = (midi_beats / 4) as u64;
self.subdivision = ((midi_beats % 4) * 6) as u8;
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_new_initial_state() {
let clock = MasterClock::new(120.0, 1000);
assert_eq!(clock.subdivision(), 0);
assert_eq!(clock.beat(), 0);
assert_eq!(clock.tempo(), 120.0);
}
#[test]
fn test_interval_at_120_bpm() {
let clock = MasterClock::new(120.0, 0);
let schedule = clock.next_tick();
assert_eq!(schedule.interval_ns, 20_833_333);
}
#[test]
fn test_subdivision_wraps_at_24() {
let mut clock = MasterClock::new(120.0, 0);
for _ in 0..24 {
clock.advance();
}
assert_eq!(clock.subdivision(), 0);
assert_eq!(clock.beat(), 1);
}
#[test]
fn test_beat_increments_correctly() {
let mut clock = MasterClock::new(120.0, 0);
for _ in 0..48 {
clock.advance();
}
assert_eq!(clock.beat(), 2);
assert_eq!(clock.subdivision(), 0);
}
#[test]
fn test_advance_schedules_are_drift_free() {
let start_ns: u64 = 0;
let mut clock = MasterClock::new(120.0, start_ns);
let interval = clock.next_tick().interval_ns;
assert_eq!(interval, 20_833_333);
for _ in 0..24_000 {
clock.advance();
}
let expected = start_ns + 24_000 * 20_833_333_u64;
assert_eq!(clock.next_tick().next_tick_ns, expected);
}
#[test]
fn test_set_tempo_changes_interval() {
let mut clock = MasterClock::new(120.0, 0);
for _ in 0..5 {
clock.advance();
}
let schedule_before = clock.next_tick();
assert_eq!(schedule_before.interval_ns, 20_833_333);
clock.set_tempo(140.0);
assert_eq!(clock.next_tick().next_tick_ns, schedule_before.next_tick_ns);
let expected_interval = interval_ns_from_bpm(140.0);
assert_eq!(clock.next_tick().interval_ns, expected_interval);
assert_eq!(clock.tempo(), 140.0);
clock.advance();
let expected_next = schedule_before.next_tick_ns + expected_interval;
assert_eq!(clock.next_tick().next_tick_ns, expected_next);
}
#[test]
fn test_reset_clears_position() {
let mut clock = MasterClock::new(120.0, 1000);
for _ in 0..30 {
clock.advance();
}
assert_eq!(clock.beat(), 1);
assert_eq!(clock.subdivision(), 6);
let next_before = clock.next_tick().next_tick_ns;
let tempo_before = clock.tempo();
clock.reset();
assert_eq!(clock.beat(), 0);
assert_eq!(clock.subdivision(), 0);
assert_eq!(clock.tempo(), tempo_before);
assert_eq!(clock.next_tick().next_tick_ns, next_before);
}
#[test]
fn test_set_position_from_spp() {
let mut clock = MasterClock::new(120.0, 0);
clock.set_position_from_spp(96);
assert_eq!(clock.beat(), 24);
assert_eq!(clock.subdivision(), 0);
clock.set_position_from_spp(7);
assert_eq!(clock.beat(), 1);
assert_eq!(clock.subdivision(), 18);
}
#[test]
fn test_next_tick_returns_current_schedule() {
let clock = MasterClock::new(120.0, 5000);
let first = clock.next_tick();
let second = clock.next_tick();
assert_eq!(first, second);
}
}