1use std::fmt;
7
8#[derive(Clone)]
37pub struct ClockTick {
38 pub sample_pos: u64,
40
41 pub samples_since_last: u32,
43
44 pub is_new_block: bool,
46
47 pub sample_rate: f32,
49
50 pub tempo: Option<f32>,
52
53 pub source: String,
55
56 pub speed_ratio: f64,
62
63 pub is_final: bool,
69
70 pub io_quantum: u32,
83}
84
85impl PartialEq for ClockTick {
86 fn eq(&self, other: &Self) -> bool {
87 self.sample_pos == other.sample_pos
88 && self.samples_since_last == other.samples_since_last
89 && self.is_new_block == other.is_new_block
90 && self.sample_rate == other.sample_rate
91 && self.tempo == other.tempo
92 && self.source == other.source
93 }
94}
95
96impl fmt::Debug for ClockTick {
97 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98 f.debug_struct("ClockTick")
99 .field("sample_pos", &self.sample_pos)
100 .field("samples_since_last", &self.samples_since_last)
101 .field("is_new_block", &self.is_new_block)
102 .field("sample_rate", &self.sample_rate)
103 .field("tempo", &self.tempo)
104 .field("source", &self.source)
105 .finish()
106 }
107}
108
109impl ClockTick {
110 pub fn new(sample_pos: u64, samples_since_last: u32, sample_rate: f32, source: String) -> Self {
121 Self {
122 sample_pos,
123 samples_since_last,
124 is_new_block: true,
125 sample_rate,
126 tempo: None,
127 source,
128 speed_ratio: 1.0,
129 is_final: true,
130 io_quantum: samples_since_last,
131 }
132 }
133
134 pub fn with_tempo(
143 sample_pos: u64,
144 samples_since_last: u32,
145 sample_rate: f32,
146 tempo: f32,
147 source: String,
148 ) -> Self {
149 Self {
150 sample_pos,
151 samples_since_last,
152 is_new_block: true,
153 sample_rate,
154 tempo: Some(tempo),
155 source,
156 speed_ratio: 1.0,
157 is_final: true,
158 io_quantum: samples_since_last,
159 }
160 }
161
162 pub fn with_io_quantum(mut self, io_quantum: u32) -> Self {
168 self.io_quantum = io_quantum;
169 self
170 }
171
172 #[inline(always)]
177 pub fn delta_seconds(&self) -> f32 {
178 self.samples_since_last as f32 / self.sample_rate
179 }
180
181 #[inline(always)]
186 pub fn absolute_seconds(&self) -> f64 {
187 self.sample_pos as f64 / self.sample_rate as f64
188 }
189
190 #[inline(always)]
196 pub fn beat_position(&self) -> Option<f64> {
197 self.tempo.map(|bpm| {
198 let seconds_per_beat = 60.0 / bpm as f64;
199 self.absolute_seconds() / seconds_per_beat
200 })
201 }
202
203 pub fn musical_position(&self) -> Option<(u32, u8, u8)> {
209 self.tempo.map(|bpm| {
210 let seconds_per_beat = 60.0 / bpm as f64;
211 let total_beats = self.absolute_seconds() / seconds_per_beat;
212
213 let bar = (total_beats / 4.0).floor() as u32;
214 let beat_in_bar = (total_beats % 4.0) as u8;
215 let sixteenth = ((total_beats.fract() * 4.0) as u8) % 4;
216
217 (bar, beat_in_bar, sixteenth)
218 })
219 }
220
221 pub fn advance(&mut self, samples: u32) {
226 self.sample_pos += samples as u64;
227 self.samples_since_last = samples;
228 self.is_new_block = true;
229 }
230
231 pub fn is_new_bar(&self) -> bool {
236 if let Some((_, beat, sixteenth)) = self.musical_position() {
237 beat == 0 && sixteenth == 0
238 } else {
239 false
240 }
241 }
242
243 pub fn is_new_beat(&self) -> bool {
248 if let Some((_, _, sixteenth)) = self.musical_position() {
249 sixteenth == 0
250 } else {
251 false
252 }
253 }
254}
255
256impl Default for ClockTick {
257 fn default() -> Self {
258 Self {
259 sample_pos: 0,
260 samples_since_last: 0,
261 is_new_block: false,
262 sample_rate: 44100.0,
263 tempo: None,
264 source: String::new(),
265 speed_ratio: 1.0,
266 is_final: true,
267 io_quantum: 0,
268 }
269 }
270}
271
272impl fmt::Display for ClockTick {
273 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274 write!(
275 f,
276 "ClockTick(pos={}, delta={}ms, rate={}Hz, source={}",
277 self.sample_pos,
278 self.delta_seconds() * 1000.0,
279 self.sample_rate,
280 self.source,
281 )?;
282
283 if let Some(tempo) = self.tempo {
284 write!(f, ", tempo={}BPM", tempo)?;
285 }
286
287 write!(f, ")")
288 }
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294
295 #[test]
296 fn test_clock_tick_creation() {
297 let tick = ClockTick::new(44100, 44100, 44100.0, "test".into());
298 assert_eq!(tick.sample_pos, 44100);
299 assert_eq!(tick.samples_since_last, 44100);
300 assert!(tick.is_new_block);
301 assert_eq!(tick.sample_rate, 44100.0);
302 assert_eq!(tick.tempo, None);
303 assert_eq!(tick.source, "test");
304 }
305
306 #[test]
307 fn test_clock_tick_with_tempo() {
308 let tick = ClockTick::with_tempo(44100, 44100, 44100.0, 120.0, "test".into());
309 assert_eq!(tick.tempo, Some(120.0));
310 }
311
312 #[test]
313 fn test_delta_seconds() {
314 let tick = ClockTick::new(0, 44100, 44100.0, "test".into());
315 assert_eq!(tick.delta_seconds(), 1.0);
316
317 let tick = ClockTick::new(0, 22050, 44100.0, "test".into());
318 assert_eq!(tick.delta_seconds(), 0.5);
319 }
320
321 #[test]
322 fn test_absolute_seconds() {
323 let tick = ClockTick::new(44100, 44100, 44100.0, "test".into());
324 assert_eq!(tick.absolute_seconds(), 1.0);
325
326 let tick = ClockTick::new(88200, 44100, 44100.0, "test".into());
327 assert_eq!(tick.absolute_seconds(), 2.0);
328 }
329
330 #[test]
331 fn test_beat_position() {
332 let tick = ClockTick::with_tempo(44100, 44100, 44100.0, 120.0, "test".into());
333 assert_eq!(tick.beat_position(), Some(2.0));
336 }
337
338 #[test]
339 fn test_musical_position() {
340 let tick = ClockTick::with_tempo(44100 * 2, 44100, 44100.0, 120.0, "test".into());
341 let pos = tick.musical_position();
344 assert_eq!(pos, Some((1, 0, 0)));
345
346 let tick = ClockTick::with_tempo(44100 * 3, 44100, 44100.0, 120.0, "test".into());
347 let pos = tick.musical_position();
349 assert_eq!(pos, Some((1, 2, 0)));
350 }
351
352 #[test]
353 fn test_advance() {
354 let mut tick = ClockTick::new(0, 0, 44100.0, "test".into());
355 tick.advance(64);
356 assert_eq!(tick.sample_pos, 64);
357 assert_eq!(tick.samples_since_last, 64);
358 assert!(tick.is_new_block);
359 }
360
361 #[test]
362 fn test_is_new_bar() {
363 let tick = ClockTick::with_tempo(0, 0, 44100.0, 120.0, "test".into());
364 assert!(tick.is_new_bar());
365
366 let tick = ClockTick::with_tempo(22050, 22050, 44100.0, 120.0, "test".into());
367 assert!(!tick.is_new_bar());
369 }
370
371 #[test]
372 fn test_is_new_beat() {
373 let tick = ClockTick::with_tempo(0, 0, 44100.0, 120.0, "test".into());
374 assert!(tick.is_new_beat());
375
376 let tick = ClockTick::with_tempo(11025, 11025, 44100.0, 120.0, "test".into());
377 assert!(!tick.is_new_beat());
379 }
380
381 #[test]
382 fn test_default() {
383 let tick = ClockTick::default();
384 assert_eq!(tick.sample_pos, 0);
385 assert_eq!(tick.samples_since_last, 0);
386 assert!(!tick.is_new_block);
387 assert_eq!(tick.sample_rate, 44100.0);
388 assert_eq!(tick.tempo, None);
389 assert_eq!(tick.source, "");
390 }
391
392 #[test]
393 fn test_display() {
394 let tick = ClockTick::new(44100, 44100, 44100.0, "test".into());
395 let display = format!("{}", tick);
396 assert!(display.contains("pos=44100"));
397 assert!(display.contains("delta=1000ms"));
398 assert!(display.contains("source=test"));
399
400 let tick = ClockTick::with_tempo(44100, 44100, 44100.0, 120.0, "test".into());
401 let display = format!("{}", tick);
402 assert!(display.contains("tempo=120BPM"));
403 }
404}