Skip to main content

rill_core/time/
clock.rs

1use std::fmt;
2use std::sync::atomic::{AtomicU64, Ordering};
3
4use super::tick;
5
6/// High-precision system clock for sample-accurate timing.
7///
8/// Provides sample-accurate timing for signal processing.
9/// Uses atomic operations for thread safety without locks.
10pub struct SystemClock {
11    /// Sample rate of the signal processing system (Hz).
12    pub sample_rate: f32,
13    /// Global sample position (atomically updated).
14    position: AtomicU64,
15    /// Current BPM stored as raw f64 bits for atomic access.
16    bpm: AtomicU64,
17}
18
19impl SystemClock {
20    /// Create a new system clock at the given sample rate and initial BPM.
21    pub fn new(sample_rate: f32, initial_bpm: f64) -> Self {
22        Self {
23            sample_rate,
24            position: AtomicU64::new(0),
25            bpm: AtomicU64::new(initial_bpm.to_bits()),
26        }
27    }
28
29    /// Create a new system clock with a default BPM of 120.
30    pub fn with_sample_rate(sample_rate: f32) -> Self {
31        Self::new(sample_rate, 120.0)
32    }
33
34    /// Advance the clock by `block_size` samples and return the clock tick.
35    pub fn next_tick(&mut self, block_size: usize) -> tick::ClockTick {
36        let samples = block_size as u32;
37        let pos = self.position.fetch_add(samples as u64, Ordering::Relaxed);
38
39        tick::ClockTick {
40            sample_pos: pos,
41            samples_since_last: samples,
42            is_new_block: true,
43            sample_rate: self.sample_rate,
44            tempo: Some(self.bpm() as f32),
45            source: String::new(),
46            speed_ratio: 1.0,
47            is_final: true,
48            io_quantum: samples,
49        }
50    }
51
52    /// Return the current BPM value.
53    pub fn bpm(&self) -> f64 {
54        f64::from_bits(self.bpm.load(Ordering::Relaxed))
55    }
56
57    /// Set the BPM value atomically.
58    pub fn set_bpm(&self, bpm: f64) {
59        self.bpm.store(bpm.to_bits(), Ordering::Relaxed);
60    }
61
62    /// Return the current sample position.
63    pub fn position(&self) -> u64 {
64        self.position.load(Ordering::Relaxed)
65    }
66
67    /// Reset the sample position to zero.
68    pub fn reset(&self) {
69        self.position.store(0, Ordering::Relaxed);
70    }
71}
72
73impl fmt::Debug for SystemClock {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        f.debug_struct("SystemClock")
76            .field("sample_rate", &self.sample_rate)
77            .field("position", &self.position.load(Ordering::Relaxed))
78            .field("bpm", &self.bpm())
79            .finish()
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn test_system_clock() {
89        let mut clock = SystemClock::new(44100.0, 120.0);
90
91        let tick = clock.next_tick(64);
92        assert_eq!(tick.sample_pos, 0);
93        assert_eq!(tick.samples_since_last, 64);
94        assert_eq!(tick.sample_rate, 44100.0);
95        assert_eq!(tick.tempo, Some(120.0));
96
97        let tick = clock.next_tick(64);
98        assert_eq!(tick.sample_pos, 64);
99    }
100}