fixed Beatmap::bpm
This commit is contained in:
+2
-1
@@ -1,6 +1,7 @@
|
|||||||
## Upcoming
|
## Upcoming
|
||||||
|
|
||||||
Nothing as of now
|
- __Fixes:__
|
||||||
|
- The `Beatmap::bpm` method now works properly by considering the most common beat length instead of just the first one
|
||||||
|
|
||||||
# v0.9.3 (2023-01-28)
|
# v0.9.3 (2023-01-28)
|
||||||
|
|
||||||
|
|||||||
+63
-4
@@ -1,4 +1,4 @@
|
|||||||
use std::{borrow::Cow, cmp::Ordering};
|
use std::{borrow::Cow, cmp::Ordering, collections::HashMap};
|
||||||
|
|
||||||
use crate::{parse::HitObject, util::SortedVec};
|
use crate::{parse::HitObject, util::SortedVec};
|
||||||
|
|
||||||
@@ -78,10 +78,69 @@ impl Beatmap {
|
|||||||
/// The beats per minute of the map.
|
/// The beats per minute of the map.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn bpm(&self) -> f64 {
|
pub fn bpm(&self) -> f64 {
|
||||||
match self.timing_points.first() {
|
// This is incorrect if the last object is a slider since there
|
||||||
Some(point) => point.beat_len.recip() * 1000.0 * 60.0,
|
// is no reasonable way to get the slider end time at this point.
|
||||||
None => 0.0,
|
let last_time = self
|
||||||
|
.hit_objects
|
||||||
|
.last()
|
||||||
|
.map(HitObject::end_time)
|
||||||
|
.or_else(|| self.timing_points.last().map(|t| t.time))
|
||||||
|
.unwrap_or(0.0);
|
||||||
|
|
||||||
|
/// Maps beat_len to a cumulative duration
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct BeatLenDuration {
|
||||||
|
last_time: f64,
|
||||||
|
map: HashMap<u64, f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BeatLenDuration {
|
||||||
|
fn new(last_time: f64) -> Self {
|
||||||
|
Self {
|
||||||
|
last_time,
|
||||||
|
map: HashMap::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(&mut self, beat_len: f64, curr_time: f64, next_time: f64) {
|
||||||
|
let beat_len = (1000.0 * beat_len).round() / 1000.0;
|
||||||
|
let entry = self.map.entry(beat_len.to_bits()).or_default();
|
||||||
|
|
||||||
|
if curr_time <= self.last_time {
|
||||||
|
*entry += next_time - curr_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut bpm_points = BeatLenDuration::new(last_time);
|
||||||
|
|
||||||
|
// * osu-stable forced the first control point to start at 0.
|
||||||
|
// * This is reproduced here to maintain compatibility around
|
||||||
|
// * osu!mania scroll speed and song select display.
|
||||||
|
match &self.timing_points[..] {
|
||||||
|
[curr] => bpm_points.add(curr.beat_len, 0.0, last_time),
|
||||||
|
[curr, next, ..] => bpm_points.add(curr.beat_len, 0.0, next.time),
|
||||||
|
[] => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.timing_points
|
||||||
|
.iter()
|
||||||
|
.skip(1)
|
||||||
|
.zip(self.timing_points.iter().skip(2).map(|t| t.time))
|
||||||
|
.for_each(|(curr, next_time)| bpm_points.add(curr.beat_len, curr.time, next_time));
|
||||||
|
|
||||||
|
if let [.., _, curr] = &self.timing_points[..] {
|
||||||
|
bpm_points.add(curr.beat_len, curr.time, last_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
let most_common_beat_len = bpm_points
|
||||||
|
.map
|
||||||
|
.into_iter()
|
||||||
|
// * Get the most common one, or 0 as a suitable default
|
||||||
|
.max_by(|(_, a), (_, b)| a.total_cmp(b))
|
||||||
|
.map_or(0.0, |(beat_len, _)| f64::from_bits(beat_len));
|
||||||
|
|
||||||
|
60_000.0 / most_common_beat_len
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sum up the duration of all breaks (in milliseconds).
|
/// Sum up the duration of all breaks (in milliseconds).
|
||||||
|
|||||||
Reference in New Issue
Block a user