bms_rs/bmson/
pulse.rs

1//! Pulse definition for bmson format. It represents only beat on the score, so you need to know previous BPMs for finding happening seconds of a note.
2
3use std::collections::BTreeMap;
4
5use serde::{Deserialize, Serialize};
6
7use crate::bms::{
8    Decimal,
9    command::time::{ObjTime, Track},
10};
11
12/// Note position for the chart [`super::Bmson`].
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
14pub struct PulseNumber(pub u64);
15
16impl PulseNumber {
17    /// Calculates an absolute difference of two pulses.
18    #[must_use]
19    pub const fn abs_diff(self, other: Self) -> u64 {
20        self.0.abs_diff(other.0)
21    }
22}
23
24/// Converter from [`ObjTime`] into pulses, which split one quarter note evenly.
25#[derive(Debug, Clone)]
26pub struct PulseConverter {
27    pulses_at_track_start: BTreeMap<Track, u64>,
28    resolution: u64,
29}
30
31impl PulseConverter {
32    /// Creates a new converter from [`Notes`].
33    #[must_use]
34    pub fn new(bms: &crate::bms::model::Bms) -> Self {
35        let resolution: u64 = bms.resolution_for_pulses();
36        let last_track = bms.last_obj_time().map_or(0, |time| time.track.0);
37
38        let mut pulses_at_track_start = BTreeMap::new();
39        pulses_at_track_start.insert(Track(0), 0);
40        let mut current_track: u64 = 0;
41        let mut current_pulses: u64 = 0;
42        loop {
43            let section_len: f64 = bms
44                .arrangers
45                .section_len_changes
46                .get(&Track(current_track))
47                .map_or_else(|| Decimal::from(1u64), |section| section.length.clone())
48                .try_into()
49                .unwrap_or(1.0);
50            current_pulses = current_pulses.saturating_add((section_len * 4.0) as u64 * resolution);
51            current_track += 1;
52            pulses_at_track_start.insert(Track(current_track), current_pulses);
53            if last_track < current_track {
54                break;
55            }
56        }
57        Self {
58            pulses_at_track_start,
59            resolution,
60        }
61    }
62
63    /// Gets pulses on the start of [`Track`].
64    #[must_use]
65    pub fn get_pulses_on(&self, track: Track) -> PulseNumber {
66        PulseNumber(
67            self.pulses_at_track_start
68                .get(&track)
69                .copied()
70                .or_else(|| {
71                    self.pulses_at_track_start
72                        .last_key_value()
73                        .map(|(_, &pulses)| pulses)
74                })
75                .unwrap_or_default(),
76        )
77    }
78
79    /// Gets pulses at the [`ObjTime`].
80    #[must_use]
81    pub fn get_pulses_at(&self, time: ObjTime) -> PulseNumber {
82        let PulseNumber(track_base) = self.get_pulses_on(time.track);
83        PulseNumber(track_base + (4 * self.resolution * time.numerator / time.denominator))
84    }
85}
86
87#[test]
88fn pulse_conversion() {
89    use crate::bms::model::{Arrangers, obj::SectionLenChangeObj};
90
91    // Source BMS:
92    // ```
93    // #00102:0.75
94    // #00103:1.25
95    // ```
96    let notes = {
97        let mut notes = Arrangers::default();
98        let mut prompt_handler = crate::bms::parse::prompt::AlwaysUseNewer;
99        notes
100            .push_section_len_change(
101                SectionLenChangeObj {
102                    track: Track(1),
103                    length: Decimal::from(0.75),
104                },
105                &mut prompt_handler,
106            )
107            .unwrap();
108        notes
109            .push_section_len_change(
110                SectionLenChangeObj {
111                    track: Track(2),
112                    length: Decimal::from(1.25),
113                },
114                &mut prompt_handler,
115            )
116            .unwrap();
117        notes
118    };
119    let converter = PulseConverter::new(&crate::bms::model::Bms {
120        arrangers: notes,
121        ..Default::default()
122    });
123
124    assert_eq!(converter.resolution, 1);
125
126    assert_eq!(
127        converter
128            .get_pulses_at(ObjTime {
129                track: Track(0),
130                numerator: 0,
131                denominator: 4
132            })
133            .0,
134        0
135    );
136    assert_eq!(
137        converter
138            .get_pulses_at(ObjTime {
139                track: Track(0),
140                numerator: 2,
141                denominator: 4
142            })
143            .0,
144        2
145    );
146    assert_eq!(
147        converter
148            .get_pulses_at(ObjTime {
149                track: Track(1),
150                numerator: 0,
151                denominator: 4
152            })
153            .0,
154        4
155    );
156    assert_eq!(
157        converter
158            .get_pulses_at(ObjTime {
159                track: Track(1),
160                numerator: 2,
161                denominator: 4
162            })
163            .0,
164        6
165    );
166    assert_eq!(
167        converter
168            .get_pulses_at(ObjTime {
169                track: Track(2),
170                numerator: 0,
171                denominator: 4
172            })
173            .0,
174        7
175    );
176    assert_eq!(
177        converter
178            .get_pulses_at(ObjTime {
179                track: Track(2),
180                numerator: 2,
181                denominator: 4
182            })
183            .0,
184        9
185    );
186    assert_eq!(
187        converter
188            .get_pulses_at(ObjTime {
189                track: Track(3),
190                numerator: 0,
191                denominator: 4
192            })
193            .0,
194        12
195    );
196}