1use std::fmt;
7
8#[derive(Debug, Clone, Copy, PartialEq)]
32pub struct ClockTick {
33 pub sample_pos: u64,
35
36 pub samples_since_last: u32,
38
39 pub is_new_block: bool,
41
42 pub sample_rate: f32,
44
45 pub tempo: Option<f32>,
47}
48
49impl ClockTick {
50 pub const fn new(sample_pos: u64, samples_since_last: u32, sample_rate: f32) -> Self {
60 Self {
61 sample_pos,
62 samples_since_last,
63 is_new_block: true,
64 sample_rate,
65 tempo: None,
66 }
67 }
68
69 pub const fn with_tempo(
77 sample_pos: u64,
78 samples_since_last: u32,
79 sample_rate: f32,
80 tempo: f32,
81 ) -> Self {
82 Self {
83 sample_pos,
84 samples_since_last,
85 is_new_block: true,
86 sample_rate,
87 tempo: Some(tempo),
88 }
89 }
90
91 #[inline(always)]
96 pub fn delta_seconds(&self) -> f32 {
97 self.samples_since_last as f32 / self.sample_rate
98 }
99
100 #[inline(always)]
105 pub fn absolute_seconds(&self) -> f64 {
106 self.sample_pos as f64 / self.sample_rate as f64
107 }
108
109 #[inline(always)]
115 pub fn beat_position(&self) -> Option<f64> {
116 self.tempo.map(|bpm| {
117 let seconds_per_beat = 60.0 / bpm as f64;
118 self.absolute_seconds() / seconds_per_beat
119 })
120 }
121
122 pub fn musical_position(&self) -> Option<(u32, u8, u8)> {
128 self.tempo.map(|bpm| {
129 let seconds_per_beat = 60.0 / bpm as f64;
130 let total_beats = self.absolute_seconds() / seconds_per_beat;
131
132 let bar = (total_beats / 4.0).floor() as u32;
133 let beat_in_bar = (total_beats % 4.0) as u8;
134 let sixteenth = ((total_beats.fract() * 4.0) as u8) % 4;
135
136 (bar, beat_in_bar, sixteenth)
137 })
138 }
139
140 pub fn advance(&mut self, samples: u32) {
145 self.sample_pos += samples as u64;
146 self.samples_since_last = samples;
147 self.is_new_block = true;
148 }
149
150 pub fn is_new_bar(&self) -> bool {
155 if let Some((_, beat, sixteenth)) = self.musical_position() {
156 beat == 0 && sixteenth == 0
157 } else {
158 false
159 }
160 }
161
162 pub fn is_new_beat(&self) -> bool {
167 if let Some((_, _, sixteenth)) = self.musical_position() {
168 sixteenth == 0
169 } else {
170 false
171 }
172 }
173}
174
175impl Default for ClockTick {
176 fn default() -> Self {
177 Self {
178 sample_pos: 0,
179 samples_since_last: 0,
180 is_new_block: false,
181 sample_rate: 44100.0,
182 tempo: None,
183 }
184 }
185}
186
187impl fmt::Display for ClockTick {
188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189 write!(
190 f,
191 "ClockTick(pos={}, delta={}ms, rate={}Hz",
192 self.sample_pos,
193 self.delta_seconds() * 1000.0,
194 self.sample_rate,
195 )?;
196
197 if let Some(tempo) = self.tempo {
198 write!(f, ", tempo={}BPM", tempo)?;
199 }
200
201 write!(f, ")")
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208
209 #[test]
210 fn test_clock_tick_creation() {
211 let tick = ClockTick::new(44100, 44100, 44100.0);
212 assert_eq!(tick.sample_pos, 44100);
213 assert_eq!(tick.samples_since_last, 44100);
214 assert!(tick.is_new_block);
215 assert_eq!(tick.sample_rate, 44100.0);
216 assert_eq!(tick.tempo, None);
217 }
218
219 #[test]
220 fn test_clock_tick_with_tempo() {
221 let tick = ClockTick::with_tempo(44100, 44100, 44100.0, 120.0);
222 assert_eq!(tick.tempo, Some(120.0));
223 }
224
225 #[test]
226 fn test_delta_seconds() {
227 let tick = ClockTick::new(0, 44100, 44100.0);
228 assert_eq!(tick.delta_seconds(), 1.0);
229
230 let tick = ClockTick::new(0, 22050, 44100.0);
231 assert_eq!(tick.delta_seconds(), 0.5);
232 }
233
234 #[test]
235 fn test_absolute_seconds() {
236 let tick = ClockTick::new(44100, 44100, 44100.0);
237 assert_eq!(tick.absolute_seconds(), 1.0);
238
239 let tick = ClockTick::new(88200, 44100, 44100.0);
240 assert_eq!(tick.absolute_seconds(), 2.0);
241 }
242
243 #[test]
244 fn test_beat_position() {
245 let tick = ClockTick::with_tempo(44100, 44100, 44100.0, 120.0);
246 assert_eq!(tick.beat_position(), Some(2.0));
249 }
250
251 #[test]
252 fn test_musical_position() {
253 let tick = ClockTick::with_tempo(44100 * 2, 44100, 44100.0, 120.0);
254 let pos = tick.musical_position();
257 assert_eq!(pos, Some((1, 0, 0)));
258
259 let tick = ClockTick::with_tempo(44100 * 3, 44100, 44100.0, 120.0);
260 let pos = tick.musical_position();
262 assert_eq!(pos, Some((1, 2, 0)));
263 }
264
265 #[test]
266 fn test_advance() {
267 let mut tick = ClockTick::new(0, 0, 44100.0);
268 tick.advance(64);
269 assert_eq!(tick.sample_pos, 64);
270 assert_eq!(tick.samples_since_last, 64);
271 assert!(tick.is_new_block);
272 }
273
274 #[test]
275 fn test_is_new_bar() {
276 let tick = ClockTick::with_tempo(0, 0, 44100.0, 120.0);
277 assert!(tick.is_new_bar());
278
279 let tick = ClockTick::with_tempo(22050, 22050, 44100.0, 120.0);
280 assert!(!tick.is_new_bar());
282 }
283
284 #[test]
285 fn test_is_new_beat() {
286 let tick = ClockTick::with_tempo(0, 0, 44100.0, 120.0);
287 assert!(tick.is_new_beat());
288
289 let tick = ClockTick::with_tempo(11025, 11025, 44100.0, 120.0);
290 assert!(!tick.is_new_beat());
292 }
293
294 #[test]
295 fn test_default() {
296 let tick = ClockTick::default();
297 assert_eq!(tick.sample_pos, 0);
298 assert_eq!(tick.samples_since_last, 0);
299 assert!(!tick.is_new_block);
300 assert_eq!(tick.sample_rate, 44100.0);
301 assert_eq!(tick.tempo, None);
302 }
303
304 #[test]
305 fn test_display() {
306 let tick = ClockTick::new(44100, 44100, 44100.0);
307 let display = format!("{}", tick);
308 assert!(display.contains("pos=44100"));
309 assert!(display.contains("delta=1000ms"));
310
311 let tick = ClockTick::with_tempo(44100, 44100, 44100.0, 120.0);
312 let display = format!("{}", tick);
313 assert!(display.contains("tempo=120BPM"));
314 }
315}