#![forbid(unsafe_code)]
use std::{
cmp::Ordering,
num::{NonZeroU8, NonZeroU16},
};
use sim_lib_midi_core::{
ChannelMessage, MetaEvent, MidiError, MidiEvent, MidiPayload, MidiTempoMap, TickTime,
TrackedMidiEvent, synthetic_origin,
};
use crate::SmfError;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum SmfFormat {
SingleTrack,
Simultaneous,
Independent,
}
impl SmfFormat {
pub const fn time_semantics(self) -> SmfTimeSemantics {
match self {
Self::SingleTrack | Self::Simultaneous => SmfTimeSemantics::SharedTimeline,
Self::Independent => SmfTimeSemantics::IndependentPatterns,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum SmfTimeSemantics {
SharedTimeline,
IndependentPatterns,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum SmpteRate {
Fps24,
Fps25,
Fps29Drop,
Fps30,
}
impl SmpteRate {
pub(crate) const fn from_header_byte(value: u8) -> Option<Self> {
match value as i8 {
-24 => Some(Self::Fps24),
-25 => Some(Self::Fps25),
-29 => Some(Self::Fps29Drop),
-30 => Some(Self::Fps30),
_ => None,
}
}
pub(crate) const fn header_byte(self) -> u8 {
match self {
Self::Fps24 => (-24_i8) as u8,
Self::Fps25 => (-25_i8) as u8,
Self::Fps29Drop => (-29_i8) as u8,
Self::Fps30 => (-30_i8) as u8,
}
}
pub const fn frames_per_second_ratio(self) -> (u32, u32) {
match self {
Self::Fps24 => (24, 1),
Self::Fps25 => (25, 1),
Self::Fps29Drop => (30_000, 1_001),
Self::Fps30 => (30, 1),
}
}
const fn nominal_frames_per_second(self) -> u32 {
match self {
Self::Fps24 => 24,
Self::Fps25 => 25,
Self::Fps29Drop | Self::Fps30 => 30,
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum SmfDivision {
Metrical {
ticks_per_quarter: NonZeroU16,
},
Smpte {
frames_per_second: SmpteRate,
ticks_per_frame: NonZeroU8,
},
}
impl SmfDivision {
pub const fn metrical(ticks_per_quarter: u16) -> Option<Self> {
if ticks_per_quarter >= 0x8000 {
return None;
}
match NonZeroU16::new(ticks_per_quarter) {
Some(ticks_per_quarter) => Some(Self::Metrical { ticks_per_quarter }),
None => None,
}
}
pub const fn smpte(frames_per_second: SmpteRate, ticks_per_frame: u8) -> Option<Self> {
match NonZeroU8::new(ticks_per_frame) {
Some(ticks_per_frame) => Some(Self::Smpte {
frames_per_second,
ticks_per_frame,
}),
None => None,
}
}
pub const fn ticks_per_quarter(self) -> Option<NonZeroU16> {
match self {
Self::Metrical { ticks_per_quarter } => Some(ticks_per_quarter),
Self::Smpte { .. } => None,
}
}
pub const fn ticks_per_second_ratio(self) -> Option<(u32, u32)> {
match self {
Self::Metrical { .. } => None,
Self::Smpte {
frames_per_second,
ticks_per_frame,
} => {
let (numerator, denominator) = frames_per_second.frames_per_second_ratio();
Some((numerator * ticks_per_frame.get() as u32, denominator))
}
}
}
pub const fn event_time_base(self) -> u32 {
match self {
Self::Metrical { ticks_per_quarter } => ticks_per_quarter.get() as u32,
Self::Smpte {
frames_per_second,
ticks_per_frame,
} => frames_per_second.nominal_frames_per_second() * ticks_per_frame.get() as u32,
}
}
pub(crate) const fn header_word(self) -> u16 {
match self {
Self::Metrical { ticks_per_quarter } => ticks_per_quarter.get(),
Self::Smpte {
frames_per_second,
ticks_per_frame,
} => u16::from_be_bytes([frames_per_second.header_byte(), ticks_per_frame.get()]),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SmfTrack {
pub events: Vec<MidiEvent>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SmfFile {
pub format: SmfFormat,
pub division: SmfDivision,
pub tracks: Vec<SmfTrack>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SmfTempoMaps {
Shared(MidiTempoMap),
Independent(Vec<MidiTempoMap>),
}
#[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 const fn ticks_per_quarter(&self) -> Option<u32> {
match self.division.ticks_per_quarter() {
Some(value) => Some(value.get() as u32),
None => None,
}
}
pub fn tempo_maps(&self) -> Result<SmfTempoMaps, MidiError> {
let tpq = self
.ticks_per_quarter()
.ok_or(MidiError::MetricalTempoRequired)?;
if self.format == SmfFormat::Independent {
let maps = self
.tracks
.iter()
.map(|track| MidiTempoMap::from_ordered_events(tpq, &track.events))
.collect::<Result<Vec<_>, _>>()?;
return Ok(SmfTempoMaps::Independent(maps));
}
let mut events = self
.tracks
.iter()
.enumerate()
.flat_map(|(track, contents)| {
contents
.events
.iter()
.enumerate()
.map(move |(index, event)| (track, index, event))
})
.collect::<Vec<_>>();
events.sort_by(|left, right| {
compare_event_order(left.2, left.0, right.2, right.0).then_with(|| left.1.cmp(&right.1))
});
Ok(SmfTempoMaps::Shared(MidiTempoMap::from_ordered_events(
tpq,
events.into_iter().map(|(_, _, event)| event),
)?))
}
pub fn canonicalize(&mut self) {
for track in &mut self.tracks {
canonicalize_track(track, self.division.event_time_base());
}
}
pub fn merge_cursor(&self) -> Result<SmfMergeCursor<'_>, SmfError> {
if self.format.time_semantics() == SmfTimeSemantics::IndependentPatterns {
return Err(SmfError::IndependentPatternsCannotMerge);
}
Ok(SmfMergeCursor {
file: self,
next_index: vec![0; self.tracks.len()],
})
}
pub fn merged_events(&self) -> Result<Vec<TrackedMidiEvent>, SmfError> {
let mut merged = Vec::new();
for event in self.merge_cursor()? {
merged.push(event);
}
Ok(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 Some(event) = track.events.get(self.next_index[track_idx]) else {
continue;
};
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,
}
}