use alloc::vec::Vec;
use crate::{SmfEvent, SmfFile, timing::TempoMap};
pub struct SmfPlayer {
events: Vec<(f64, SmfEvent)>,
}
impl SmfPlayer {
pub fn new(file: SmfFile) -> Self {
let map = TempoMap::from_file(&file);
let mut events: Vec<(f64, SmfEvent)> = Vec::new();
for track in &file.tracks {
let mut abs_tick: u64 = 0;
for ev in &track.events {
abs_tick += ev.delta_ticks as u64;
let t = map.tick_to_secs(abs_tick);
events.push((t, ev.event.clone()));
}
}
events.sort_by(|(a, _), (b, _)| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal));
Self { events }
}
pub fn events(&self) -> impl Iterator<Item = &(f64, SmfEvent)> {
self.events.iter()
}
pub fn midi_events(&self) -> impl Iterator<Item = (f64, &oxisound_core::MidiMessage)> {
self.events.iter().filter_map(|(t, ev)| {
if let SmfEvent::Midi(msg) = ev {
Some((*t, msg))
} else {
None
}
})
}
#[cfg(feature = "std")]
pub fn play(
&self,
output: &mut dyn oxisound_core::MidiOutput,
) -> Result<(), oxisound_core::OxiSoundError> {
let mut last_time = 0.0f64;
for (t, ev) in &self.events {
let delay = t - last_time;
if delay > 0.001 {
std::thread::sleep(std::time::Duration::from_secs_f64(delay));
}
if let SmfEvent::Midi(msg) = ev {
output.send(msg)?;
}
last_time = *t;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Division, SmfEvent, SmfFile, SmfFormat, SmfTrack, TrackEvent};
use oxisound_core::MidiMessage;
fn note_on(delta: u32, note: u8, vel: u8) -> TrackEvent {
TrackEvent {
delta_ticks: delta,
event: SmfEvent::Midi(MidiMessage {
status: 0x90,
data: alloc::vec![note, vel],
timestamp_micros: 0,
}),
}
}
fn eot() -> TrackEvent {
TrackEvent {
delta_ticks: 0,
event: SmfEvent::EndOfTrack,
}
}
fn simple_file(ticks_per_beat: u16, track_events: Vec<TrackEvent>) -> SmfFile {
SmfFile {
format: SmfFormat::SingleTrack,
division: Division::TicksPerBeat(ticks_per_beat),
tracks: vec![SmfTrack {
name: None,
events: track_events,
}],
}
}
#[test]
fn test_midi_events_filter() {
let file = simple_file(
480,
vec![
note_on(0, 60, 64),
TrackEvent {
delta_ticks: 480,
event: SmfEvent::Tempo(500_000),
},
note_on(0, 62, 80),
eot(),
],
);
let player = SmfPlayer::new(file);
let midi: Vec<_> = player.midi_events().collect();
assert_eq!(midi.len(), 2);
assert_eq!(midi[0].1.status, 0x90);
assert_eq!(midi[0].1.data[0], 60);
assert_eq!(midi[1].1.data[0], 62);
}
#[test]
fn test_events_sorted_by_time() {
let track_a = SmfTrack {
name: None,
events: vec![note_on(0, 60, 64), note_on(960, 62, 64), eot()],
};
let track_b = SmfTrack {
name: None,
events: vec![note_on(480, 64, 64), note_on(960, 65, 64), eot()],
};
let file = SmfFile {
format: SmfFormat::MultiTrack,
division: Division::TicksPerBeat(480),
tracks: vec![track_a, track_b],
};
let player = SmfPlayer::new(file);
let times: Vec<f64> = player.midi_events().map(|(t, _)| t).collect();
for pair in times.windows(2) {
assert!(pair[0] <= pair[1], "events out of order: {:?}", times);
}
assert_eq!(times.len(), 4);
}
#[test]
fn test_events_timing_values() {
let file = simple_file(480, vec![note_on(0, 60, 64), note_on(480, 62, 64), eot()]);
let player = SmfPlayer::new(file);
let midi: Vec<_> = player.midi_events().collect();
assert_eq!(midi.len(), 2);
let diff0 = midi[0].0.abs();
assert!(
diff0 < 1e-9,
"first event should be at t=0, got {}",
midi[0].0
);
let diff1 = (midi[1].0 - 0.5).abs();
assert!(
diff1 < 1e-9,
"second event should be at t=0.5, got {}",
midi[1].0
);
}
}