use crate::errors;
use crate::models::common::*;
use crate::models::generic::{
GenericManiaChart,
ChartInfo,
HitObjects,
Metadata,
TimingPoints
};
use crate::models::timeline::{HitObjectTimeline, TimelineOps, TimelineTimingPoint, TimingPointTimeline};
use crate::utils::rhythm::calculate_time_from_beat;
use crate::utils::string::{
remove_comments, StrDefaultExtension, StrNumericDefaultExtension, trim_split_iter,
};
use crate::utils::time::{merge_bpm_and_stops, to_millis};
type BpmsAndStops = (Vec<f32>, Vec<f32>, Vec<TimingChangeType>);
pub fn parse_bpms(raw: &str) -> (Vec<f32>, Vec<f32>) {
match raw {
ChartDefaults::RAW_BPMS => return (vec![0.0], vec![*ChartDefaults::BPM]),
_ => {}
}
let mut beats = Vec::new();
let mut bpms = Vec::new();
raw.split(',')
.filter_map(|beat_bpm_str| {
let mut beat_bpm = beat_bpm_str.trim().split('=');
if let (Some(beat_str), Some(bpm_str)) = (beat_bpm.next(), beat_bpm.next()) {
if let (Ok(beat), Ok(bpm)) = (beat_str.parse::<f32>(), bpm_str.parse::<f32>()) {
debug_assert_ne!(bpm, 0.0);
return Some((beat, bpm));
}
}
None
})
.for_each(|(beat, bpm)| {
beats.push(beat);
bpms.push(bpm);
});
(beats, bpms)
}
pub fn parse_stops(raw: &str) -> (Vec<f32>, Vec<f32>) {
match raw {
ChartDefaults::RAW_STOPS => return (vec![], vec![]),
_ => {}
}
let mut beats = Vec::new();
let mut durations = Vec::new();
raw.split(',')
.filter_map(|beat_bpm_str| {
let mut beat_bpm = beat_bpm_str.trim().split('=');
if let (Some(beat_str), Some(duration_str)) = (beat_bpm.next(), beat_bpm.next()) {
if let (Ok(beat), Ok(duration)) =
(beat_str.parse::<f32>(), duration_str.parse::<f32>())
{
return Some((beat, to_millis(duration)));
}
}
None
})
.for_each(|(beat, bpm)| {
beats.push(beat);
durations.push(bpm);
});
(beats, durations)
}
pub fn parse_keys_in_row(row: &str) -> Vec<Key> {
let mut result: Vec<Key> = Vec::with_capacity(row.len());
for c in row.chars() {
result.push(get_sm_note_type(c));
}
result
}
#[inline]
pub(crate) fn get_sm_note_type(note: char) -> Key {
match note {
'0' => Key::empty(),
'1' => Key::normal(),
'2' => Key::slider_start(None),
'3' => Key::slider_end(),
'4' => Key::slider_start(None),
'M' => Key::mine(),
'F' => Key::fake(),
_ => Key::unknown(),
}
}
fn process_sections<F>(raw_chart: &str, mut lambda: F)
where
F: FnMut(&str, &str),
{
for pair in raw_chart.split(';') {
if let Some(colon_index) = pair.find(":") {
let header = pair[..colon_index].trim();
let content = pair[colon_index + 1..].trim();
if let Some(second_colon_index) = content.find(":") {
let first_content = content[..second_colon_index].trim();
let second_content = content[second_colon_index + 1..].trim();
let full_content = format!("{}:{}", first_content, second_content);
lambda(header, full_content.as_str());
} else {
lambda(header, content);
}
}
}
}
fn process_timing_points(bpms_and_stops: &BpmsAndStops, start_time: i32) -> TimingPoints {
let mut timeline = TimingPointTimeline::new();
let (beats, bpms_and_durations, change_types) = bpms_and_stops;
let stops: Vec<_> = beats
.iter()
.zip(bpms_and_durations.iter())
.zip(change_types.iter())
.enumerate()
.filter_map(|(_i, ((beat, value), change_type))| {
let insert_time = calculate_time_from_beat(
*beat,
start_time,
(beats, bpms_and_durations, change_types),
);
match change_type {
TimingChangeType::Bpm => {
timeline.add(TimelineTimingPoint {
time: insert_time,
value: *value,
change_type: TimingChangeType::Bpm,
});
None
}
TimingChangeType::Stop => Some((insert_time, *value)),
_ => None,
}
})
.collect();
for (stop_time, stop_duration) in stops {
timeline.add(TimelineTimingPoint {
time: stop_time,
value: 0.0,
change_type: TimingChangeType::Sv,
});
let stop_end_time = stop_time + stop_duration as i32;
timeline.add(TimelineTimingPoint {
time: stop_end_time,
value: 1.0,
change_type: TimingChangeType::Sv,
});
}
let mut timing_points = TimingPoints::with_capacity(timeline.len());
timeline.to_timing_points(&mut timing_points, start_time);
timing_points
}
fn process_notes(
raw_note_data: &str,
chartinfo: &mut ChartInfo,
bpms_and_stops: &BpmsAndStops,
) -> HitObjects {
if raw_note_data.contains("No Note Data") {
return HitObjects::with_capacity(2048);
}
let (beats, bpms_and_durations, change_types) = bpms_and_stops;
let start_time = chartinfo.audio_offset;
let separated_note_data: Vec<&str> = trim_split_iter(raw_note_data.split(":"), false);
let difficulty_name = separated_note_data[2];
chartinfo.difficulty_name = difficulty_name.or_default_empty(ChartDefaults::DIFFICULTY_NAME);
let key_count = 4;
let raw_notes = separated_note_data
.last()
.unwrap_or(&"Failed to get raw notes in notes section");
let measures: Vec<&str> = raw_notes.split(",").collect();
let mut measure_beat_count: f32 = 0.0;
let mut rows = Vec::new();
for measure in measures {
let trimmed_measure = measure.trim();
let measure_rows: Vec<_> = trimmed_measure.split('\n').collect();
let row_count = measure_rows.len();
let beat_time_per_row = 4.0 / row_count as f32;
for (row_index, row) in measure_rows.into_iter().enumerate() {
let row_beat = measure_beat_count + row_index as f32 * beat_time_per_row;
let row_time = calculate_time_from_beat(
row_beat,
start_time,
(beats, bpms_and_durations, change_types),
);
let keys = parse_keys_in_row(row);
rows.push(HitObjectRow {
time: row_time,
beat: row_beat,
keys,
});
}
measure_beat_count += 4.0;
}
let flattened = HitObjectTimeline::flatten_rows(&rows, key_count);
HitObjects::new(flattened)
}
pub(crate) fn from_sm_generic(
raw_chart: &str,
) -> Result<GenericManiaChart, Box<dyn std::error::Error>> {
let uncommented_chart = remove_comments(raw_chart, "//");
if uncommented_chart.trim().is_empty() {
return Err(Box::new(errors::ParseError::<GameMode>::EmptyChartData));
}
let mut metadata = Metadata::empty();
let mut chartinfo = ChartInfo::empty();
chartinfo.bpm_affects_sv = false;
let mut bpms: (Vec<f32>, Vec<f32>) = (vec![0.0], vec![0.0]);
let mut raw_bpms = ChartDefaults::RAW_BPMS.to_string();
let mut stops = (vec![], vec![]);
let mut raw_stops = ChartDefaults::RAW_STOPS.to_string();
let mut raw_notes = ChartDefaults::RAW_NOTES.to_string();
process_sections(&uncommented_chart, |header, content| {
match header {
"#TITLE" => metadata.title = content.or_default_empty(ChartDefaults::TITLE),
"#ARTIST" => metadata.artist = content.or_default_empty(ChartDefaults::ARTIST),
"#SUBTITLE" => metadata.source = content.or_default_empty(ChartDefaults::SOURCE),
"#TITLETRANSLIT" => {
metadata.alt_title = content.or_default_empty(ChartDefaults::ALT_TITLE)
}
"#ARTISTTRANSLIT" => {
metadata.alt_artist = content.or_default_empty(ChartDefaults::ALT_ARTIST)
}
"#SUBTITLETRANSLIT" => {}
"#GENRE" => metadata.genre = content.or_default_empty(ChartDefaults::GENRE),
"#CREDIT" => metadata.creator = content.or_default_empty(ChartDefaults::CREATOR),
"#BACKGROUND" => chartinfo.bg_path = content.or_default_empty(ChartDefaults::BG_PATH),
"#MUSIC" => chartinfo.song_path = content.or_default_empty(ChartDefaults::SONG_PATH),
"#OFFSET" => {
chartinfo.audio_offset = -to_millis(
content.or_default_empty_as(*ChartDefaults::AUDIO_OFFSET as f32),
) as i32
}
"#SAMPLESTART" => {
chartinfo.preview_time = to_millis(
content.or_default_empty_as(*ChartDefaults::PREVIEW_TIME as f32),
) as i32
}
"#BPMS" => {
raw_bpms = content.or_default_empty(ChartDefaults::RAW_BPMS);
bpms = parse_bpms(&raw_bpms);
}
"#STOPS" => {
raw_stops = content.or_default_empty(ChartDefaults::RAW_STOPS);
stops = parse_stops(&raw_stops);
}
"#NOTES" => raw_notes = content.or_default_empty(ChartDefaults::RAW_NOTES),
_ => {}
}
});
let bpms_and_stops = merge_bpm_and_stops(bpms.0, bpms.1, stops.0, stops.1);
let timing_points = process_timing_points(&bpms_and_stops, chartinfo.audio_offset);
let hitobjects = process_notes(&raw_notes, &mut chartinfo, &bpms_and_stops);
Ok(GenericManiaChart::new(
metadata,
chartinfo,
timing_points,
hitobjects,
None,
))
}
#[allow(unused)]
pub(crate) fn from_sma(
raw_chart: &str,
) -> Result<GenericManiaChart, Box<dyn std::error::Error>> {
unimplemented!();
}
#[allow(unused)]
pub(crate) fn from_ssc(
raw_chart: &str,
) -> Result<GenericManiaChart, Box<dyn std::error::Error>> {
unimplemented!();
}