use crate::structs::{SmFile, OsuSettings, Chart};
const ROWS_PER_BEAT: f64 = 48.0; pub fn create_basic_osu(sm_file: &SmFile, chart: &Chart, settings: &OsuSettings) -> Result<String, String> {
let mut osu = String::new();
osu.push_str("osu file format v14\n");
osu.push_str("\n");
osu.push_str("[General]\n");
osu.push_str("AudioFilename: ");
osu.push_str(&sm_file.metadata.music);
osu.push_str("\n");
osu.push_str("AudioLeadIn: 0\n");
osu.push_str("PreviewTime: -1\n");
osu.push_str("Countdown: 0\n");
osu.push_str("SampleSet: Normal\n");
osu.push_str("StackLeniency: 0.7\n");
osu.push_str("Mode: 3\n"); osu.push_str("LetterboxInBreaks: 0\n");
osu.push_str("WidescreenStoryboard: 0\n");
osu.push_str("\n");
osu.push_str("[Editor]\n");
osu.push_str("\n");
osu.push_str("[Metadata]\n");
osu.push_str("Title:");
osu.push_str(&sm_file.metadata.title);
osu.push_str("\n");
osu.push_str("TitleUnicode:");
osu.push_str(&sm_file.metadata.title);
osu.push_str("\n");
osu.push_str("Artist:");
osu.push_str(&sm_file.metadata.artist);
osu.push_str("\n");
osu.push_str("ArtistUnicode:");
osu.push_str(&sm_file.metadata.artist);
osu.push_str("\n");
osu.push_str("Creator:");
osu.push_str(&sm_file.metadata.credit);
osu.push_str("\n");
osu.push_str("Version:");
osu.push_str(&chart.difficulty);
osu.push_str("\n");
osu.push_str("Source:\n");
osu.push_str("Tags: rOtterna\n");
osu.push_str("BeatmapID: 0\n");
osu.push_str("BeatmapSetID: -1\n");
osu.push_str("\n");
osu.push_str("[Difficulty]\n");
osu.push_str(&format!("HPDrainRate: {}\n", settings.hp));
osu.push_str("CircleSize: 4\n");
osu.push_str(&format!("OverallDifficulty: {}\n", settings.od));
osu.push_str("ApproachRate: 5\n");
osu.push_str("SliderMultiplier: 1.4\n");
osu.push_str("SliderTickRate: 1\n");
osu.push_str("\n");
osu.push_str("[Events]\n");
osu.push_str("//Background and Video events\n");
if !sm_file.metadata.background.is_empty() {
osu.push_str("0,0,\"");
osu.push_str(&sm_file.metadata.background);
osu.push_str("\",0,0\n");
}
osu.push_str("//Break Periods\n");
osu.push_str("//Storyboard Layer 0 (Background)\n");
osu.push_str("//Storyboard Layer 1 (Fail)\n");
osu.push_str("//Storyboard Layer 2 (Pass)\n");
osu.push_str("//Storyboard Layer 3 (Foreground)\n");
osu.push_str("//Storyboard Sound Samples\n");
osu.push_str("\n");
osu.push_str("[TimingPoints]\n");
println!("[create_basic_osu] Found {} BPM change(s)", sm_file.bpms.len());
let offset_ms = sm_file.offset as i32;
if sm_file.bpms.is_empty() {
println!("[create_basic_osu] No BPMs found, using default 120 BPM");
osu.push_str(&format!("{},{},4,2,0,100,1,0\n", offset_ms, 500.0)); } else {
let mut current_time_ms = 0.0;
let mut current_row = 0.0;
let mut current_bpm = sm_file.bpms[0].1;
for (idx, (bpm_row, bpm)) in sm_file.bpms.iter().enumerate() {
if idx > 0 {
let rows_elapsed = bpm_row - current_row;
let beats_elapsed = rows_elapsed / ROWS_PER_BEAT;
let time_elapsed_ms = (beats_elapsed / current_bpm) * 60000.0;
current_time_ms += time_elapsed_ms;
}
let time_ms = (current_time_ms + sm_file.offset) as i32;
let beat_duration_ms = 60000.0 / bpm;
let bpm_beat = bpm_row / ROWS_PER_BEAT;
println!("[create_basic_osu] BPM change at row {} (beat {}) ({}ms): {} BPM ({}ms per beat)",
bpm_row, bpm_beat, time_ms, bpm, beat_duration_ms);
osu.push_str(&format!("{},{},4,2,0,100,1,0\n", time_ms, beat_duration_ms));
current_row = *bpm_row;
current_bpm = *bpm;
}
}
osu.push_str("\n");
osu.push_str("[HitObjects]\n");
let column_count = if chart.column_count > 0 { chart.column_count } else { 4 };
for measure in chart.measures.iter() {
for beat in measure.beats.iter() {
let time_ms = beat.time;
for (note_idx, note) in beat.notes.iter().enumerate() {
if *note {
let column = (note_idx as f64 + 0.5) * 512.0 / column_count as f64;
let note_time_ms = (time_ms + sm_file.offset) as i32;
osu.push_str(&format!("{},{},{},1,0,0:0:0:0:\n", column as i32, 192, note_time_ms));
}
}
}
}
Ok(osu)
}