use crate::models;
use crate::models::common::{
ChartDefaults,
KeyType,
Measure,
};
use crate::utils::math::approx_eq;
use crate::utils::string::add_key_value_template;
use crate::utils::time::to_seconds;
use crate::utils::rhythm::{
MeasureRange,
snap_to_nearest_note_type_normed,
};
#[allow(unused)]
use crate::errors;
#[inline]
fn sm_row_to_str(row: &[KeyType]) -> String {
let mut result = String::with_capacity(row.len());
for note in row {
result.push(match note {
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: &models::hitobjects::HitObjects, range: &MeasureRange) -> Measure {
let key_count = rows.iter_zipped().next().map_or(0, |row| row.3.len());
if range.is_empty() {
return vec![vec![KeyType::Empty; key_count]; 4];
}
let measure: Vec<_> = rows.iter_zipped()
.skip(range.0)
.take(range.1 - range.0)
.collect();
if measure.is_empty() {
return vec![vec![KeyType::Empty; key_count]; 4];
}
let measure_start_beat = measure[0].1;
let normalized_measure: Vec<_> = measure.iter()
.map(|row| (row.0, row.1 - measure_start_beat, row.2, row.3))
.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(row.3.clone());
measure_iter.next();
continue;
}
}
padded_measure.push(vec![KeyType::Empty; key_count]);
}
padded_measure
}
pub(crate) fn to_sm(chart: &models::chart::Chart) -> 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 mut measure_indices: Vec<MeasureRange> = Vec::with_capacity(85);
let mut padded_measures: Vec<Measure> = Vec::with_capacity(chart.hitobjects.times.len() * 2);
let bpms: Vec<_> = chart.timing_points.bpm_changes_views().collect();
for (row_index, beat) in chart.hitobjects.beats.iter().enumerate() {
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, chart.hitobjects.rows.len(), false));
for measure_range in measure_indices {
padded_measures.push(pad_measure(&chart.hitobjects, &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.value.to_string(), ",\n");
} else {
add_key_value_template(&mut bpm_template, &bpm.beat.to_string(), "=", &bpm.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));
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).to_string(), ";\n");
add_key_value_template(&mut template,
"#SAMPLESTART",":", &to_seconds(chart.chartinfo.preview_time).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: &models::chart::Chart) -> Result<String, Box<dyn std::error::Error>> {
unimplemented!();
}
#[allow(unused)]
pub(crate) fn to_ssc(chart: &models::chart::Chart) -> Result<String, Box<dyn std::error::Error>> {
unimplemented!();
}