1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
use crate::tween::Tweenable;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PlaybackRate {
Factor(f64),
Semitones(f64),
}
impl PlaybackRate {
pub fn as_factor(&self) -> f64 {
match self {
PlaybackRate::Factor(factor) => *factor,
PlaybackRate::Semitones(semitones) => 2.0f64.powf(*semitones / 12.0),
}
}
pub fn as_semitones(&self) -> f64 {
match self {
PlaybackRate::Factor(factor) => 12.0 * factor.log2(),
PlaybackRate::Semitones(semitones) => *semitones,
}
}
}
impl From<f64> for PlaybackRate {
fn from(factor: f64) -> Self {
Self::Factor(factor)
}
}
impl Tweenable for PlaybackRate {
fn lerp(a: Self, b: Self, amount: f64) -> Self {
match b {
PlaybackRate::Factor(b) => {
PlaybackRate::Factor(Tweenable::lerp(a.as_factor(), b, amount))
}
PlaybackRate::Semitones(b) => {
PlaybackRate::Semitones(Tweenable::lerp(a.as_semitones(), b, amount))
}
}
}
}
#[cfg(test)]
#[test]
#[allow(clippy::float_cmp)]
fn test() {
const TEST_CALCULATIONS: [(f64, f64); 5] = [
(0.0, 1.0),
(1.0, 1.059463),
(2.0, 1.122462),
(-1.0, 0.943874),
(-2.0, 0.890899),
];
for (semitones, factor) in TEST_CALCULATIONS {
assert_eq!(PlaybackRate::Factor(factor).as_factor(), factor);
assert!((PlaybackRate::Factor(factor).as_semitones() - semitones).abs() < 0.00001);
assert_eq!(PlaybackRate::Semitones(semitones).as_semitones(), semitones);
assert!((PlaybackRate::Semitones(semitones).as_factor() - factor).abs() < 0.00001);
}
}