use super::tick;
use std::fmt;
use std::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub struct ClockTick {
pub sample_pos: u64,
pub samples_since_last: u32,
pub is_new_block: bool,
pub sample_rate: f32,
pub tempo: Option<f64>,
}
#[allow(dead_code)]
impl ClockTick {
pub 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 fn delta_seconds(&self) -> f32 {
self.samples_since_last as f32 / self.sample_rate
}
pub fn absolute_seconds(&self) -> f64 {
self.sample_pos as f64 / self.sample_rate as f64
}
pub fn advance(&mut self, samples: u32) {
self.sample_pos += samples as u64;
self.samples_since_last = samples;
self.is_new_block = true;
}
}
#[allow(dead_code)]
pub trait ClockSource: Send + Sync {
fn next_tick(&mut self) -> ClockTick;
fn sample_rate(&self) -> f32;
fn start(&mut self) -> Result<(), ClockError>;
fn stop(&mut self) -> Result<(), ClockError>;
}
pub struct SystemClock {
pub sample_rate: f32,
position: AtomicU64,
bpm: AtomicU64, }
impl SystemClock {
pub fn new(sample_rate: f32, initial_bpm: f64) -> Self {
Self {
sample_rate,
position: AtomicU64::new(0),
bpm: AtomicU64::new(initial_bpm.to_bits()),
}
}
pub fn with_sample_rate(sample_rate: f32) -> Self {
Self::new(sample_rate, 120.0)
}
pub fn next_tick(&mut self, block_size: usize) -> tick::ClockTick {
let samples = block_size as u32;
let pos = self.position.fetch_add(samples as u64, Ordering::Relaxed);
tick::ClockTick {
sample_pos: pos,
samples_since_last: samples,
is_new_block: true,
sample_rate: self.sample_rate,
tempo: Some(self.bpm() as f32),
}
}
pub fn bpm(&self) -> f64 {
f64::from_bits(self.bpm.load(Ordering::Relaxed))
}
pub fn set_bpm(&self, bpm: f64) {
self.bpm.store(bpm.to_bits(), Ordering::Relaxed);
}
pub fn position(&self) -> u64 {
self.position.load(Ordering::Relaxed)
}
pub fn reset(&self) {
self.position.store(0, Ordering::Relaxed);
}
}
impl ClockSource for SystemClock {
fn next_tick(&mut self) -> ClockTick {
let pos = self.position.fetch_add(1, Ordering::Relaxed);
ClockTick {
sample_pos: pos,
samples_since_last: 1,
is_new_block: true,
sample_rate: self.sample_rate,
tempo: Some(self.bpm()),
}
}
fn sample_rate(&self) -> f32 {
self.sample_rate
}
fn start(&mut self) -> Result<(), ClockError> {
Ok(())
}
fn stop(&mut self) -> Result<(), ClockError> {
Ok(())
}
}
impl fmt::Debug for SystemClock {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("SystemClock")
.field("sample_rate", &self.sample_rate)
.field("position", &self.position.load(Ordering::Relaxed))
.field("bpm", &self.bpm())
.finish()
}
}
#[derive(Debug, thiserror::Error)]
#[allow(dead_code)]
pub enum ClockError {
#[error("Hardware error: {0}")]
Hardware(String),
#[error("Invalid sample rate: {0}")]
InvalidSampleRate(f32),
#[error("Clock not started")]
NotStarted,
#[error("Clock already started")]
AlreadyStarted,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_system_clock() {
let mut clock = SystemClock::new(44100.0, 120.0);
let tick = clock.next_tick(64);
assert_eq!(tick.sample_pos, 0);
assert_eq!(tick.samples_since_last, 64);
assert_eq!(tick.sample_rate, 44100.0);
assert_eq!(tick.tempo, Some(120.0));
let tick = clock.next_tick(64);
assert_eq!(tick.sample_pos, 64);
}
#[test]
fn test_clock_tick_math() {
let tick = ClockTick::new(44100, 44100, 44100.0);
assert_eq!(tick.absolute_seconds(), 1.0);
assert_eq!(tick.delta_seconds(), 1.0);
}
}