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}