use crate::models::common::*;
use crate::models::generic::GenericManiaChart;
use crate::models::timeline::HitObjectTimeline;
use crate::utils::math::approx_eq;
use crate::utils::rhythm::{MeasureRange, snap_to_nearest_note_type_normed};
use crate::utils::string::add_key_value_template;
use crate::utils::time::to_seconds;
#[allow(unused)]
use crate::errors;
#[inline]
fn sm_row_to_str(row: &[Key]) -> String {
let mut result = String::with_capacity(row.len());
for key in row {
result.push(match key.key_type {
KeyType::Empty => '0',
KeyType::Normal => '1',
KeyType::SliderStart => '2',
KeyType::SliderEnd => '3',
KeyType::Mine => 'M',
KeyType::Fake => 'F',
KeyType::Unknown => '0',
});
}
result
}
#[inline]
fn pad_measure(rows: &[HitObjectRow], range: &MeasureRange) -> Measure {
let key_count = rows.first().map_or(0, |row| row.keys.len());
if range.is_empty() {
return vec![HitObjectRow::empty(0, 0.0, key_count); 4];
}
let measure: Vec<&HitObjectRow> = rows
.iter()
.skip(range.0)
.take(range.1 - range.0)
.collect();
if measure.is_empty() {
return vec![HitObjectRow::empty(0, 0.0, key_count); 4];
}
let measure_start_beat = measure[0].beat;
let measure_start_time = measure[0].time;
let normalized_measure: Vec<_> = measure
.iter()
.map(|row| (row.time, row.beat - measure_start_beat, &row.keys))
.collect();
let mut minimal_beat_diff = 4.0;
for i in 1..normalized_measure.len() {
let diff = normalized_measure[i].1 - normalized_measure[i - 1].1;
if diff < minimal_beat_diff && diff > 1e-5 {
minimal_beat_diff = diff;
}
}
let snapped_beat_diff = snap_to_nearest_note_type_normed(minimal_beat_diff);
let row_count = (4.0 / snapped_beat_diff).round() as usize;
let expected_beats: Vec<f32> = (0..row_count)
.map(|k| k as f32 * snapped_beat_diff)
.collect();
let mut padded_measure = Vec::with_capacity(row_count);
let mut measure_iter = normalized_measure.iter().peekable();
for &expected_beat in &expected_beats {
if let Some(row) = measure_iter.peek() {
if approx_eq(row.1, expected_beat, 0.15) {
padded_measure.push(HitObjectRow {
time: row.0,
beat: measure_start_beat + expected_beat,
keys: row.2.clone(),
});
measure_iter.next();
continue;
}
}
let empty_time = measure_start_time + ((expected_beat / 4.0) * 1000.0) as i32;
padded_measure.push(HitObjectRow::empty(
empty_time,
measure_start_beat + expected_beat,
key_count,
));
}
padded_measure
}
pub(crate) fn to_sm_generic(chart: &GenericManiaChart) -> Result<String, Box<dyn std::error::Error>> {
let mut template = String::new();
let mut bpm_template = String::new();
let mut notes_template = String::new();
let mut prev_beat = 0.0;
let mut prev_measure = 0;
let mut prev_measure_beat = 0.0;
let mut current_measure_index = 0;
let scale_factor = 24.0;
let beats_per_measure = 4.0;
let beats_per_measure_scaled = scale_factor * beats_per_measure;
let key_count = chart
.hitobjects
.iter()
.map(|obj| obj.lane as usize + 1)
.max()
.unwrap_or(4);
let rows = HitObjectTimeline::to_rows(&chart.hitobjects.objects, key_count);
let mut measure_indices: Vec<MeasureRange> = Vec::with_capacity(85);
let mut padded_measures: Vec<Measure> = Vec::with_capacity(rows.len() * 2);
let bpms: Vec<_> = chart.timing_points.bpm_changes().collect();
for (row_index, row) in rows.iter().enumerate() {
let beat = row.beat;
let beat_scaled = (beat * scale_factor).round();
let measure = (beat_scaled / beats_per_measure_scaled) as u32;
if measure != prev_measure {
measure_indices.push(MeasureRange(current_measure_index, row_index, false));
if beat - prev_measure_beat > 5.0 {
let empty_measure_count = ((beat - prev_beat) / 4.0).floor() as usize;
for _ in 0..empty_measure_count {
measure_indices.push(MeasureRange(current_measure_index, row_index, true));
current_measure_index = row_index;
}
}
current_measure_index = row_index;
prev_measure = measure;
prev_measure_beat = beat;
}
prev_beat = beat;
}
measure_indices.push(MeasureRange(current_measure_index, rows.len(), false));
for measure_range in measure_indices {
padded_measures.push(pad_measure(&rows, &measure_range));
}
let last_bpm_beat = bpms.last().unwrap().beat;
for bpm in bpms {
if bpm.beat < last_bpm_beat {
add_key_value_template(
&mut bpm_template,
&bpm.beat.to_string(),
"=",
&bpm.change.value.to_string(),
",\n",
);
} else {
add_key_value_template(
&mut bpm_template,
&bpm.beat.to_string(),
"=",
&bpm.change.value.to_string(),
"\n",
);
}
}
let last_measure_index = padded_measures.len() - 1;
notes_template.push('\n');
notes_template.push_str(" ");
notes_template.push_str("dance-single:\n"); notes_template.push_str(" ");
notes_template.push_str(&chart.metadata.creator);
notes_template.push_str(":\n");
notes_template.push_str(" ");
notes_template.push_str("Edit"); notes_template.push_str(":\n");
notes_template.push_str(" ");
notes_template.push_str("1:\n");
notes_template.push_str(" ");
notes_template.push_str("0.000,0.000,0.000,0.000,0.000:\n");
for (measure_index, measure) in padded_measures.iter().enumerate() {
notes_template.push_str("// Measure ");
notes_template.push_str(&(measure_index + 1).to_string());
notes_template.push('\n');
for row in measure {
notes_template.push_str(&sm_row_to_str(&row.keys));
notes_template.push('\n');
}
if measure_index != last_measure_index {
notes_template.push_str(", ");
}
}
add_key_value_template(&mut template, "#TITLE", ":", &chart.metadata.title, ";\n");
let subtitle = if chart.metadata.source == ChartDefaults::SOURCE {
""
} else {
&chart.metadata.source
};
add_key_value_template(&mut template, "#SUBTITLE", ":", subtitle, ";\n");
add_key_value_template(&mut template, "#ARTIST", ":", &chart.metadata.artist, ";\n");
add_key_value_template(
&mut template,
"#TITLETRANSLIT",
":",
&chart.metadata.alt_title,
";\n",
);
add_key_value_template(&mut template, "#SUBTITLETRANSLIT", ":", "", ";\n");
add_key_value_template(
&mut template,
"#ARTISTTRANSLIT",
":",
&chart.metadata.alt_artist,
";\n",
);
add_key_value_template(&mut template, "#GENRE", ":", &chart.metadata.genre, ";\n");
add_key_value_template(&mut template, "#CREDIT", ":", &chart.metadata.creator, ";\n");
add_key_value_template(&mut template, "#BANNER", ":", &chart.chartinfo.bg_path, ";\n");
add_key_value_template(
&mut template,
"#BACKGROUND",
":",
&chart.chartinfo.bg_path,
";\n",
);
add_key_value_template(&mut template, "#LYRICSPATH", ":", "", ";\n");
add_key_value_template(&mut template, "#CDTITLE", ":", "", ";\n");
add_key_value_template(&mut template, "#MUSIC", ":", &chart.chartinfo.song_path, ";\n");
add_key_value_template(
&mut template,
"#OFFSET",
":",
&to_seconds(-chart.chartinfo.audio_offset as f32).to_string(),
";\n",
);
add_key_value_template(
&mut template,
"#SAMPLESTART",
":",
&to_seconds(chart.chartinfo.preview_time as f32).to_string(),
";\n",
);
add_key_value_template(&mut template, "#SAMPLELENGTH", ":", "12.000", ";\n"); add_key_value_template(&mut template, "#SELECTABLE", ":", "YES", ";\n");
add_key_value_template(&mut template, "#BPMS", ":", &bpm_template, ";\n");
add_key_value_template(&mut template, "#STOPS", ":", "", ";\n");
add_key_value_template(&mut template, "#BGCHANGES", ":", "", ";\n");
add_key_value_template(&mut template, "#KEYSOUNDS", ":", "", ";\n");
add_key_value_template(&mut template, "#NOTES", ":", ¬es_template, ";\n");
Ok(template)
}
#[allow(unused)]
pub(crate) fn to_sma(chart: &GenericManiaChart) -> Result<String, Box<dyn std::error::Error>> {
unimplemented!();
}
#[allow(unused)]
pub(crate) fn to_ssc(chart: &GenericManiaChart) -> Result<String, Box<dyn std::error::Error>> {
unimplemented!();
}