1use crate::{
2 decode::{DecodeBeatmap, DecodeState},
3 section::{
4 general::{CountdownType, GameMode, General, GeneralState, ParseGeneralError},
5 hit_objects::hit_samples::{ParseSampleBankError, SampleBank},
6 },
7 util::{ParseNumber, ParseNumberError, StrExt, MAX_PARSE_VALUE},
8 Beatmap,
9};
10
11use super::{
12 DifficultyPoint, EffectFlags, EffectPoint, ParseEffectFlagsError, SamplePoint, TimeSignature,
13 TimeSignatureError, TimingPoint,
14};
15
16#[derive(Clone, Debug, PartialEq)]
19pub struct TimingPoints {
20 pub audio_file: String,
22 pub audio_lead_in: f64,
23 pub preview_time: i32,
24 pub default_sample_bank: SampleBank,
25 pub default_sample_volume: i32,
26 pub stack_leniency: f32,
27 pub mode: GameMode,
28 pub letterbox_in_breaks: bool,
29 pub special_style: bool,
30 pub widescreen_storyboard: bool,
31 pub epilepsy_warning: bool,
32 pub samples_match_playback_rate: bool,
33 pub countdown: CountdownType,
34 pub countdown_offset: i32,
35
36 pub control_points: ControlPoints,
38}
39
40impl Default for TimingPoints {
41 fn default() -> Self {
42 let general = General::default();
43
44 Self {
45 audio_file: general.audio_file,
46 audio_lead_in: general.audio_lead_in,
47 preview_time: general.preview_time,
48 default_sample_bank: general.default_sample_bank,
49 default_sample_volume: general.default_sample_volume,
50 stack_leniency: general.stack_leniency,
51 mode: general.mode,
52 letterbox_in_breaks: general.letterbox_in_breaks,
53 special_style: general.special_style,
54 widescreen_storyboard: general.widescreen_storyboard,
55 epilepsy_warning: general.epilepsy_warning,
56 samples_match_playback_rate: general.samples_match_playback_rate,
57 countdown: general.countdown,
58 countdown_offset: general.countdown_offset,
59 control_points: ControlPoints::default(),
60 }
61 }
62}
63
64impl From<TimingPoints> for Beatmap {
65 fn from(timing_points: TimingPoints) -> Self {
66 Self {
67 audio_file: timing_points.audio_file,
68 audio_lead_in: timing_points.audio_lead_in,
69 preview_time: timing_points.preview_time,
70 default_sample_bank: timing_points.default_sample_bank,
71 default_sample_volume: timing_points.default_sample_volume,
72 stack_leniency: timing_points.stack_leniency,
73 mode: timing_points.mode,
74 letterbox_in_breaks: timing_points.letterbox_in_breaks,
75 special_style: timing_points.special_style,
76 widescreen_storyboard: timing_points.widescreen_storyboard,
77 epilepsy_warning: timing_points.epilepsy_warning,
78 samples_match_playback_rate: timing_points.samples_match_playback_rate,
79 countdown: timing_points.countdown,
80 countdown_offset: timing_points.countdown_offset,
81 control_points: timing_points.control_points,
82 ..Self::default()
83 }
84 }
85}
86
87#[derive(Clone, Debug, Default, PartialEq)]
89pub struct ControlPoints {
90 pub timing_points: Vec<TimingPoint>,
91 pub difficulty_points: Vec<DifficultyPoint>,
92 pub effect_points: Vec<EffectPoint>,
93 pub sample_points: Vec<SamplePoint>,
94}
95
96impl ControlPoints {
97 pub fn difficulty_point_at(&self, time: f64) -> Option<&DifficultyPoint> {
99 self.difficulty_points
100 .binary_search_by(|probe| probe.time.total_cmp(&time))
101 .map_or_else(|i| i.checked_sub(1), Some)
102 .map(|i| &self.difficulty_points[i])
103 }
104
105 pub fn effect_point_at(&self, time: f64) -> Option<&EffectPoint> {
107 self.effect_points
108 .binary_search_by(|probe| probe.time.total_cmp(&time))
109 .map_or_else(|i| i.checked_sub(1), Some)
110 .map(|i| &self.effect_points[i])
111 }
112
113 pub fn sample_point_at(&self, time: f64) -> Option<&SamplePoint> {
115 let i = self
116 .sample_points
117 .binary_search_by(|probe| probe.time.total_cmp(&time))
118 .unwrap_or_else(|i| i.saturating_sub(1));
119
120 self.sample_points.get(i)
121 }
122
123 pub fn timing_point_at(&self, time: f64) -> Option<&TimingPoint> {
125 let i = self
126 .timing_points
127 .binary_search_by(|probe| probe.time.total_cmp(&time))
128 .unwrap_or_else(|i| i.saturating_sub(1));
129
130 self.timing_points.get(i)
131 }
132
133 pub fn add<P: ControlPoint<ControlPoints>>(&mut self, point: P) {
135 if !point.check_already_existing(self) {
136 point.add(self);
137 }
138 }
139}
140
141pub trait ControlPoint<C> {
143 fn check_already_existing(&self, control_points: &C) -> bool;
145
146 fn add(self, control_points: &mut C);
150}
151
152impl ControlPoint<ControlPoints> for TimingPoint {
153 fn check_already_existing(&self, _: &ControlPoints) -> bool {
154 false
155 }
156
157 fn add(self, control_points: &mut ControlPoints) {
158 match control_points
159 .timing_points
160 .binary_search_by(|probe| probe.time.total_cmp(&self.time))
161 {
162 Err(i) => control_points.timing_points.insert(i, self),
163 Ok(i) => control_points.timing_points[i] = self,
164 }
165 }
166}
167
168impl ControlPoint<ControlPoints> for DifficultyPoint {
169 fn check_already_existing(&self, control_points: &ControlPoints) -> bool {
170 match control_points.difficulty_point_at(self.time) {
171 Some(existing) => self.is_redundant(existing),
172 None => self.is_redundant(&DifficultyPoint::default()),
173 }
174 }
175
176 fn add(self, control_points: &mut ControlPoints) {
177 match control_points
178 .difficulty_points
179 .binary_search_by(|probe| probe.time.total_cmp(&self.time))
180 {
181 Err(i) => control_points.difficulty_points.insert(i, self),
182 Ok(i) => control_points.difficulty_points[i] = self,
183 }
184 }
185}
186
187impl ControlPoint<ControlPoints> for EffectPoint {
188 fn check_already_existing(&self, control_points: &ControlPoints) -> bool {
189 match control_points.effect_point_at(self.time) {
190 Some(existing) => self.is_redundant(existing),
191 None => self.is_redundant(&EffectPoint::default()),
192 }
193 }
194
195 fn add(self, control_points: &mut ControlPoints) {
196 match control_points
197 .effect_points
198 .binary_search_by(|probe| probe.time.total_cmp(&self.time))
199 {
200 Err(i) => control_points.effect_points.insert(i, self),
201 Ok(i) => control_points.effect_points[i] = self,
202 }
203 }
204}
205
206impl ControlPoint<ControlPoints> for SamplePoint {
207 fn check_already_existing(&self, control_points: &ControlPoints) -> bool {
208 control_points
209 .sample_points
210 .binary_search_by(|probe| probe.time.total_cmp(&self.time))
211 .map_or_else(|i| i.checked_sub(1), Some)
212 .map_or(false, |i| {
213 self.is_redundant(&control_points.sample_points[i])
214 })
215 }
216
217 fn add(self, control_points: &mut ControlPoints) {
218 match control_points
219 .sample_points
220 .binary_search_by(|probe| probe.time.total_cmp(&self.time))
221 {
222 Err(i) => control_points.sample_points.insert(i, self),
223 Ok(i) => control_points.sample_points[i] = self,
224 }
225 }
226}
227
228thiserror! {
229 #[derive(Debug)]
231 pub enum ParseTimingPointsError {
232 #[error("failed to parse effect flags")]
233 EffectFlags(#[from] ParseEffectFlagsError),
234 #[error("failed to parse general section")]
235 General(#[from] ParseGeneralError),
236 #[error("invalid line")]
237 InvalidLine,
238 #[error("failed to parse number")]
239 Number(#[from] ParseNumberError),
240 #[error("failed to parse sample bank")]
241 SampleBank(#[from] ParseSampleBankError),
242 #[error("time signature error")]
243 TimeSignature(#[from] TimeSignatureError),
244 #[error("beat length cannot be NaN in a timing control point")]
245 TimingControlPointNaN,
246 }
247}
248
249pub struct TimingPointsState {
251 general: GeneralState,
252 pending_control_points_time: f64,
253 pending_timing_point: Option<TimingPoint>,
254 pending_difficulty_point: Option<DifficultyPoint>,
255 pending_effect_point: Option<EffectPoint>,
256 pending_sample_point: Option<SamplePoint>,
257 control_points: ControlPoints,
258}
259
260impl TimingPointsState {
261 pub(crate) const fn mode(&self) -> GameMode {
262 self.general.mode
263 }
264}
265
266trait Pending: Sized {
267 fn pending(state: &mut TimingPointsState) -> &mut Option<Self>;
268
269 fn push_front(self, state: &mut TimingPointsState) {
270 let pending = Self::pending(state);
271
272 if pending.is_none() {
273 *pending = Some(self);
274 }
275 }
276
277 fn push_back(self, state: &mut TimingPointsState) {
278 *Self::pending(state) = Some(self);
279 }
280}
281
282impl Pending for TimingPoint {
283 fn pending(state: &mut TimingPointsState) -> &mut Option<Self> {
284 &mut state.pending_timing_point
285 }
286}
287
288impl Pending for DifficultyPoint {
289 fn pending(state: &mut TimingPointsState) -> &mut Option<Self> {
290 &mut state.pending_difficulty_point
291 }
292}
293
294impl Pending for EffectPoint {
295 fn pending(state: &mut TimingPointsState) -> &mut Option<Self> {
296 &mut state.pending_effect_point
297 }
298}
299
300impl Pending for SamplePoint {
301 fn pending(state: &mut TimingPointsState) -> &mut Option<Self> {
302 &mut state.pending_sample_point
303 }
304}
305
306impl TimingPointsState {
307 fn add_control_point<P: Pending>(&mut self, time: f64, point: P, timing_change: bool) {
308 if (time - self.pending_control_points_time).abs() >= f64::EPSILON {
309 self.flush_pending_points();
310 }
311
312 if timing_change {
313 point.push_front(self);
314 } else {
315 point.push_back(self);
316 }
317
318 self.pending_control_points_time = time;
319 }
320
321 fn flush_pending_points(&mut self) {
322 if let Some(point) = self.pending_timing_point.take() {
323 self.control_points.add(point);
324 }
325
326 if let Some(point) = self.pending_difficulty_point.take() {
327 self.control_points.add(point);
328 }
329
330 if let Some(point) = self.pending_effect_point.take() {
331 self.control_points.add(point);
332 }
333
334 if let Some(point) = self.pending_sample_point.take() {
335 self.control_points.add(point);
336 }
337 }
338}
339
340impl DecodeState for TimingPointsState {
341 fn create(version: i32) -> Self {
342 Self {
343 general: GeneralState::create(version),
344 pending_control_points_time: 0.0,
345 pending_timing_point: None,
346 pending_difficulty_point: None,
347 pending_effect_point: None,
348 pending_sample_point: None,
349 control_points: ControlPoints::default(),
350 }
351 }
352}
353
354impl From<TimingPointsState> for TimingPoints {
355 fn from(mut state: TimingPointsState) -> Self {
356 state.flush_pending_points();
357
358 Self {
359 audio_file: state.general.audio_file,
360 audio_lead_in: state.general.audio_lead_in,
361 preview_time: state.general.preview_time,
362 default_sample_bank: state.general.default_sample_bank,
363 default_sample_volume: state.general.default_sample_volume,
364 stack_leniency: state.general.stack_leniency,
365 mode: state.general.mode,
366 letterbox_in_breaks: state.general.letterbox_in_breaks,
367 special_style: state.general.special_style,
368 widescreen_storyboard: state.general.widescreen_storyboard,
369 epilepsy_warning: state.general.epilepsy_warning,
370 samples_match_playback_rate: state.general.samples_match_playback_rate,
371 countdown: state.general.countdown,
372 countdown_offset: state.general.countdown_offset,
373 control_points: state.control_points,
374 }
375 }
376}
377
378impl DecodeBeatmap for TimingPoints {
379 type Error = ParseTimingPointsError;
380 type State = TimingPointsState;
381
382 fn parse_general(state: &mut Self::State, line: &str) -> Result<(), Self::Error> {
383 General::parse_general(&mut state.general, line).map_err(ParseTimingPointsError::General)
384 }
385
386 fn parse_editor(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
387 Ok(())
388 }
389
390 fn parse_metadata(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
391 Ok(())
392 }
393
394 fn parse_difficulty(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
395 Ok(())
396 }
397
398 fn parse_events(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
399 Ok(())
400 }
401
402 fn parse_timing_points(state: &mut Self::State, line: &str) -> Result<(), Self::Error> {
403 let mut split = line.trim_comment().split(',');
404
405 let (time, beat_len) = split
406 .next()
407 .zip(split.next())
408 .ok_or(ParseTimingPointsError::InvalidLine)?;
409
410 let time = time.parse_num::<f64>()?;
411
412 let beat_len = beat_len
414 .trim()
415 .parse::<f64>()
416 .map_err(ParseNumberError::InvalidFloat)?;
417
418 if beat_len < f64::from(-MAX_PARSE_VALUE) {
419 return Err(ParseNumberError::NumberUnderflow.into());
420 } else if beat_len > f64::from(MAX_PARSE_VALUE) {
421 return Err(ParseNumberError::NumberOverflow.into());
422 }
423
424 let speed_multiplier = if beat_len < 0.0 {
425 100.0 / -beat_len
426 } else {
427 1.0
428 };
429
430 let mut time_signature = TimeSignature::new_simple_quadruple();
431
432 if let Some(next) = split.next() {
433 if !matches!(next.chars().next(), Some('0')) {
434 time_signature = TimeSignature::new(next.parse_num()?)?;
435 }
436 }
437
438 let mut sample_set = split
439 .next()
440 .map(i32::parse)
441 .transpose()?
442 .map(SampleBank::try_from)
443 .and_then(Result::ok)
444 .unwrap_or(state.general.default_sample_bank);
445
446 let custom_sample_bank = split.next().map(i32::parse).transpose()?.unwrap_or(0);
447
448 let sample_volume = split
449 .next()
450 .map(i32::parse)
451 .transpose()?
452 .unwrap_or(state.general.default_sample_volume);
453
454 let timing_change = split
455 .next()
456 .map_or(true, |next| matches!(next.chars().next(), Some('1')));
457
458 let mut kiai_mode = false;
459 let mut omit_first_bar_signature = false;
460
461 if let Some(next) = split.next() {
462 let effect_flags: EffectFlags = next.parse()?;
463 kiai_mode = effect_flags.has_flag(EffectFlags::KIAI);
464 omit_first_bar_signature = effect_flags.has_flag(EffectFlags::OMIT_FIRST_BAR_LINE);
465 }
466
467 if sample_set == SampleBank::None {
468 sample_set = SampleBank::Normal;
469 }
470
471 if timing_change {
472 if beat_len.is_nan() {
473 return Err(ParseTimingPointsError::TimingControlPointNaN);
474 }
475
476 let timing = TimingPoint::new(time, beat_len, omit_first_bar_signature, time_signature);
477 state.add_control_point(time, timing, timing_change);
478 }
479
480 let difficulty = DifficultyPoint::new(time, beat_len, speed_multiplier);
481 state.add_control_point(time, difficulty, timing_change);
482
483 let sample = SamplePoint::new(time, sample_set, sample_volume, custom_sample_bank);
484 state.add_control_point(time, sample, timing_change);
485
486 let mut effect = EffectPoint::new(time, kiai_mode);
487
488 if matches!(state.general.mode, GameMode::Taiko | GameMode::Mania) {
489 effect.scroll_speed = speed_multiplier.clamp(0.01, 10.0);
490 }
491
492 state.add_control_point(time, effect, timing_change);
493
494 state.pending_control_points_time = time;
495
496 Ok(())
497 }
498
499 fn parse_colors(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
500 Ok(())
501 }
502
503 fn parse_hit_objects(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
504 Ok(())
505 }
506
507 fn parse_variables(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
508 Ok(())
509 }
510
511 fn parse_catch_the_beat(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
512 Ok(())
513 }
514
515 fn parse_mania(_: &mut Self::State, _: &str) -> Result<(), Self::Error> {
516 Ok(())
517 }
518}