#![forbid(unsafe_code)]
use std::cmp::Ordering;
use sim_lib_midi_core::{
ChannelMessage, MetaEvent, MidiEvent, MidiPayload, TickTime, TrackedMidiEvent, synthetic_origin,
};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum SmfFormat {
SingleTrack,
Simultaneous,
Independent,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SmfTrack {
pub events: Vec<MidiEvent>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SmfFile {
pub format: SmfFormat,
pub tpq: u32,
pub tracks: Vec<SmfTrack>,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct SmfWriteOptions {
pub running_status: bool,
}
pub struct SmfMergeCursor<'a> {
file: &'a SmfFile,
next_index: Vec<usize>,
}
impl SmfFile {
pub fn canonicalize(&mut self) {
for track in &mut self.tracks {
canonicalize_track(track, self.tpq);
}
}
pub fn merge_cursor(&self) -> SmfMergeCursor<'_> {
SmfMergeCursor {
file: self,
next_index: vec![0; self.tracks.len()],
}
}
pub fn merged_events(&self) -> Vec<TrackedMidiEvent> {
let mut merged = Vec::new();
for event in self.merge_cursor() {
merged.push(event);
}
merged
}
}
impl<'a> Iterator for SmfMergeCursor<'a> {
type Item = TrackedMidiEvent;
fn next(&mut self) -> Option<Self::Item> {
let mut best: Option<(usize, &MidiEvent)> = None;
for (track_idx, track) in self.file.tracks.iter().enumerate() {
let event = track.events.get(self.next_index[track_idx])?;
match best {
None => best = Some((track_idx, event)),
Some((best_track, best_event)) => {
if compare_event_order(event, track_idx, best_event, best_track)
== Ordering::Less
{
best = Some((track_idx, event));
}
}
}
}
let (track_idx, event) = best?;
self.next_index[track_idx] += 1;
Some(TrackedMidiEvent {
last_track: track_idx,
event: event.clone(),
})
}
}
pub(crate) fn canonicalize_track(track: &mut SmfTrack, tpq: u32) {
track.events.sort_by(compare_events_same_track);
if !track
.events
.iter()
.any(|event| matches!(event.payload, MidiPayload::Meta(MetaEvent::EndOfTrack)))
{
let last_ticks = track
.events
.last()
.map(|event| event.time.ticks)
.unwrap_or(0);
track.events.push(MidiEvent {
time: TickTime::new(last_ticks, tpq).unwrap_or(TickTime::ZERO),
origin: synthetic_origin(),
payload: MidiPayload::Meta(MetaEvent::EndOfTrack),
});
}
track.events.sort_by(compare_events_same_track);
}
pub(crate) fn compare_event_order(
left: &MidiEvent,
left_track: usize,
right: &MidiEvent,
right_track: usize,
) -> Ordering {
compare_time(left.time, right.time)
.then_with(|| event_priority(left).cmp(&event_priority(right)))
.then_with(|| left_track.cmp(&right_track))
}
fn compare_events_same_track(left: &MidiEvent, right: &MidiEvent) -> Ordering {
compare_time(left.time, right.time)
.then_with(|| event_priority(left).cmp(&event_priority(right)))
}
fn compare_time(left: TickTime, right: TickTime) -> Ordering {
let left_scaled = i128::from(left.ticks) * i128::from(right.tpq);
let right_scaled = i128::from(right.ticks) * i128::from(left.tpq);
left_scaled.cmp(&right_scaled)
}
fn event_priority(event: &MidiEvent) -> u8 {
match event.payload {
MidiPayload::Meta(MetaEvent::EndOfTrack) => 4,
MidiPayload::Meta(_) => 0,
MidiPayload::Channel(ChannelMessage::NoteOff { .. }) => 1,
MidiPayload::Channel(ChannelMessage::NoteOn { .. }) => 2,
_ => 3,
}
}