bms_rs/bms/command/
time.rs

1//! Definitions of time in BMS.
2
3use num::Integer;
4use std::num::NonZeroU64;
5
6/// A track, or measure, or bar, in the score. It must greater than 0, but some scores may include the 0 track, where the object is in.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub struct Track(pub u64);
10
11impl std::fmt::Display for Track {
12    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
13        write!(f, "Track: {:03}", self.0)
14    }
15}
16
17/// A time of the object on the score.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20pub struct ObjTime {
21    /// The track, or measure, where the object is in.
22    track: Track,
23    /// The time offset numerator in the track.
24    numerator: u64,
25    /// The time offset denominator in the track.
26    denominator: NonZeroU64,
27}
28
29impl ObjTime {
30    /// Create a new time.
31    #[must_use]
32    pub fn new(track: u64, numerator: u64, denominator: NonZeroU64) -> Self {
33        // If numerator is greater than denominator, add the integer part of numerator / denominator to track and set numerator to the remainder.
34        let (track, numerator) = if numerator > denominator.get() {
35            (
36                track + (numerator / denominator.get()),
37                numerator % denominator.get(),
38            )
39        } else {
40            (track, numerator)
41        };
42        // Reduce the fraction to the simplest form.
43        // Note: 0.gcd(&num) == num, when num > 0
44        let gcd = numerator.gcd(&denominator.get());
45        Self {
46            track: Track(track),
47            numerator: numerator / gcd,
48            denominator: NonZeroU64::new(denominator.get() / gcd)
49                .expect("GCD should never make denominator zero"),
50        }
51    }
52
53    /// Get the track where the object is in.
54    #[must_use]
55    pub const fn track(&self) -> Track {
56        self.track
57    }
58
59    /// Get the time offset numerator in the track.
60    #[must_use]
61    pub const fn numerator(&self) -> u64 {
62        self.numerator
63    }
64
65    /// Get the time offset denominator in the track.
66    #[must_use]
67    pub const fn denominator(&self) -> NonZeroU64 {
68        self.denominator
69    }
70
71    /// Get the time offset denominator in the track as u64.
72    #[must_use]
73    pub const fn denominator_u64(&self) -> u64 {
74        self.denominator.get()
75    }
76}
77
78impl PartialOrd for ObjTime {
79    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
80        Some(self.cmp(other))
81    }
82}
83
84impl Ord for ObjTime {
85    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
86        let self_time_in_track = self.numerator() * other.denominator().get();
87        let other_time_in_track = other.numerator() * self.denominator().get();
88        self.track()
89            .cmp(&other.track())
90            .then(self_time_in_track.cmp(&other_time_in_track))
91    }
92}
93
94impl std::fmt::Display for ObjTime {
95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96        write!(
97            f,
98            "ObjTime: {}, {} / {}",
99            self.track(),
100            self.numerator(),
101            self.denominator().get()
102        )
103    }
104}