Skip to main content

rill_core/time/
clock.rs

1//! Clock and time abstractions for signal processing
2//!
3//! Provides timing information for sample-accurate processing
4//! and synchronization between signal graph and control world.
5
6use super::tick;
7use std::fmt;
8use std::sync::atomic::{AtomicU64, Ordering};
9
10// ============================================================================
11// Clock Tick
12// ============================================================================
13
14/// A tick of the audio clock
15///
16/// Sent to nodes on every signal block to provide timing information
17/// and synchronize processing.
18#[derive(Debug, Clone, Copy)]
19#[allow(dead_code)]
20pub struct ClockTick {
21    /// Absolute sample position since start
22    pub sample_pos: u64,
23
24    /// Number of samples since last tick
25    pub samples_since_last: u32,
26
27    /// Whether this is the start of a new block
28    pub is_new_block: bool,
29
30    /// Current sample rate
31    pub sample_rate: f32,
32
33    /// Current tempo in BPM (if available)
34    pub tempo: Option<f64>,
35}
36
37#[allow(dead_code)]
38impl ClockTick {
39    /// Create a new clock tick
40    pub fn new(sample_pos: u64, samples_since_last: u32, sample_rate: f32) -> Self {
41        Self {
42            sample_pos,
43            samples_since_last,
44            is_new_block: true,
45            sample_rate,
46            tempo: None,
47        }
48    }
49
50    /// Get time since last tick in seconds
51    pub fn delta_seconds(&self) -> f32 {
52        self.samples_since_last as f32 / self.sample_rate
53    }
54
55    /// Get absolute time in seconds
56    pub fn absolute_seconds(&self) -> f64 {
57        self.sample_pos as f64 / self.sample_rate as f64
58    }
59
60    /// Advance to next tick
61    pub fn advance(&mut self, samples: u32) {
62        self.sample_pos += samples as u64;
63        self.samples_since_last = samples;
64        self.is_new_block = true;
65    }
66}
67
68// ============================================================================
69// Clock Source
70// ============================================================================
71
72/// Source of clock ticks
73///
74/// Can be either a hardware device (ALSA, JACK) or a software generator.
75#[allow(dead_code)]
76pub trait ClockSource: Send + Sync {
77    /// Get the next clock tick
78    fn next_tick(&mut self) -> ClockTick;
79
80    /// Get the sample rate
81    fn sample_rate(&self) -> f32;
82
83    /// Start the clock
84    fn start(&mut self) -> Result<(), ClockError>;
85
86    /// Stop the clock
87    fn stop(&mut self) -> Result<(), ClockError>;
88}
89
90// ============================================================================
91// System Clock
92// ============================================================================
93
94/// High-precision system clock
95///
96/// Provides sample-accurate timing for signal processing.
97/// Uses atomic operations for thread safety without locks.
98pub struct SystemClock {
99    pub sample_rate: f32,
100    position: AtomicU64,
101    bpm: AtomicU64, // stored as bits for atomic operations
102}
103
104impl SystemClock {
105    /// Create a new system clock
106    pub fn new(sample_rate: f32, initial_bpm: f64) -> Self {
107        Self {
108            sample_rate,
109            position: AtomicU64::new(0),
110            bpm: AtomicU64::new(initial_bpm.to_bits()),
111        }
112    }
113
114    /// Create a clock with default BPM (120)
115    pub fn with_sample_rate(sample_rate: f32) -> Self {
116        Self::new(sample_rate, 120.0)
117    }
118
119    /// Get the next tick
120    pub fn next_tick(&mut self, block_size: usize) -> tick::ClockTick {
121        let samples = block_size as u32;
122        let pos = self.position.fetch_add(samples as u64, Ordering::Relaxed);
123
124        tick::ClockTick {
125            sample_pos: pos,
126            samples_since_last: samples,
127            is_new_block: true,
128            sample_rate: self.sample_rate,
129            tempo: Some(self.bpm() as f32),
130        }
131    }
132
133    /// Get current BPM
134    pub fn bpm(&self) -> f64 {
135        f64::from_bits(self.bpm.load(Ordering::Relaxed))
136    }
137
138    /// Set BPM
139    pub fn set_bpm(&self, bpm: f64) {
140        self.bpm.store(bpm.to_bits(), Ordering::Relaxed);
141    }
142
143    /// Get current sample position
144    pub fn position(&self) -> u64 {
145        self.position.load(Ordering::Relaxed)
146    }
147
148    /// Reset position to zero
149    pub fn reset(&self) {
150        self.position.store(0, Ordering::Relaxed);
151    }
152}
153
154impl ClockSource for SystemClock {
155    fn next_tick(&mut self) -> ClockTick {
156        let pos = self.position.fetch_add(1, Ordering::Relaxed);
157        ClockTick {
158            sample_pos: pos,
159            samples_since_last: 1,
160            is_new_block: true,
161            sample_rate: self.sample_rate,
162            tempo: Some(self.bpm()),
163        }
164    }
165
166    fn sample_rate(&self) -> f32 {
167        self.sample_rate
168    }
169
170    fn start(&mut self) -> Result<(), ClockError> {
171        Ok(())
172    }
173
174    fn stop(&mut self) -> Result<(), ClockError> {
175        Ok(())
176    }
177}
178
179impl fmt::Debug for SystemClock {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        f.debug_struct("SystemClock")
182            .field("sample_rate", &self.sample_rate)
183            .field("position", &self.position.load(Ordering::Relaxed))
184            .field("bpm", &self.bpm())
185            .finish()
186    }
187}
188
189// ============================================================================
190// Clock Error
191// ============================================================================
192
193/// Errors that can occur in clock operations
194#[derive(Debug, thiserror::Error)]
195#[allow(dead_code)]
196pub enum ClockError {
197    /// Hardware error (ALSA, JACK, etc.)
198    #[error("Hardware error: {0}")]
199    Hardware(String),
200
201    /// Invalid sample rate
202    #[error("Invalid sample rate: {0}")]
203    InvalidSampleRate(f32),
204
205    /// Clock not started
206    #[error("Clock not started")]
207    NotStarted,
208
209    /// Clock already started
210    #[error("Clock already started")]
211    AlreadyStarted,
212}
213
214// ============================================================================
215// Tests
216// ============================================================================
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221
222    #[test]
223    fn test_system_clock() {
224        let mut clock = SystemClock::new(44100.0, 120.0);
225
226        let tick = clock.next_tick(64);
227        assert_eq!(tick.sample_pos, 0);
228        assert_eq!(tick.samples_since_last, 64);
229        assert_eq!(tick.sample_rate, 44100.0);
230        assert_eq!(tick.tempo, Some(120.0));
231
232        let tick = clock.next_tick(64);
233        assert_eq!(tick.sample_pos, 64);
234    }
235
236    #[test]
237    fn test_clock_tick_math() {
238        let tick = ClockTick::new(44100, 44100, 44100.0);
239        assert_eq!(tick.absolute_seconds(), 1.0);
240        assert_eq!(tick.delta_seconds(), 1.0);
241    }
242}