1pub mod error;
2pub mod types;
3
4use crate::osu_file::types::Decimal;
5use either::Either;
6use nom::{
7 branch::alt,
8 combinator::{cut, map_res, success, verify, rest},
9 error::context,
10 sequence::{preceded, tuple},
11 Parser,
12};
13use rust_decimal_macros::dec;
14
15use crate::{helper::parse_zero_one_bool, parsers::*};
16
17use super::{
18 Error, Integer, Version, VersionedDefault, VersionedFrom, VersionedFromStr, VersionedToString,
19};
20
21pub use error::*;
22pub use types::*;
23
24#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
25pub struct TimingPoints(pub Vec<TimingPoint>);
26
27impl VersionedFromStr for TimingPoints {
28 type Err = Error<ParseError>;
29
30 fn from_str(s: &str, version: Version) -> std::result::Result<Option<Self>, Self::Err> {
31 let mut timing_points = Vec::new();
32
33 for (line_index, s) in s.lines().enumerate() {
34 if s.trim().is_empty() {
35 continue;
36 }
37
38 timing_points.push(Error::new_from_result_into(
39 TimingPoint::from_str(s, version),
40 line_index,
41 )?);
42 }
43
44 if let Some(s) = timing_points.get(0) {
45 if s.is_some() {
46 Ok(Some(TimingPoints(
47 timing_points
48 .into_iter()
49 .map(|v| v.unwrap())
50 .collect::<Vec<_>>(),
51 )))
52 } else {
53 Ok(None)
54 }
55 } else {
56 Ok(Some(TimingPoints(Vec::new())))
57 }
58 }
59}
60
61impl VersionedToString for TimingPoints {
62 fn to_string(&self, version: Version) -> Option<String> {
63 Some(
64 self.0
65 .iter()
66 .filter_map(|t| t.to_string(version))
67 .collect::<Vec<_>>()
68 .join("\n"),
69 )
70 }
71}
72
73impl VersionedDefault for TimingPoints {
74 fn default(_: Version) -> Option<Self> {
75 Some(TimingPoints(Vec::new()))
76 }
77}
78
79#[derive(Clone, Hash, PartialEq, Eq, Debug)]
83pub struct TimingPoint {
84 time: Decimal,
86 beat_length: Decimal,
87 meter: Integer,
88 sample_set: SampleSet,
89 sample_index: SampleIndex,
90 volume: Volume,
91 uninherited: Option<bool>,
92 effects: Option<Effects>,
93}
94
95impl TimingPoint {
96 pub fn beat_duration_ms_to_bpm(
98 beat_duration_ms: rust_decimal::Decimal,
99 ) -> rust_decimal::Decimal {
100 rust_decimal::Decimal::ONE / beat_duration_ms * dec!(60000)
101 }
102
103 pub fn bpm_to_beat_duration_ms(bpm: rust_decimal::Decimal) -> rust_decimal::Decimal {
105 rust_decimal::Decimal::ONE / (bpm / dec!(60000))
106 }
107
108 pub fn new_inherited(
110 time: Integer,
111 slider_velocity_multiplier: rust_decimal::Decimal,
112 meter: Integer,
113 sample_set: SampleSet,
114 sample_index: SampleIndex,
115 volume: Volume,
116 effects: Effects,
117 ) -> Self {
118 let beat_length = (rust_decimal::Decimal::ONE / slider_velocity_multiplier) * dec!(-100);
119
120 Self {
121 time: time.into(),
122 beat_length: beat_length.into(),
123 meter,
124 sample_set,
125 sample_index,
126 volume,
127 uninherited: Some(false),
128 effects: Some(effects),
129 }
130 }
131
132 pub fn new_uninherited(
134 time: Integer,
135 beat_duration_ms: Decimal,
136 meter: Integer,
137 sample_set: SampleSet,
138 sample_index: SampleIndex,
139 volume: Volume,
140 effects: Effects,
141 ) -> Self {
142 Self {
143 time: time.into(),
144 beat_length: beat_duration_ms,
145 meter,
146 sample_set,
147 sample_index,
148 volume,
149 uninherited: Some(true),
150 effects: Some(effects),
151 }
152 }
153
154 pub fn calc_bpm(&self) -> Option<rust_decimal::Decimal> {
157 match self.uninherited {
158 Some(uninherited) => {
159 if uninherited {
160 match self.beat_length.get() {
161 Either::Left(value) => Some(Self::beat_duration_ms_to_bpm(*value)),
162 Either::Right(_) => None,
163 }
164 } else {
165 None
166 }
167 }
168 None => None,
169 }
170 }
171 pub fn calc_slider_velocity_multiplier(&self) -> Option<rust_decimal::Decimal> {
174 match self.uninherited {
175 Some(uninherited) => {
176 if uninherited {
177 None
178 } else {
179 match self.beat_length.get() {
180 Either::Left(value) => {
181 Some(rust_decimal::Decimal::ONE / (value / dec!(-100)))
182 }
183 Either::Right(_) => None,
184 }
185 }
186 }
187 None => None,
188 }
189 }
190
191 pub fn time(&self) -> &Decimal {
194 &self.time
195 }
196
197 pub fn set_time(&mut self, time: Integer) {
199 self.time = time.into();
200 }
201
202 pub fn meter(&self) -> i32 {
204 self.meter
205 }
206
207 pub fn set_meter(&mut self, meter: Integer) {
209 self.meter = meter;
210 }
211
212 pub fn sample_set(&self) -> SampleSet {
214 self.sample_set
215 }
216
217 pub fn set_sample_set(&mut self, sample_set: SampleSet) {
219 self.sample_set = sample_set;
220 }
221
222 pub fn sample_index(&self) -> SampleIndex {
224 self.sample_index
225 }
226
227 pub fn sample_index_mut(&mut self) -> &mut SampleIndex {
229 &mut self.sample_index
230 }
231
232 pub fn volume(&self) -> &Volume {
234 &self.volume
235 }
236
237 pub fn set_volume(&mut self, volume: Volume) {
239 self.volume = volume;
240 }
241
242 pub fn uninherited(&self) -> bool {
244 self.uninherited.unwrap_or(true)
245 }
246
247 pub fn set_uninherited(&mut self, uninherited: bool) {
249 self.uninherited = Some(uninherited);
250 }
251
252 pub fn effects(&self) -> Option<&Effects> {
254 self.effects.as_ref()
255 }
256
257 pub fn effects_mut(&mut self) -> &mut Option<Effects> {
259 &mut self.effects
260 }
261}
262
263const OLD_VERSION_TIME_OFFSET: rust_decimal::Decimal = dec!(24);
264
265impl VersionedFromStr for TimingPoint {
266 type Err = ParseTimingPointError;
267
268 fn from_str(s: &str, version: Version) -> std::result::Result<Option<Self>, Self::Err> {
269 let meter_fallback = 4;
270 let sample_set_fallback = SampleSet::Normal;
271 let sample_index_fallback = <SampleIndex as VersionedFrom<u32>>::from(1, version).unwrap();
272 let volume_fallback = <Volume as VersionedFrom<Integer>>::from(100, version).unwrap();
273
274 let (
277 _,
278 (
279 time,
280 (beat_length, meter, sample_set, (sample_index, (volume, uninherited, effects))),
281 ),
282 ) = tuple((
283 context(
284 ParseTimingPointError::InvalidTime.into(),
285 comma_field_type(),
286 )
287 .map(|mut t: Decimal| {
288 if (3..=4).contains(&version) {
289 if let Either::Left(value) = t.get_mut() {
290 *value += OLD_VERSION_TIME_OFFSET;
291 }
292 }
293
294 t
295 }),
296 preceded(
297 context(ParseTimingPointError::MissingBeatLength.into(), comma()),
298 alt((
299 preceded(verify(success(0), |_| version <= 3), consume_rest_type()).map(
300 |beat_length| {
301 (
302 beat_length,
303 meter_fallback,
304 sample_set_fallback,
305 (sample_index_fallback, (volume_fallback, None, None)),
306 )
307 },
308 ),
309 tuple((
310 comma_field_type(),
311 preceded(
312 context(ParseTimingPointError::MissingMeter.into(), comma()),
313 context(
314 ParseTimingPointError::InvalidMeter.into(),
315 comma_field_type(),
316 ),
317 ),
318 preceded(
319 context(ParseTimingPointError::MissingSampleSet.into(), comma()),
320 context(
321 ParseTimingPointError::InvalidSampleSet.into(),
322 comma_field_versioned_type(version),
323 ),
324 ),
325 preceded(
326 context(ParseTimingPointError::MissingSampleIndex.into(), comma()),
327 alt((
328 preceded(
329 verify(success(0), |_| version <= 4),
330 context(
331 ParseTimingPointError::InvalidSampleIndex.into(),
332 consume_rest_versioned_type(version),
333 ),
334 )
335 .map(|sample_index| (sample_index, (volume_fallback, None, None))),
336 tuple((
337 context(
338 ParseTimingPointError::InvalidSampleIndex.into(),
339 comma_field_versioned_type(version),
340 ),
341 preceded(
342 context(
343 ParseTimingPointError::MissingVolume.into(),
344 comma(),
345 ),
346 alt((
347 preceded(
348 verify(success(0), |_| version <= 4),
349 context(
350 ParseTimingPointError::InvalidVolume.into(),
351 cut(consume_rest_versioned_type(version)),
352 ),
353 )
354 .map(|volume| (volume, None, None)),
355 context(
356 ParseTimingPointError::InvalidVolume.into(),
357 consume_rest_versioned_type(version),
358 )
359 .map(|volume| (volume, None, None)),
360 tuple((
361 context(
362 ParseTimingPointError::InvalidVolume.into(),
363 comma_field_versioned_type(version),
364 ),
365 preceded(context(
366 ParseTimingPointError::MissingUninherited
367 .into(),
368 comma(),
369 ),
370 alt((
371 preceded(verify(success(0), |_| version <= 5),
372 context(
373 ParseTimingPointError::InvalidUninherited.into(),
374 map_res(rest, parse_zero_one_bool),
375 )
376 ).map(|uninherited| (Some(uninherited), None)),
377 tuple((
378 context(
379 ParseTimingPointError::InvalidUninherited
380 .into(),
381 map_res(comma_field(), parse_zero_one_bool),
382 ),
383 preceded(
384 context(
385 ParseTimingPointError::MissingEffects
386 .into(),
387 comma(),
388 ),
389 context(
390 ParseTimingPointError::InvalidEffects
391 .into(),
392 consume_rest_versioned_type(version),
393 ),
394 ),
395 )).map(|(uninherited, effects)| (Some(uninherited), Some(effects))),
396 ))
397 )))
398 .map(
399 |(volume, (uninherited, effects))| {
400 (volume, uninherited, effects)
401 },
402 ),
403 )),
404 ),
405 ))
406 .map(
407 |(sample_index, (volume, uninherited, effects))| {
408 (sample_index, (volume, uninherited, effects))
409 },
410 ),
411 )),
412 ),
413 ))
414 .map(
415 |(
416 beat_length,
417 meter,
418 sample_set,
419 (sample_index, (volume, uninherited, effects)),
420 )| {
421 (
422 beat_length,
423 meter,
424 sample_set,
425 (sample_index, (volume, uninherited, effects)),
426 )
427 },
428 ),
429 )),
430 ),
431 ))(s)?;
432
433 Ok(Some(TimingPoint {
434 time,
435 beat_length,
436 meter,
437 sample_set,
438 sample_index,
439 volume,
440 uninherited,
441 effects,
442 }))
443 }
444}
445
446impl VersionedToString for TimingPoint {
447 fn to_string(&self, version: Version) -> Option<String> {
448 let mut fields = vec![
449 if (3..=4).contains(&version) {
450 match self.time.get() {
451 Either::Left(value) => (value - OLD_VERSION_TIME_OFFSET).to_string(),
452 Either::Right(value) => value.to_string(),
453 }
454 } else {
455 self.time.to_string()
456 },
457 self.beat_length.to_string(),
458 ];
459
460 if version > 3 {
461 fields.push(self.meter.to_string());
462 fields.push(self.sample_set.to_string(version).unwrap());
463 fields.push(self.sample_index.to_string(version).unwrap());
464 }
465 if version > 4 {
466 fields.push(self.volume.to_string(version).unwrap());
467 match self.uninherited {
468 Some(value) => fields.push((value as u8).to_string()),
469 None => {
470 if self.effects.is_some() {
471 fields.push(String::new())
472 }
473 }
474 }
475 if let Some(value) = self.effects {
476 fields.push(value.to_string(version).unwrap())
477 }
478 }
479
480 Some(fields.join(","))
481 }
482}