rosu_pp/model/control_point/
difficulty.rs

1use crate::util::float_ext::FloatExt;
2
3/// Difficulty-related info about this control point.
4#[derive(Clone, Debug, PartialEq)]
5pub struct DifficultyPoint {
6    pub time: f64,
7    pub slider_velocity: f64,
8    pub bpm_multiplier: f64,
9    pub generate_ticks: bool,
10}
11
12impl DifficultyPoint {
13    pub const DEFAULT_SLIDER_VELOCITY: f64 = 1.0;
14    pub const DEFAULT_BPM_MULTIPLIER: f64 = 1.0;
15    pub const DEFAULT_GENERATE_TICKS: bool = true;
16
17    pub fn new(time: f64, beat_len: f64, speed_multiplier: f64) -> Self {
18        Self {
19            time,
20            slider_velocity: speed_multiplier.clamp(0.1, 10.0),
21            bpm_multiplier: if beat_len < 0.0 {
22                f64::from((-beat_len) as f32).clamp(10.0, 10_000.0) / 100.0
23            } else {
24                1.0
25            },
26            generate_ticks: !beat_len.is_nan(),
27        }
28    }
29
30    pub fn is_redundant(&self, existing: &Self) -> bool {
31        self.generate_ticks == existing.generate_ticks
32            && self.slider_velocity.eq(existing.slider_velocity)
33    }
34}
35
36impl Default for DifficultyPoint {
37    fn default() -> Self {
38        Self {
39            time: 0.0,
40            slider_velocity: Self::DEFAULT_SLIDER_VELOCITY,
41            bpm_multiplier: Self::DEFAULT_BPM_MULTIPLIER,
42            generate_ticks: Self::DEFAULT_GENERATE_TICKS,
43        }
44    }
45}
46
47pub fn difficulty_point_at(points: &[DifficultyPoint], time: f64) -> Option<&DifficultyPoint> {
48    points
49        .binary_search_by(|probe| probe.time.total_cmp(&time))
50        .map_or_else(|i| i.checked_sub(1), Some)
51        .map(|i| &points[i])
52}