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
71impl PartialEq for ClockTick {
72 fn eq(&self, other: &Self) -> bool {
73 self.sample_pos == other.sample_pos
74 && self.samples_since_last == other.samples_since_last
75 && self.is_new_block == other.is_new_block
76 && self.sample_rate == other.sample_rate
77 && self.tempo == other.tempo
78 && self.source == other.source
79 }
80}
81
82impl fmt::Debug for ClockTick {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 f.debug_struct("ClockTick")
85 .field("sample_pos", &self.sample_pos)
86 .field("samples_since_last", &self.samples_since_last)
87 .field("is_new_block", &self.is_new_block)
88 .field("sample_rate", &self.sample_rate)
89 .field("tempo", &self.tempo)
90 .field("source", &self.source)
91 .finish()
92 }
93}
94
95impl ClockTick {
96 pub fn new(sample_pos: u64, samples_since_last: u32, sample_rate: f32, source: String) -> Self {
107 Self {
108 sample_pos,
109 samples_since_last,
110 is_new_block: true,
111 sample_rate,
112 tempo: None,
113 source,
114 speed_ratio: 1.0,
115 is_final: true,
116 }
117 }
118
119 pub fn with_tempo(
128 sample_pos: u64,
129 samples_since_last: u32,
130 sample_rate: f32,
131 tempo: f32,
132 source: String,
133 ) -> Self {
134 Self {
135 sample_pos,
136 samples_since_last,
137 is_new_block: true,
138 sample_rate,
139 tempo: Some(tempo),
140 source,
141 speed_ratio: 1.0,
142 is_final: true,
143 }
144 }
145
146 #[inline(always)]
151 pub fn delta_seconds(&self) -> f32 {
152 self.samples_since_last as f32 / self.sample_rate
153 }
154
155 #[inline(always)]
160 pub fn absolute_seconds(&self) -> f64 {
161 self.sample_pos as f64 / self.sample_rate as f64
162 }
163
164 #[inline(always)]
170 pub fn beat_position(&self) -> Option<f64> {
171 self.tempo.map(|bpm| {
172 let seconds_per_beat = 60.0 / bpm as f64;
173 self.absolute_seconds() / seconds_per_beat
174 })
175 }
176
177 pub fn musical_position(&self) -> Option<(u32, u8, u8)> {
183 self.tempo.map(|bpm| {
184 let seconds_per_beat = 60.0 / bpm as f64;
185 let total_beats = self.absolute_seconds() / seconds_per_beat;
186
187 let bar = (total_beats / 4.0).floor() as u32;
188 let beat_in_bar = (total_beats % 4.0) as u8;
189 let sixteenth = ((total_beats.fract() * 4.0) as u8) % 4;
190
191 (bar, beat_in_bar, sixteenth)
192 })
193 }
194
195 pub fn advance(&mut self, samples: u32) {
200 self.sample_pos += samples as u64;
201 self.samples_since_last = samples;
202 self.is_new_block = true;
203 }
204
205 pub fn is_new_bar(&self) -> bool {
210 if let Some((_, beat, sixteenth)) = self.musical_position() {
211 beat == 0 && sixteenth == 0
212 } else {
213 false
214 }
215 }
216
217 pub fn is_new_beat(&self) -> bool {
222 if let Some((_, _, sixteenth)) = self.musical_position() {
223 sixteenth == 0
224 } else {
225 false
226 }
227 }
228}
229
230impl Default for ClockTick {
231 fn default() -> Self {
232 Self {
233 sample_pos: 0,
234 samples_since_last: 0,
235 is_new_block: false,
236 sample_rate: 44100.0,
237 tempo: None,
238 source: String::new(),
239 speed_ratio: 1.0,
240 is_final: true,
241 }
242 }
243}
244
245impl fmt::Display for ClockTick {
246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247 write!(
248 f,
249 "ClockTick(pos={}, delta={}ms, rate={}Hz, source={}",
250 self.sample_pos,
251 self.delta_seconds() * 1000.0,
252 self.sample_rate,
253 self.source,
254 )?;
255
256 if let Some(tempo) = self.tempo {
257 write!(f, ", tempo={}BPM", tempo)?;
258 }
259
260 write!(f, ")")
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267
268 #[test]
269 fn test_clock_tick_creation() {
270 let tick = ClockTick::new(44100, 44100, 44100.0, "test".into());
271 assert_eq!(tick.sample_pos, 44100);
272 assert_eq!(tick.samples_since_last, 44100);
273 assert!(tick.is_new_block);
274 assert_eq!(tick.sample_rate, 44100.0);
275 assert_eq!(tick.tempo, None);
276 assert_eq!(tick.source, "test");
277 }
278
279 #[test]
280 fn test_clock_tick_with_tempo() {
281 let tick = ClockTick::with_tempo(44100, 44100, 44100.0, 120.0, "test".into());
282 assert_eq!(tick.tempo, Some(120.0));
283 }
284
285 #[test]
286 fn test_delta_seconds() {
287 let tick = ClockTick::new(0, 44100, 44100.0, "test".into());
288 assert_eq!(tick.delta_seconds(), 1.0);
289
290 let tick = ClockTick::new(0, 22050, 44100.0, "test".into());
291 assert_eq!(tick.delta_seconds(), 0.5);
292 }
293
294 #[test]
295 fn test_absolute_seconds() {
296 let tick = ClockTick::new(44100, 44100, 44100.0, "test".into());
297 assert_eq!(tick.absolute_seconds(), 1.0);
298
299 let tick = ClockTick::new(88200, 44100, 44100.0, "test".into());
300 assert_eq!(tick.absolute_seconds(), 2.0);
301 }
302
303 #[test]
304 fn test_beat_position() {
305 let tick = ClockTick::with_tempo(44100, 44100, 44100.0, 120.0, "test".into());
306 assert_eq!(tick.beat_position(), Some(2.0));
309 }
310
311 #[test]
312 fn test_musical_position() {
313 let tick = ClockTick::with_tempo(44100 * 2, 44100, 44100.0, 120.0, "test".into());
314 let pos = tick.musical_position();
317 assert_eq!(pos, Some((1, 0, 0)));
318
319 let tick = ClockTick::with_tempo(44100 * 3, 44100, 44100.0, 120.0, "test".into());
320 let pos = tick.musical_position();
322 assert_eq!(pos, Some((1, 2, 0)));
323 }
324
325 #[test]
326 fn test_advance() {
327 let mut tick = ClockTick::new(0, 0, 44100.0, "test".into());
328 tick.advance(64);
329 assert_eq!(tick.sample_pos, 64);
330 assert_eq!(tick.samples_since_last, 64);
331 assert!(tick.is_new_block);
332 }
333
334 #[test]
335 fn test_is_new_bar() {
336 let tick = ClockTick::with_tempo(0, 0, 44100.0, 120.0, "test".into());
337 assert!(tick.is_new_bar());
338
339 let tick = ClockTick::with_tempo(22050, 22050, 44100.0, 120.0, "test".into());
340 assert!(!tick.is_new_bar());
342 }
343
344 #[test]
345 fn test_is_new_beat() {
346 let tick = ClockTick::with_tempo(0, 0, 44100.0, 120.0, "test".into());
347 assert!(tick.is_new_beat());
348
349 let tick = ClockTick::with_tempo(11025, 11025, 44100.0, 120.0, "test".into());
350 assert!(!tick.is_new_beat());
352 }
353
354 #[test]
355 fn test_default() {
356 let tick = ClockTick::default();
357 assert_eq!(tick.sample_pos, 0);
358 assert_eq!(tick.samples_since_last, 0);
359 assert!(!tick.is_new_block);
360 assert_eq!(tick.sample_rate, 44100.0);
361 assert_eq!(tick.tempo, None);
362 assert_eq!(tick.source, "");
363 }
364
365 #[test]
366 fn test_display() {
367 let tick = ClockTick::new(44100, 44100, 44100.0, "test".into());
368 let display = format!("{}", tick);
369 assert!(display.contains("pos=44100"));
370 assert!(display.contains("delta=1000ms"));
371 assert!(display.contains("source=test"));
372
373 let tick = ClockTick::with_tempo(44100, 44100, 44100.0, 120.0, "test".into());
374 let display = format!("{}", tick);
375 assert!(display.contains("tempo=120BPM"));
376 }
377}