1use std::collections::BTreeMap;
4
5use serde::{Deserialize, Serialize};
6
7use crate::bms::{
8 Decimal,
9 command::time::{ObjTime, Track},
10};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
14pub struct PulseNumber(pub u64);
15
16impl PulseNumber {
17 #[must_use]
19 pub const fn abs_diff(self, other: Self) -> u64 {
20 self.0.abs_diff(other.0)
21 }
22}
23
24#[derive(Debug, Clone)]
26pub struct PulseConverter {
27 pulses_at_track_start: BTreeMap<Track, u64>,
28 resolution: u64,
29}
30
31impl PulseConverter {
32 #[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 #[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 #[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 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}