diff --git a/Cargo.lock b/Cargo.lock index 5e1a73f1..91c33439 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1865,6 +1865,7 @@ dependencies = [ name = "midi-file" version = "0.1.0" dependencies = [ + "midi-io", "midly", ] diff --git a/Cargo.toml b/Cargo.toml index 6c8117d9..c63cb03c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,4 +23,5 @@ wgpu-jumpstart = { path = "./wgpu-jumpstart" } neothesia = { path = "./neothesia", default-features = false } neothesia-core = { path = "./neothesia-core" } midi-file = { path = "./midi-file" } +midi-io = { path = "./midi-io" } piano-math = { path = "./piano-math" } diff --git a/midi-file/Cargo.toml b/midi-file/Cargo.toml index b6eaae0d..9f8f0a08 100644 --- a/midi-file/Cargo.toml +++ b/midi-file/Cargo.toml @@ -6,3 +6,6 @@ edition = "2021" [dependencies] midly = "0.5" + +[dev-dependencies] +midi-io.workspace = true diff --git a/midi-file/examples/play.rs b/midi-file/examples/play.rs new file mode 100644 index 00000000..3a801714 --- /dev/null +++ b/midi-file/examples/play.rs @@ -0,0 +1,92 @@ +use std::{collections::BTreeMap, io::Write, time::Duration}; + +use midi_io::{MidiOutputConnection, MidiOutputManager, MidiOutputPort}; +use midly::{MetaMessage, Smf, Timing, TrackEvent}; + +fn connect_midi_out() -> MidiOutputConnection { + let manager = MidiOutputManager::new().unwrap(); + + let mut out_ports = manager.outputs(); + let out_port: MidiOutputPort = match out_ports.len() { + 0 => panic!("MidiOut not found"), + 1 => { + println!("Choosing the only available output port: {}", &out_ports[0]); + out_ports.remove(0) + } + _ => { + println!("\nAvailable output ports:"); + for (i, p) in out_ports.iter().enumerate() { + println!("{}: {}", i, p); + } + print!("Please select output port: "); + std::io::stdout().flush().unwrap(); + let mut input = String::new(); + std::io::stdin().read_line(&mut input).unwrap(); + out_ports.remove(input.trim().parse::().unwrap()) + } + }; + + MidiOutputManager::connect_output(out_port).unwrap() +} + +fn build_schedule(smf: Smf) -> BTreeMap> { + let mut schedule: BTreeMap> = BTreeMap::new(); + for events in smf.tracks { + let mut pulses = 0; + for event in events { + pulses += event.delta.as_int() as u64; + schedule.entry(pulses).or_default().push(event); + } + } + schedule +} + +fn pulse_to_duration(pulses: u64, tempo: u32, pulses_per_quarter_note: u16) -> Duration { + let u_time = pulses as f64 / pulses_per_quarter_note as f64; + let time = (u_time * tempo as f64).floor() as u64; + Duration::from_micros(time) +} + +fn main() { + let mut out_port = connect_midi_out(); + + // let data = std::fs::read("/home/poly/Downloads/hopmon_package/Music01.mid").unwrap(); + // let data = std::fs::read("/home/poly/Downloads/mididownload.mid").unwrap(); + let data = std::fs::read("../test.mid").unwrap(); + let smf = Smf::parse(&data).unwrap(); + + let pulses_per_quarter_note = match smf.header.timing { + Timing::Metrical(t) => t.as_int(), + Timing::Timecode(_fps, _u) => { + panic!("Unsupported Timing::Timecode"); + } + }; + + let schedule = build_schedule(smf); + + let mut curr_tempo = 500_000; + let mut last_pulses = 0; + let mut buf = Vec::with_capacity(8); + + for (pulses, events) in schedule { + std::thread::sleep(pulse_to_duration( + pulses - last_pulses, + curr_tempo, + pulses_per_quarter_note, + )); + + last_pulses = pulses; + + for event in events { + if let midly::TrackEventKind::Meta(MetaMessage::Tempo(tempo)) = event.kind { + curr_tempo = tempo.as_int(); + } + + if let Some(event) = event.kind.as_live_event() { + buf.clear(); + event.write(&mut buf).unwrap(); + out_port.send(&buf).ok(); + } + } + } +}