1use schemars::JsonSchema;
13use serde::{Deserialize, Serialize};
14
15#[derive(
18 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
19)]
20#[serde(transparent)]
21pub struct Frames(pub u64);
22
23#[derive(
25 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
26)]
27#[serde(transparent)]
28pub struct Samples(pub u64);
29
30#[derive(
32 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
33)]
34#[serde(transparent)]
35pub struct SampleRate(pub u32);
36
37#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema)]
39#[serde(transparent)]
40pub struct Hertz(pub f32);
41
42#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema)]
44#[serde(transparent)]
45pub struct Decibels(pub f32);
46
47#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, JsonSchema)]
51#[serde(transparent)]
52pub struct Tempo(pub f32);
53
54#[derive(
57 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
58)]
59#[serde(transparent)]
60pub struct Bars(pub u32);
61
62impl From<u64> for Frames {
63 fn from(n: u64) -> Self {
64 Frames(n)
65 }
66}
67
68impl From<u64> for Samples {
69 fn from(n: u64) -> Self {
70 Samples(n)
71 }
72}
73
74impl std::ops::Add for Frames {
75 type Output = Frames;
76 fn add(self, rhs: Frames) -> Frames {
77 Frames(self.0 + rhs.0)
78 }
79}
80
81impl std::ops::Sub for Frames {
82 type Output = Frames;
83 fn sub(self, rhs: Frames) -> Frames {
84 Frames(self.0 - rhs.0)
85 }
86}
87
88impl std::ops::Add for Samples {
89 type Output = Samples;
90 fn add(self, rhs: Samples) -> Samples {
91 Samples(self.0 + rhs.0)
92 }
93}
94
95impl std::ops::Sub for Samples {
96 type Output = Samples;
97 fn sub(self, rhs: Samples) -> Samples {
98 Samples(self.0 - rhs.0)
99 }
100}
101
102impl std::ops::Add for Bars {
103 type Output = Bars;
104 fn add(self, rhs: Bars) -> Bars {
105 Bars(self.0 + rhs.0)
106 }
107}
108
109impl std::ops::Sub for Bars {
110 type Output = Bars;
111 fn sub(self, rhs: Bars) -> Bars {
112 Bars(self.0 - rhs.0)
113 }
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, JsonSchema)]
127pub struct Beat {
128 pub num: i64,
130 pub den: u32,
132}
133
134impl Beat {
135 pub const fn new(num: i64, den: u32) -> Beat {
138 let den = if den == 0 { 1 } else { den };
139 if num == 0 {
140 return Beat { num: 0, den: 1 };
141 }
142 let mut a = num.unsigned_abs();
145 let mut b = den as u64;
146 while b != 0 {
147 let t = a % b;
148 a = b;
149 b = t;
150 }
151 let g = a;
152 Beat {
153 num: num / g as i64,
154 den: (den as u64 / g) as u32,
155 }
156 }
157
158 pub const fn zero() -> Beat {
160 Beat { num: 0, den: 1 }
161 }
162
163 pub const fn from_int(n: i64) -> Beat {
165 Beat { num: n, den: 1 }
166 }
167
168 pub fn checked_add(self, other: Beat) -> Result<Beat, BeatError> {
170 let num = self.num as i128 * other.den as i128 + other.num as i128 * self.den as i128;
171 let den = self.den as i128 * other.den as i128;
172 checked(num, den)
173 }
174
175 pub fn checked_sub(self, other: Beat) -> Result<Beat, BeatError> {
178 let num = self.num as i128 * other.den as i128 - other.num as i128 * self.den as i128;
179 let den = self.den as i128 * other.den as i128;
180 checked(num, den)
181 }
182
183 pub fn mul_rational(self, num: i64, den: u32) -> Result<Beat, BeatError> {
186 checked(
187 self.num as i128 * num as i128,
188 self.den as i128 * den as i128,
189 )
190 }
191
192 pub fn scale(self, factor: i64) -> Result<Beat, BeatError> {
195 self.mul_rational(factor, 1)
196 }
197
198 pub fn to_f64(self) -> f64 {
201 self.num as f64 / self.den as f64
202 }
203}
204
205fn checked(num: i128, den: i128) -> Result<Beat, BeatError> {
207 let num = i64::try_from(num).map_err(|_| BeatError::Overflow)?;
208 let den = u32::try_from(den).map_err(|_| BeatError::Overflow)?;
209 Ok(Beat::new(num, den))
210}
211
212impl From<(i64, u32)> for Beat {
213 fn from((num, den): (i64, u32)) -> Beat {
214 Beat::new(num, den)
215 }
216}
217
218impl Ord for Beat {
219 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
220 (self.num as i128 * other.den as i128).cmp(&(other.num as i128 * self.den as i128))
224 }
225}
226
227impl PartialOrd for Beat {
228 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
229 Some(self.cmp(other))
230 }
231}
232
233impl std::fmt::Display for Beat {
234 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
236 if self.den == 1 {
237 write!(f, "{}", self.num)
238 } else {
239 write!(f, "{}/{}", self.num, self.den)
240 }
241 }
242}
243
244impl<'de> Deserialize<'de> for Beat {
247 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
248 where
249 D: serde::Deserializer<'de>,
250 {
251 #[derive(Deserialize)]
252 struct Raw {
253 num: i64,
254 den: u32,
255 }
256 let raw = Raw::deserialize(deserializer)?;
257 Ok(Beat::new(raw.num, raw.den))
258 }
259}
260
261#[derive(Debug, Clone, Copy, PartialEq, Eq)]
263pub enum BeatError {
264 Overflow,
267}
268
269impl std::fmt::Display for BeatError {
270 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
271 match self {
272 BeatError::Overflow => f.write_str("beat arithmetic overflow"),
273 }
274 }
275}
276
277impl std::error::Error for BeatError {}
278
279pub fn beat_to_frames(beat: Beat, tempo: Tempo, rate: SampleRate) -> Frames {
293 let bpm = (tempo.0 as f64).max(1.0);
294 let seconds = beat.to_f64() * 60.0 / bpm;
295 let frames = seconds * rate.0 as f64;
296 Frames(frames.round().max(0.0) as u64)
297}
298
299#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
304pub struct MeterPoint {
305 pub bar: u32,
308 pub numerator: u32,
310 pub denominator: u32,
312}
313
314pub fn meter_at(map: &[MeterPoint], default_numerator: u32, bar: u32) -> MeterPoint {
317 let fallback = MeterPoint {
318 bar: 0,
319 numerator: default_numerator.max(1),
320 denominator: 4,
321 };
322 map.iter()
323 .rev()
324 .find(|p| p.bar <= bar)
325 .copied()
326 .unwrap_or(fallback)
327}
328
329pub fn bar_len(
332 map: &[MeterPoint],
333 default_numerator: u32,
334 pickup: Option<Beat>,
335 index: u32,
336) -> Beat {
337 if index == 0
338 && let Some(p) = pickup
339 {
340 return p;
341 }
342 let meter = meter_at(map, default_numerator, index);
343 Beat::new(meter.numerator as i64 * 4, meter.denominator)
344}
345
346pub fn beat_at_bar(
350 map: &[MeterPoint],
351 default_numerator: u32,
352 pickup: Option<Beat>,
353 bar: u32,
354) -> Beat {
355 let mut beats = Beat::zero();
356 if bar == 0 {
357 return beats;
358 }
359 beats = beats
360 .checked_add(bar_len(map, default_numerator, pickup, 0))
361 .unwrap_or(Beat::new(i64::MAX, 1));
362 let mut i = 1u32;
363 while i < bar {
364 let seg_end = map
365 .iter()
366 .map(|p| p.bar)
367 .filter(|b| *b > i)
368 .min()
369 .unwrap_or(u32::MAX)
370 .min(bar);
371 let span = bar_len(map, default_numerator, pickup, i)
372 .scale(i64::from(seg_end - i))
373 .unwrap_or(Beat::new(i64::MAX, 1));
374 beats = beats.checked_add(span).unwrap_or(Beat::new(i64::MAX, 1));
375 i = seg_end;
376 }
377 beats
378}
379
380pub fn bar_count_at_beat(
383 map: &[MeterPoint],
384 default_numerator: u32,
385 pickup: Option<Beat>,
386 beat: Beat,
387) -> u32 {
388 if beat <= Beat::zero() {
389 return 0;
390 }
391 let mut lo = 0u32;
392 let mut hi = 1u32;
393 while beat_at_bar(map, default_numerator, pickup, hi) < beat {
394 hi = hi.saturating_mul(2);
395 if hi == u32::MAX {
396 return hi;
397 }
398 }
399 while lo + 1 < hi {
400 let mid = lo + (hi - lo) / 2;
401 if beat_at_bar(map, default_numerator, pickup, mid) < beat {
402 lo = mid;
403 } else {
404 hi = mid;
405 }
406 }
407 hi
408}
409
410#[cfg(test)]
411mod tests {
412 use super::*;
413
414 #[test]
415 fn new_normalizes_by_gcd() {
416 assert_eq!(Beat::new(2, 4), Beat::new(1, 2));
417 assert_eq!(Beat::new(-3, 6), Beat { num: -1, den: 2 });
418 assert_eq!(Beat::new(7, 1), Beat { num: 7, den: 1 });
419 assert_eq!(Beat::new(0, 7), Beat::zero());
421 assert_eq!(Beat::new(5, 0), Beat::from_int(5));
422 }
423
424 #[test]
425 fn orders_across_denominators() {
426 assert!(Beat::new(1, 3) < Beat::new(1, 2));
427 assert!(Beat::new(2, 3) > Beat::new(1, 2));
428 assert_eq!(
429 Beat::new(2, 4).cmp(&Beat::new(1, 2)),
430 std::cmp::Ordering::Equal
431 );
432 let mut v = vec![Beat::new(3, 4), Beat::new(1, 3), Beat::new(1, 2)];
434 v.sort();
435 assert_eq!(v, vec![Beat::new(1, 3), Beat::new(1, 2), Beat::new(3, 4)]);
436 }
437
438 #[test]
439 fn comparison_never_overflows() {
440 let a = Beat::new(i64::MAX, u32::MAX);
442 let b = Beat::new(i64::MAX, u32::MAX - 1);
443 assert!(a < b, "same numerator, smaller denominator is larger");
444 assert!(Beat::new(i64::MAX, 1) > Beat::new(1, u32::MAX));
445 assert!(Beat::new(i64::MIN, 1) < Beat::new(-1, u32::MAX));
446 }
447
448 #[test]
449 fn add_sub_stay_exact() {
450 assert_eq!(
451 Beat::new(1, 3).checked_add(Beat::new(1, 6)).unwrap(),
452 Beat::new(1, 2)
453 );
454 assert_eq!(
455 Beat::new(1, 2).checked_sub(Beat::new(1, 3)).unwrap(),
456 Beat::new(1, 6)
457 );
458 assert_eq!(
459 Beat::zero().checked_sub(Beat::new(1, 4)).unwrap(),
460 Beat::new(-1, 4)
461 );
462 }
463
464 #[test]
465 fn add_reports_overflow() {
466 assert_eq!(
467 Beat::new(i64::MAX, 1).checked_add(Beat::from_int(1)),
468 Err(BeatError::Overflow)
469 );
470 assert_eq!(
471 Beat::new(1, u32::MAX).checked_add(Beat::new(1, u32::MAX - 1)),
472 Err(BeatError::Overflow),
473 "the unreduced denominator u32::MAX * (u32::MAX - 1) doesn't fit"
474 );
475 }
476
477 #[test]
478 fn scales_rationally() {
479 assert_eq!(Beat::new(2, 3).mul_rational(3, 4).unwrap(), Beat::new(1, 2));
480 assert_eq!(Beat::new(1, 2).scale(3).unwrap(), Beat::new(3, 2));
481 assert_eq!(Beat::new(1, 3).mul_rational(0, 1).unwrap(), Beat::zero());
482 }
483
484 #[test]
485 fn triplet_math_is_exact_at_the_frame_boundary() {
486 assert_eq!(
488 beat_to_frames(Beat::new(1, 3), Tempo(120.0), SampleRate(48_000)),
489 Frames(8000)
490 );
491 assert_eq!(
493 beat_to_frames(Beat::from_int(1), Tempo(120.0), SampleRate(48_000)),
494 Frames(24_000)
495 );
496 }
497
498 #[test]
499 fn rounds_half_away_from_zero() {
500 assert_eq!(
503 beat_to_frames(Beat::from_int(1), Tempo(40.0), SampleRate(3)),
504 Frames(5)
505 );
506 }
507
508 #[test]
509 fn clamps_degenerate_inputs() {
510 assert_eq!(
512 beat_to_frames(Beat::from_int(1), Tempo(0.5), SampleRate(48_000)),
513 Frames(2_880_000)
514 );
515 assert_eq!(
518 beat_to_frames(Beat::new(-1, 2), Tempo(120.0), SampleRate(48_000)),
519 Frames(0)
520 );
521 }
522
523 #[test]
524 fn integer_units_do_the_obvious_arithmetic() {
525 assert_eq!(Frames(10) + Frames(5), Frames(15));
526 assert_eq!(Frames(10) - Frames(5), Frames(5));
527 assert_eq!(Bars(1) + Bars(2), Bars(3));
528 assert_eq!(Frames::from(3u64), Frames(3));
529 assert!(SampleRate(96_000) > SampleRate(44_100));
530 }
531
532 #[test]
533 fn beat_displays_compactly() {
534 assert_eq!(Beat::new(3, 2).to_string(), "3/2");
535 assert_eq!(Beat::from_int(4).to_string(), "4");
536 assert_eq!(Beat::new(-1, 2).to_string(), "-1/2");
537 }
538
539 #[test]
540 fn beat_serde_is_a_flat_normalized_struct() {
541 assert_eq!(
542 serde_json::to_string(&Beat::new(1, 2)).unwrap(),
543 r#"{"num":1,"den":2}"#
544 );
545 let b: Beat = serde_json::from_str(r#"{"num":2,"den":4}"#).unwrap();
547 assert_eq!(b, Beat::new(1, 2));
548 }
549
550 #[test]
551 fn units_serialize_as_the_bare_inner_value() {
552 assert_eq!(serde_json::to_string(&Frames(8)).unwrap(), "8");
553 assert_eq!(serde_json::to_string(&Tempo(127.5)).unwrap(), "127.5");
554 let f: Frames = serde_json::from_str("8").unwrap();
555 assert_eq!(f, Frames(8));
556 }
557}