euphony_core/time/
tempo.rs1use crate::time::{beat::Beat, duration::Duration, unquantized_beat::UnquantizedBeat};
2use core::ops::{Div, Mul};
3
4new_ratio!(Tempo, u64);
5
6const MINUTE: Duration = Duration::from_secs(60);
7
8impl Tempo {
9 pub fn as_beat_duration(self) -> Duration {
10 MINUTE / self.as_ratio()
11 }
12
13 pub fn from_beat_duration(duration: Duration) -> Self {
14 Tempo(MINUTE.as_nanos() as u64, duration.as_nanos() as u64)
15 .reduce()
16 .simplify(64)
17 }
18}
19
20impl Mul<Beat> for Tempo {
21 type Output = Duration;
22
23 fn mul(self, beat: Beat) -> Self::Output {
24 self.as_beat_duration() * beat.as_ratio()
25 }
26}
27
28impl Div<Tempo> for Duration {
29 type Output = UnquantizedBeat;
30
31 fn div(self, tempo: Tempo) -> Self::Output {
32 let a = self.as_nanos();
33 let b = tempo.as_beat_duration().as_nanos();
34
35 let whole = (a / b) as u64;
36 let fract = (a % b) as u64;
37
38 UnquantizedBeat(whole, Beat(fract, b as u64).reduce())
39 }
40}
41
42#[test]
43fn beat_round_trip_test() {
44 let tempos = 60..320;
45 let beats = [
46 Beat(1, 64),
47 Beat(1, 48),
48 Beat(1, 32),
49 Beat(1, 24),
50 Beat(1, 16),
51 Beat(1, 12),
52 Beat(1, 8),
53 Beat(1, 6),
54 Beat(1, 4),
55 Beat(1, 3),
56 Beat(1, 2),
57 Beat(1, 1),
58 ];
59 let counts = [
60 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100, 1000, 10_000, 100_000, 1_000_000,
61 ];
62 for tempo in tempos {
63 let tempo = Tempo(tempo, 1);
64 assert_eq!(tempo, Tempo::from_beat_duration(tempo.as_beat_duration()));
65
66 for count in counts.iter() {
67 for beat in beats.iter() {
68 let expected = *beat * *count;
69 let actual: UnquantizedBeat = (tempo * expected) / tempo;
70 assert_eq!(
71 actual.quantize(Beat(1, 192)),
72 expected,
73 "{:?} {:?}",
74 tempo,
75 beat
76 );
77 }
78 }
79 }
80}