rosu_map/section/timing_points/
decode.rs

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/// Struct containing all data from a `.osu` file's `[TimingPoints]` and
17/// `[General]` section.
18#[derive(Clone, Debug, PartialEq)]
19pub struct TimingPoints {
20    // General
21    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    // TimingPoints
37    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/// All control points of a [`Beatmap`].
88#[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    /// Finds the [`DifficultyPoint`] that is active at the given time.
98    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    /// Finds the [`EffectPoint`] that is active at the given time.
106    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    /// Finds the [`SamplePoint`] that is active at the given time.
114    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    /// Finds the [`TimingPoint`] that is active at the given time.
124    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    /// Add a [`ControlPoint`] into its corresponding list.
134    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
141/// A control point to be added into a collection of type `C`.
142pub trait ControlPoint<C> {
143    /// Whether `self` is redundant w.r.t. an already existing control point.
144    fn check_already_existing(&self, control_points: &C) -> bool;
145
146    /// Adding the control point into the collection.
147    ///
148    /// Note that control points should be inserted in order by time.
149    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    /// All the ways that parsing a `.osu` file into [`TimingPoints`] can fail.
230    #[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
249/// The parsing state for [`TimingPoints`] in [`DecodeBeatmap`].
250pub 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        // Manual `str::parse_num::<f64>` so that NaN does not cause an error
413        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}