rosu_map/section/timing_points/control_points/
sample.rs

1use std::{cmp::Ordering, num::NonZeroU32};
2
3use crate::section::hit_objects::hit_samples::{HitSampleInfo, HitSampleInfoName, SampleBank};
4
5#[derive(Clone, Debug, PartialEq)]
6/// Audio-related info about this control point.
7pub struct SamplePoint {
8    pub time: f64,
9    pub sample_bank: SampleBank,
10    pub sample_volume: i32,
11    pub custom_sample_bank: i32,
12}
13
14impl SamplePoint {
15    pub const DEFAULT_SAMPLE_BANK: SampleBank = SampleBank::Normal;
16    pub const DEFAULT_SAMPLE_VOLUME: i32 = 100;
17    pub const DEFAULT_CUSTOM_SAMPLE_BANK: i32 = 0;
18
19    pub fn new(
20        time: f64,
21        sample_bank: SampleBank,
22        sample_volume: i32,
23        custom_sample_bank: i32,
24    ) -> Self {
25        Self {
26            time,
27            sample_bank,
28            sample_volume: sample_volume.clamp(0, 100),
29            custom_sample_bank,
30        }
31    }
32
33    pub fn is_redundant(&self, existing: &Self) -> bool {
34        self.sample_bank == existing.sample_bank
35            && self.sample_volume == existing.sample_volume
36            && self.custom_sample_bank == existing.custom_sample_bank
37    }
38
39    pub fn apply(&self, sample: &mut HitSampleInfo) {
40        if matches!(sample.name, HitSampleInfoName::Default(_)) {
41            if sample.custom_sample_bank == 0 {
42                sample.custom_sample_bank = self.custom_sample_bank;
43
44                if sample.custom_sample_bank >= 2 {
45                    // SAFETY: The value is guaranteed to be >= 2
46                    sample.suffix = Some(unsafe {
47                        NonZeroU32::new_unchecked(sample.custom_sample_bank as u32)
48                    });
49                }
50            }
51
52            if sample.volume == 0 {
53                sample.volume = self.sample_volume.clamp(0, 100);
54            }
55
56            if !sample.bank_specified {
57                sample.bank = self.sample_bank;
58                sample.bank_specified = true;
59            }
60        } else {
61            sample.bank = SamplePoint::DEFAULT_SAMPLE_BANK;
62            sample.suffix = None;
63
64            if sample.volume == 0 {
65                sample.volume = self.sample_volume.clamp(0, 100);
66            }
67
68            sample.custom_sample_bank = 1;
69            sample.bank_specified = false;
70            sample.is_layered = false;
71        }
72    }
73}
74
75impl Default for SamplePoint {
76    fn default() -> Self {
77        Self {
78            time: 0.0,
79            sample_bank: Self::DEFAULT_SAMPLE_BANK,
80            sample_volume: Self::DEFAULT_SAMPLE_VOLUME,
81            custom_sample_bank: Self::DEFAULT_CUSTOM_SAMPLE_BANK,
82        }
83    }
84}
85
86impl PartialOrd for SamplePoint {
87    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
88        self.time.partial_cmp(&other.time)
89    }
90}