diff --git a/Cargo.lock b/Cargo.lock index bbc5385..f26cc09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1695,6 +1695,15 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +[[package]] +name = "ordered-float" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d84eb1409416d254e4a9c8fa56cc24701755025b458f0fcd8e59e1f5f40c23bf" +dependencies = [ + "num-traits", +] + [[package]] name = "owned_ttf_parser" version = "0.18.1" @@ -1761,6 +1770,7 @@ dependencies = [ "flate2", "gd_plist", "home", + "ordered-float", "rodio", ] diff --git a/Cargo.toml b/Cargo.toml index b4724d4..61dca13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,4 @@ base64 = {version = "0.21.0"} flate2 = {version = "1.0.25"} gd_plist = {git = "https://github.com/Syudagye/gd-plist.git", version = "1.4.0"} chrono = {version = "0.4.23"} +ordered-float = {version = "3.4.0"} diff --git a/src/music.rs b/src/music.rs index cc826f8..2f0b89a 100644 --- a/src/music.rs +++ b/src/music.rs @@ -3,16 +3,21 @@ use chrono::Duration; use std::collections::BTreeMap; +use ordered_float::OrderedFloat as Float; + +pub type BeatPosition = Float; /// Like BPM, but not necessarily represented in terms of minutes /// Only BPM jumps for now; no smooth accel/decel pub struct BeatRate { initial: StaticBeatRate, - changes: BTreeMap, + changes: BTreeMap, } +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct StaticBeatRate(Duration); + impl StaticBeatRate { pub fn from_bpm(bpm: f32) -> Self { Self(Duration::microseconds(60_000_000 / bpm as i64)) @@ -27,3 +32,55 @@ impl From for BeatRate { } } } + +impl BeatRate { + pub fn at_beat(&self, pos: BeatPosition) -> StaticBeatRate { + match self.changes.first_key_value() { + Some((first_change, _)) => { + if &pos < first_change { + self.initial + } else { + *self.changes.iter().rev().find(|&el| el.0 <= &pos).unwrap().1 + } + }, + None => self.initial, + } + } + + pub fn add_change(&mut self, new_pos: BeatPosition, new_rate: StaticBeatRate) { + self.changes.insert(new_pos, new_rate); + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn rate_when_no_changes() { + let rate: BeatRate = StaticBeatRate::from_bpm(100.0).into(); + assert_eq!(rate.at_beat(5.0.into()), StaticBeatRate::from_bpm(100.0)); + } + + #[test] + fn rate_before_changes() { + let mut rate: BeatRate = StaticBeatRate::from_bpm(100.0).into(); + rate.add_change(5.0.into(), StaticBeatRate::from_bpm(120.0)); + assert_eq!(rate.at_beat(3.0.into()), StaticBeatRate::from_bpm(100.0)); + } + + #[test] + fn rate_after_change() { + let mut rate: BeatRate = StaticBeatRate::from_bpm(100.0).into(); + rate.add_change(5.0.into(), StaticBeatRate::from_bpm(120.0)); + rate.add_change(10.0.into(), StaticBeatRate::from_bpm(140.0)); + assert_eq!(rate.at_beat(6.0.into()), StaticBeatRate::from_bpm(120.0)); + } + + #[test] + fn rate_at_change() { + let mut rate: BeatRate = StaticBeatRate::from_bpm(100.0).into(); + rate.add_change(5.0.into(), StaticBeatRate::from_bpm(120.0)); + rate.add_change(10.0.into(), StaticBeatRate::from_bpm(140.0)); + assert_eq!(rate.at_beat(5.0.into()), StaticBeatRate::from_bpm(120.0)); + } +}