use std::fs::File;
use std::path::Path;
use e_midi_shared::embed_musicxml;
use e_midi_shared::embed_midi;
fn main() {
println!("cargo:rerun-if-changed=midi/");
let midi_songs = embed_midi::extract_midi_songs(Path::new("midi"));
let xml_songs = embed_musicxml::extract_musicxml_songs(Path::new("midi"));
let mut song_data_entries = Vec::new();
let mut song_info_entries = Vec::new();
let mut song_idx = 0;
let out_dir = std::env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("embedded_midi.rs");
let mut out = File::create(&dest_path).unwrap();
writeln!(out, "use e_midi_shared::types::SongData;\n").unwrap();
for song in &midi_songs {
let mut non_empty_track_count = 0;
for track_notes in song.track_notes.iter() {
if !track_notes.is_empty() {
writeln!(out, "static SONG_{}_TRACK_{}_NOTES: &[crate::Note] = &[", song_idx, non_empty_track_count).unwrap();
for (start_ticks, dur_ticks, chan, pitch, vel, track_idx) in track_notes {
writeln!(out, " crate::Note {{ start_ms: {}, dur_ms: {}, chan: {}, pitch: {}, vel: {}, track: {} }},", start_ticks, dur_ticks, chan, pitch, vel, track_idx).unwrap();
}
writeln!(out, "];").unwrap();
non_empty_track_count += 1;
}
}
writeln!(out, "static SONG_{}_TRACK_NOTES: &[&[crate::Note]] = &[", song_idx).unwrap();
for track_idx in 0..non_empty_track_count {
writeln!(out, " SONG_{}_TRACK_{}_NOTES,", song_idx, track_idx).unwrap();
}
writeln!(out, "]\n").unwrap();
writeln!(out, ";").unwrap();
song_data_entries.push(format!(
"SongData {{ track_notes: SONG_{}_TRACK_NOTES, ticks_per_q: {}, default_tempo: {}, filename: \"{}\", name: \"{}\" }}",
song_idx, song.ticks_per_q, song.default_tempo, song.filename, song.name
));
let tracks = song.tracks.iter().map(|t| {
let guess_str = match &t.guess {
Some(s) if !s.trim().is_empty() && s.trim() != "-" => format!("Some({:?}.to_string())", s),
None => {
if let Some(ch) = t.channels.first() {
let guess = match ch {
0 => "Piano",
1 => "Strings",
2 => "Guitar",
9 => "Percussion",
_ => "Unknown",
};
if guess == "Unknown" {
"None".to_string()
} else {
format!("Some(\"{}\".to_string())", guess)
}
} else {
"None".to_string()
}
}
_ => "None".to_string(),
};
let channels_vec = format!("vec!{:?}", t.channels);
format!(
"TrackInfo {{ index: {}, program: {:?}, guess: {}, channels: {}, note_count: {}, pitch_range: ({}, {}), sample_notes: vec!{:?} }}",
t.index, t.program, guess_str, channels_vec, t.note_count, t.pitch_range.0, t.pitch_range.1, t.sample_notes)
}).collect::<Vec<_>>().join(",");
let mut track_index_map = String::from("{ let mut m = std::collections::HashMap::new(); ");
for (dense_idx, t) in song.tracks.iter().enumerate() {
track_index_map.push_str(&format!("m.insert({}, {}); ", t.index, dense_idx));
}
track_index_map.push_str("m }");
song_info_entries.push(format!(
"SongInfo {{ filename: \"{}\".to_string(), name: \"{}\".to_string(), tracks: vec![{}], default_tempo: {}, ticks_per_q: Some({}), song_type: SongType::Midi, track_index_map: {} }}",
song.filename, song.name, tracks, song.default_tempo, song.ticks_per_q, track_index_map));
song_idx += 1;
}
for song in &xml_songs {
eprintln!("[BUILD DEBUG] MusicXML file: {}", song.filename);
for track in &song.tracks {
eprintln!(" part_idx: {} name: '{}' midi_program: {} note_count: {}", track.index, track.name, track.program, track.note_count);
}
let mut non_empty_track_count = 0;
for track_notes in song.track_notes.iter() {
if !track_notes.is_empty() {
writeln!(out, "static SONG_{}_TRACK_{}_NOTES: &[crate::Note] = &[", song_idx, non_empty_track_count).unwrap();
for (start_ticks, dur_ticks, voice, pitch, vel) in track_notes {
writeln!(out, " crate::Note {{ start_ms: {}, dur_ms: {}, chan: {}, pitch: {}, vel: {}, track: {} }},", start_ticks, dur_ticks, voice, pitch, vel, non_empty_track_count).unwrap();
}
writeln!(out, "];").unwrap();
non_empty_track_count += 1;
}
}
writeln!(out, "static SONG_{}_TRACK_NOTES: &[&[crate::Note]] = &[", song_idx).unwrap();
for track_idx in 0..non_empty_track_count {
writeln!(out, " SONG_{}_TRACK_{}_NOTES,", song_idx, track_idx).unwrap();
}
writeln!(out, "]\n").unwrap();
writeln!(out, ";").unwrap();
song_data_entries.push(format!(
"SongData {{ track_notes: SONG_{}_TRACK_NOTES, ticks_per_q: {}, default_tempo: {}, filename: \"{}\", name: \"{}\" }}",
song_idx, song.ticks_per_q, song.default_tempo, song.filename, song.name
));
let tracks = song.tracks.iter().map(|t| {
let guess_str = format!("Some({:?}.to_string())", t.name);
let program_val = format!("Some({})", t.program);
format!(
"TrackInfo {{ index: {}, program: {}, guess: {}, channels: vec![], note_count: {}, pitch_range: ({}, {}), sample_notes: vec!{:?} }}",
t.index, program_val, guess_str, t.note_count, t.pitch_range.0, t.pitch_range.1, t.sample_notes)
}).collect::<Vec<_>>().join(",");
let mut track_index_map = String::from("{");
for (dense_idx, t) in song.tracks.iter().enumerate() {
track_index_map.push_str(&format!("let mut m = std::collections::HashMap::new(); m.insert({}, {});", t.index, dense_idx));
}
track_index_map.push_str("m}");
song_info_entries.push(format!(
"SongInfo {{ filename: \"{}\".to_string(), name: \"{}\".to_string(), tracks: vec![{}], default_tempo: {}, ticks_per_q: Some({}), song_type: SongType::MusicXml, track_index_map: {} }}",
song.filename, song.name, tracks, song.default_tempo, song.ticks_per_q, track_index_map));
song_idx += 1;
}
writeln!(out, "static SONG_DATA: &[SongData] = &[" ).unwrap();
for entry in &song_data_entries {
writeln!(out, " {},", entry).unwrap();
}
writeln!(out, "];").unwrap();
writeln!(out, "\npub fn get_songs() -> Vec<SongInfo> {{").unwrap();
writeln!(out, " vec![").unwrap();
for entry in &song_info_entries {
writeln!(out, " {},", entry).unwrap();
}
writeln!(out, " ]").unwrap();
writeln!(out, "}}\n").unwrap();
writeln!(out, "pub fn get_events_for_song_tracks(song_index: usize, track_indices: &[usize], tempo_bpm: u32) -> Vec<crate::Note> {{").unwrap();
writeln!(out, " let song_data = &SONG_DATA[song_index];").unwrap();
writeln!(out, " let ticks_per_q = song_data.ticks_per_q;").unwrap();
writeln!(out, " let mut events = Vec::new();").unwrap();
writeln!(out, " let track_notes = song_data.track_notes;").unwrap();
writeln!(out, " let mut debug_count = 0;").unwrap();
writeln!(out, " let tempo_usec_per_q = 60_000_000 / tempo_bpm;").unwrap();
writeln!(out, " for &track_idx in track_indices {{").unwrap();
writeln!(out, " if let Some(track) = track_notes.get(track_idx) {{").unwrap();
writeln!(out, " for note in (*track).iter() {{").unwrap();
writeln!(out, " let start_ms = ((note.start_ms as f64) * 60_000.0 / (tempo_bpm as f64 * ticks_per_q as f64)) as u32;").unwrap();
writeln!(out, " let mut dur_ms = ((note.dur_ms as f64) * 60_000.0 / (tempo_bpm as f64 * ticks_per_q as f64)) as u32;").unwrap();
writeln!(out, " dur_ms = dur_ms.max(50);").unwrap();
writeln!(out, " events.push(crate::Note {{ start_ms, dur_ms, chan: note.chan, pitch: note.pitch, vel: note.vel, track: note.track }});").unwrap();
writeln!(out, " }}").unwrap();
writeln!(out, " }}").unwrap();
writeln!(out, " }}").unwrap();
writeln!(out, " events.sort_by_key(|n| n.start_ms);").unwrap();
writeln!(out, " events").unwrap();
writeln!(out, "}}\n").unwrap();
use std::fs::OpenOptions;
use std::io::Write;
let debug_path = std::env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| "target".to_string());
let debug_file_path = Path::new(&debug_path).join("musicxml_part_debug.txt");
if let Some(parent_dir) = debug_file_path.parent() {
std::fs::create_dir_all(parent_dir).unwrap();
}
let mut debug_file = OpenOptions::new()
.create(true)
.append(true)
.open(&debug_file_path)
.unwrap();
for song in &xml_songs {
writeln!(debug_file, "[BUILD DEBUG] MusicXML file: {}", song.filename).unwrap();
for track in &song.tracks {
writeln!(debug_file, " part_idx: {} name: '{}' midi_program: {} note_count: {}", track.index, track.name, track.program, track.note_count).unwrap();
}
debug_file.flush().unwrap();
}
}