rosu-pattern-detector 0.2.1

Pattern detector for osu!
Documentation
use crate::mania::models::base::{ManiaMeasure, NotesStruct};
use crate::mania::models::pattern::Pattern;
use crate::structs::CommonMeasure;
use rosu_map;
use rosu_map::section::hit_objects::{HitObject, HitObjectKind};
use rosu_map::section::timing_points::TimingPoint;
use std::collections::{BTreeMap, HashMap}   ;
use crate::mania::detector::HitObjects;

pub(crate) fn transform_hit_object_to_mania_notes(
    ho: Vec<HitObject>,
    num_keys: i32,
) -> Vec<NotesStruct> {
    let num_keys = num_keys as usize;
    let positions = match num_keys {
        4 => vec![64.0, 192.0, 320.0, 448.0],
        7 => vec![36.0, 109.0, 182.0, 256.0, 329.0, 402.0, 475.0],
        _ => return Vec::new(),
    };
    let mut grouped: BTreeMap<i32, Vec<usize>> = BTreeMap::new();
    for hit_object in ho {
        let (pos_x, timestamp) = match &hit_object.kind {
            HitObjectKind::Circle(circle) => (circle.pos.x, hit_object.start_time as i32),
            HitObjectKind::Slider(slider) => (slider.pos.x, hit_object.start_time as i32),
            _ => continue,
        };
        if let Some(key_index) = positions.iter().position(|&x| x == pos_x) {
            grouped.entry(timestamp).or_default().push(key_index);
        }
    }
    let mut notes_vec = Vec::with_capacity(grouped.len());
    for (timestamp, indices) in grouped {
        let mut keys = vec![false; num_keys];
        for &key_index in &indices {
            keys[key_index] = true;
        }
        let temporary_note = NotesStruct {
            timestamp,
            notes: keys.clone(),
        };
        notes_vec.push(temporary_note);
    }
    notes_vec
}


pub(crate) fn group_notes_by_measures(
    notes: Vec<NotesStruct>,
    timing_points: Vec<TimingPoint>,
) -> HitObjects {
    let mut measures = HitObjects(HashMap::new());

    for note in notes {
        let timing_point = timing_points
            .iter()
            .rev()
            .find(|tp| note.timestamp >= tp.time as i32)
            .unwrap_or_else(|| timing_points.first().unwrap());

        let beat_len = timing_point.beat_len as f32;
        let start_time = timing_point.time as i32;

        let measure_idx = ((note.timestamp - start_time) as f32 / beat_len).floor() as i32;
        let measure_start_time = start_time + (measure_idx as f32 * beat_len) as i32;

        let measure_entry = measures.0.entry(measure_start_time).or_insert_with(|| ManiaMeasure {
            measure: CommonMeasure {
                start_time: measure_start_time,
                npm: 0,
            },
            notes: Vec::new(),
            pattern: Pattern::None,
            value: 0.0,
        });


        measure_entry.notes.push(note.clone());
        measure_entry.measure.npm += note.notes.iter().filter(|&&n| n).count() as i32;
    }

    measures
}