Skip to main content

rgc_chart/models/osu/
timing_points.rs

1use std::str::FromStr;
2
3#[derive(Debug, Clone, Copy, PartialEq)]
4pub struct TimingPoint {
5    pub time: f32,
6    pub beat_length: f32,
7    pub meter: i32,
8    pub sample_set: i32,
9    pub sample_index: i32,
10    pub volume: i32,
11    pub uninherited: bool,
12    pub effects: i32,
13}
14
15impl TimingPoint {
16    pub fn new(
17        time: f32,
18        beat_length: f32,
19        meter: i32,
20        sample_set: i32,
21        sample_index: i32,
22        volume: i32,
23        uninherited: bool,
24        effects: i32,
25    ) -> Self {
26        Self {
27            time,
28            beat_length,
29            meter,
30            sample_set,
31            sample_index,
32            volume,
33            uninherited,
34            effects,
35        }
36    }
37    
38    pub fn is_uninherited(&self) -> bool {
39        self.uninherited
40    }
41    
42    pub fn is_inherited(&self) -> bool {
43        !self.uninherited
44    }
45    
46    pub fn bpm(&self) -> Option<f32> {
47        if self.is_uninherited() && self.beat_length > 0.0 {
48            Some(60000.0 / self.beat_length)
49        } else {
50            None
51        }
52    }
53    
54    pub fn slider_velocity_multiplier(&self) -> Option<f32> {
55        if self.is_inherited() && self.beat_length < 0.0 {
56            Some(-100.0 / self.beat_length)
57        } else {
58            None
59        }
60    }
61    
62    pub fn to_osu_format(&self) -> String {
63        format!(
64            "{},{},{},{},{},{},{},{}",
65            self.time,
66            self.beat_length,
67            self.meter,
68            self.sample_set,
69            self.sample_index,
70            self.volume,
71            if self.uninherited { 1 } else { 0 },
72            self.effects
73        )
74    }
75}
76
77impl Default for TimingPoint {
78    fn default() -> Self {
79        TimingPoint { time: 0.0, beat_length: 500.0, meter: 4, sample_set: 0, sample_index: 0, volume: 100, uninherited: true, effects: 0 }
80    }
81}
82
83impl FromStr for TimingPoint {
84    type Err = String;
85    
86    fn from_str(s: &str) -> Result<Self, Self::Err> {
87        let parts: Vec<&str> = s.split(',').collect();
88        
89        if parts.len() != 8 {
90            return Err(format!("Expected 8 comma-separated values, found {}", parts.len()));
91        }
92        
93        let time = parts[0].parse::<f32>()
94            .map_err(|_| format!("Invalid time value: {}", parts[0]))?;
95            
96        let beat_length = parts[1].parse::<f32>()
97            .map_err(|_| format!("Invalid beatLength value: {}", parts[1]))?;
98            
99        let meter = parts[2].parse::<i32>()
100            .map_err(|_| format!("Invalid meter value: {}", parts[2]))?;
101            
102        let sample_set = parts[3].parse::<i32>()
103            .map_err(|_| format!("Invalid sampleSet value: {}", parts[3]))?;
104            
105        let sample_index = parts[4].parse::<i32>()
106            .map_err(|_| format!("Invalid sampleIndex value: {}", parts[4]))?;
107            
108        let volume = parts[5].parse::<i32>()
109            .map_err(|_| format!("Invalid volume value: {}", parts[5]))?;
110            
111        let uninherited_val = parts[6].parse::<i32>()
112            .map_err(|_| format!("Invalid uninherited value: {}", parts[6]))?;
113        let uninherited = uninherited_val != 0;
114        
115        let effects = parts[7].parse::<i32>()
116            .map_err(|_| format!("Invalid effects value: {}", parts[7]))?;
117        
118        Ok(TimingPoint {
119            time,
120            beat_length,
121            meter,
122            sample_set,
123            sample_index,
124            volume,
125            uninherited,
126            effects,
127        })
128    }
129}
130
131#[derive(Debug, Clone, PartialEq)]
132pub struct TimingPoints {
133    pub timing_points: Vec<TimingPoint>,
134}
135
136impl Default for TimingPoints {
137    fn default() -> Self {
138        Self {
139            timing_points: Vec::new(),
140        }
141    }
142}
143
144impl FromStr for TimingPoints {
145    type Err = String;
146    
147    fn from_str(s: &str) -> Result<Self, Self::Err> {
148        let mut timing_points = Vec::new();
149        
150        for line in s.lines() {
151            let line = line.trim();
152            if line.is_empty() || line.starts_with("//") {
153                continue;
154            }
155            
156            let timing_point = TimingPoint::from_str(line)?;
157            timing_points.push(timing_point);
158        }
159        
160        Ok(TimingPoints { timing_points })
161    }
162}
163
164impl TimingPoints {
165    pub fn new() -> Self {
166        Self::default()
167    }
168    
169    pub fn add_timing_point(&mut self, timing_point: TimingPoint) {
170        self.timing_points.push(timing_point);
171    }
172    
173    pub fn count(&self) -> usize {
174        self.timing_points.len()
175    }
176    
177    pub fn uninherited_count(&self) -> usize {
178        self.timing_points.iter().filter(|tp| tp.is_uninherited()).count()
179    }
180    
181    pub fn inherited_count(&self) -> usize {
182        self.timing_points.iter().filter(|tp| tp.is_inherited()).count()
183    }
184    
185    pub fn start_time(&self) -> Option<f32> {
186        self.timing_points.first().map(|tp| tp.time)
187    }
188    
189    pub fn end_time(&self) -> Option<f32> {
190        self.timing_points.last().map(|tp| tp.time)
191    }
192    
193    pub fn points_in_range(&self, start_time: f32, end_time: f32) -> Vec<&TimingPoint> {
194        self.timing_points
195            .iter()
196            .filter(|tp| tp.time >= start_time && tp.time <= end_time)
197            .collect()
198    }
199    
200    pub fn sort_by_time(&mut self) {
201        self.timing_points.sort_by(|a, b| {
202            match (a.time.is_nan(), b.time.is_nan()) {
203                (true, true) => std::cmp::Ordering::Equal,
204                (true, false) => std::cmp::Ordering::Greater,
205                (false, true) => std::cmp::Ordering::Less,
206                (false, false) => a.time.partial_cmp(&b.time).unwrap(),
207            }
208        });
209    }
210    
211    pub fn get_bpms(&self) -> Vec<f32> {
212        self.timing_points
213            .iter()
214            .filter_map(|tp| tp.bpm())
215            .collect()
216    }
217    
218    pub fn get_slider_velocities(&self) -> Vec<f32> {
219        self.timing_points
220            .iter()
221            .filter_map(|tp| tp.slider_velocity_multiplier())
222            .collect()
223    }
224    
225    pub fn to_osu_format(&self) -> String {
226        self.timing_points
227            .iter()
228            .map(|tp| tp.to_osu_format())
229            .collect::<Vec<_>>()
230            .join("\n")
231    }
232}