use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ClockTick {
pub sample_pos: u64,
pub samples_since_last: u32,
pub is_new_block: bool,
pub sample_rate: f32,
pub tempo: Option<f32>,
}
impl ClockTick {
pub const fn new(sample_pos: u64, samples_since_last: u32, sample_rate: f32) -> Self {
Self {
sample_pos,
samples_since_last,
is_new_block: true,
sample_rate,
tempo: None,
}
}
pub const fn with_tempo(
sample_pos: u64,
samples_since_last: u32,
sample_rate: f32,
tempo: f32,
) -> Self {
Self {
sample_pos,
samples_since_last,
is_new_block: true,
sample_rate,
tempo: Some(tempo),
}
}
#[inline(always)]
pub fn delta_seconds(&self) -> f32 {
self.samples_since_last as f32 / self.sample_rate
}
#[inline(always)]
pub fn absolute_seconds(&self) -> f64 {
self.sample_pos as f64 / self.sample_rate as f64
}
#[inline(always)]
pub fn beat_position(&self) -> Option<f64> {
self.tempo.map(|bpm| {
let seconds_per_beat = 60.0 / bpm as f64;
self.absolute_seconds() / seconds_per_beat
})
}
pub fn musical_position(&self) -> Option<(u32, u8, u8)> {
self.tempo.map(|bpm| {
let seconds_per_beat = 60.0 / bpm as f64;
let total_beats = self.absolute_seconds() / seconds_per_beat;
let bar = (total_beats / 4.0).floor() as u32;
let beat_in_bar = (total_beats % 4.0) as u8;
let sixteenth = ((total_beats.fract() * 4.0) as u8) % 4;
(bar, beat_in_bar, sixteenth)
})
}
pub fn advance(&mut self, samples: u32) {
self.sample_pos += samples as u64;
self.samples_since_last = samples;
self.is_new_block = true;
}
pub fn is_new_bar(&self) -> bool {
if let Some((_, beat, sixteenth)) = self.musical_position() {
beat == 0 && sixteenth == 0
} else {
false
}
}
pub fn is_new_beat(&self) -> bool {
if let Some((_, _, sixteenth)) = self.musical_position() {
sixteenth == 0
} else {
false
}
}
}
impl Default for ClockTick {
fn default() -> Self {
Self {
sample_pos: 0,
samples_since_last: 0,
is_new_block: false,
sample_rate: 44100.0,
tempo: None,
}
}
}
impl fmt::Display for ClockTick {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"ClockTick(pos={}, delta={}ms, rate={}Hz",
self.sample_pos,
self.delta_seconds() * 1000.0,
self.sample_rate,
)?;
if let Some(tempo) = self.tempo {
write!(f, ", tempo={}BPM", tempo)?;
}
write!(f, ")")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_clock_tick_creation() {
let tick = ClockTick::new(44100, 44100, 44100.0);
assert_eq!(tick.sample_pos, 44100);
assert_eq!(tick.samples_since_last, 44100);
assert!(tick.is_new_block);
assert_eq!(tick.sample_rate, 44100.0);
assert_eq!(tick.tempo, None);
}
#[test]
fn test_clock_tick_with_tempo() {
let tick = ClockTick::with_tempo(44100, 44100, 44100.0, 120.0);
assert_eq!(tick.tempo, Some(120.0));
}
#[test]
fn test_delta_seconds() {
let tick = ClockTick::new(0, 44100, 44100.0);
assert_eq!(tick.delta_seconds(), 1.0);
let tick = ClockTick::new(0, 22050, 44100.0);
assert_eq!(tick.delta_seconds(), 0.5);
}
#[test]
fn test_absolute_seconds() {
let tick = ClockTick::new(44100, 44100, 44100.0);
assert_eq!(tick.absolute_seconds(), 1.0);
let tick = ClockTick::new(88200, 44100, 44100.0);
assert_eq!(tick.absolute_seconds(), 2.0);
}
#[test]
fn test_beat_position() {
let tick = ClockTick::with_tempo(44100, 44100, 44100.0, 120.0);
assert_eq!(tick.beat_position(), Some(2.0));
}
#[test]
fn test_musical_position() {
let tick = ClockTick::with_tempo(44100 * 2, 44100, 44100.0, 120.0);
let pos = tick.musical_position();
assert_eq!(pos, Some((1, 0, 0)));
let tick = ClockTick::with_tempo(44100 * 3, 44100, 44100.0, 120.0);
let pos = tick.musical_position();
assert_eq!(pos, Some((1, 2, 0)));
}
#[test]
fn test_advance() {
let mut tick = ClockTick::new(0, 0, 44100.0);
tick.advance(64);
assert_eq!(tick.sample_pos, 64);
assert_eq!(tick.samples_since_last, 64);
assert!(tick.is_new_block);
}
#[test]
fn test_is_new_bar() {
let tick = ClockTick::with_tempo(0, 0, 44100.0, 120.0);
assert!(tick.is_new_bar());
let tick = ClockTick::with_tempo(22050, 22050, 44100.0, 120.0);
assert!(!tick.is_new_bar());
}
#[test]
fn test_is_new_beat() {
let tick = ClockTick::with_tempo(0, 0, 44100.0, 120.0);
assert!(tick.is_new_beat());
let tick = ClockTick::with_tempo(11025, 11025, 44100.0, 120.0);
assert!(!tick.is_new_beat());
}
#[test]
fn test_default() {
let tick = ClockTick::default();
assert_eq!(tick.sample_pos, 0);
assert_eq!(tick.samples_since_last, 0);
assert!(!tick.is_new_block);
assert_eq!(tick.sample_rate, 44100.0);
assert_eq!(tick.tempo, None);
}
#[test]
fn test_display() {
let tick = ClockTick::new(44100, 44100, 44100.0);
let display = format!("{}", tick);
assert!(display.contains("pos=44100"));
assert!(display.contains("delta=1000ms"));
let tick = ClockTick::with_tempo(44100, 44100, 44100.0, 120.0);
let display = format!("{}", tick);
assert!(display.contains("tempo=120BPM"));
}
}