Skip to main content

sim_lib_midi_smf/
model.rs

1#![forbid(unsafe_code)]
2
3use std::cmp::Ordering;
4
5use sim_lib_midi_core::{
6    ChannelMessage, MetaEvent, MidiEvent, MidiPayload, TickTime, TrackedMidiEvent, synthetic_origin,
7};
8
9/// The SMF header format field: how the file's tracks relate.
10#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
11pub enum SmfFormat {
12    /// Format 0: a single multi-channel track.
13    SingleTrack,
14    /// Format 1: several tracks played simultaneously.
15    Simultaneous,
16    /// Format 2: several independent single-track patterns.
17    Independent,
18}
19
20/// One track: an ordered list of timestamped events.
21#[derive(Clone, Debug, PartialEq, Eq)]
22pub struct SmfTrack {
23    /// Events in this track, in absolute time order after canonicalisation.
24    pub events: Vec<MidiEvent>,
25}
26
27/// A parsed Standard MIDI File: its format, resolution, and tracks.
28#[derive(Clone, Debug, PartialEq, Eq)]
29pub struct SmfFile {
30    /// Header format field.
31    pub format: SmfFormat,
32    /// Resolution in ticks per quarter note.
33    pub tpq: u32,
34    /// Tracks in file order.
35    pub tracks: Vec<SmfTrack>,
36}
37
38/// Options controlling SMF serialisation.
39#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
40pub struct SmfWriteOptions {
41    /// When set, omit a redundant status byte for consecutive channel messages
42    /// (MIDI running status).
43    pub running_status: bool,
44}
45
46/// An iterator that merges all tracks of an [`SmfFile`] into a single
47/// time-ordered, track-tagged stream.
48///
49/// Created by [`SmfFile::merge_cursor`]; yields
50/// [`TrackedMidiEvent`](sim_lib_midi_core::TrackedMidiEvent)s.
51pub struct SmfMergeCursor<'a> {
52    file: &'a SmfFile,
53    next_index: Vec<usize>,
54}
55
56impl SmfFile {
57    /// Sorts every track into canonical order and ensures each ends with an
58    /// end-of-track meta event.
59    pub fn canonicalize(&mut self) {
60        for track in &mut self.tracks {
61            canonicalize_track(track, self.tpq);
62        }
63    }
64
65    /// Returns a cursor that merges all tracks into one time-ordered stream.
66    pub fn merge_cursor(&self) -> SmfMergeCursor<'_> {
67        SmfMergeCursor {
68            file: self,
69            next_index: vec![0; self.tracks.len()],
70        }
71    }
72
73    /// Collects every track's events into a single time-ordered,
74    /// track-tagged vector.
75    pub fn merged_events(&self) -> Vec<TrackedMidiEvent> {
76        let mut merged = Vec::new();
77        for event in self.merge_cursor() {
78            merged.push(event);
79        }
80        merged
81    }
82}
83
84impl<'a> Iterator for SmfMergeCursor<'a> {
85    type Item = TrackedMidiEvent;
86
87    fn next(&mut self) -> Option<Self::Item> {
88        let mut best: Option<(usize, &MidiEvent)> = None;
89        for (track_idx, track) in self.file.tracks.iter().enumerate() {
90            let event = track.events.get(self.next_index[track_idx])?;
91            match best {
92                None => best = Some((track_idx, event)),
93                Some((best_track, best_event)) => {
94                    if compare_event_order(event, track_idx, best_event, best_track)
95                        == Ordering::Less
96                    {
97                        best = Some((track_idx, event));
98                    }
99                }
100            }
101        }
102        let (track_idx, event) = best?;
103        self.next_index[track_idx] += 1;
104        Some(TrackedMidiEvent {
105            last_track: track_idx,
106            event: event.clone(),
107        })
108    }
109}
110
111pub(crate) fn canonicalize_track(track: &mut SmfTrack, tpq: u32) {
112    track.events.sort_by(compare_events_same_track);
113    if !track
114        .events
115        .iter()
116        .any(|event| matches!(event.payload, MidiPayload::Meta(MetaEvent::EndOfTrack)))
117    {
118        let last_ticks = track
119            .events
120            .last()
121            .map(|event| event.time.ticks)
122            .unwrap_or(0);
123        track.events.push(MidiEvent {
124            time: TickTime::new(last_ticks, tpq).unwrap_or(TickTime::ZERO),
125            origin: synthetic_origin(),
126            payload: MidiPayload::Meta(MetaEvent::EndOfTrack),
127        });
128    }
129    track.events.sort_by(compare_events_same_track);
130}
131
132pub(crate) fn compare_event_order(
133    left: &MidiEvent,
134    left_track: usize,
135    right: &MidiEvent,
136    right_track: usize,
137) -> Ordering {
138    compare_time(left.time, right.time)
139        .then_with(|| event_priority(left).cmp(&event_priority(right)))
140        .then_with(|| left_track.cmp(&right_track))
141}
142
143fn compare_events_same_track(left: &MidiEvent, right: &MidiEvent) -> Ordering {
144    compare_time(left.time, right.time)
145        .then_with(|| event_priority(left).cmp(&event_priority(right)))
146}
147
148fn compare_time(left: TickTime, right: TickTime) -> Ordering {
149    let left_scaled = i128::from(left.ticks) * i128::from(right.tpq);
150    let right_scaled = i128::from(right.ticks) * i128::from(left.tpq);
151    left_scaled.cmp(&right_scaled)
152}
153
154fn event_priority(event: &MidiEvent) -> u8 {
155    match event.payload {
156        MidiPayload::Meta(MetaEvent::EndOfTrack) => 4,
157        MidiPayload::Meta(_) => 0,
158        MidiPayload::Channel(ChannelMessage::NoteOff { .. }) => 1,
159        MidiPayload::Channel(ChannelMessage::NoteOn { .. }) => 2,
160        _ => 3,
161    }
162}