1#![forbid(unsafe_code)]
2
3use std::{
4 cmp::Ordering,
5 num::{NonZeroU8, NonZeroU16},
6};
7
8use sim_lib_midi_core::{
9 ChannelMessage, MetaEvent, MidiError, MidiEvent, MidiPayload, MidiTempoMap, TickTime,
10 TrackedMidiEvent, synthetic_origin,
11};
12
13use crate::SmfError;
14
15#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
17pub enum SmfFormat {
18 SingleTrack,
20 Simultaneous,
22 Independent,
24}
25
26impl SmfFormat {
27 pub const fn time_semantics(self) -> SmfTimeSemantics {
29 match self {
30 Self::SingleTrack | Self::Simultaneous => SmfTimeSemantics::SharedTimeline,
31 Self::Independent => SmfTimeSemantics::IndependentPatterns,
32 }
33 }
34}
35
36#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
38pub enum SmfTimeSemantics {
39 SharedTimeline,
41 IndependentPatterns,
44}
45
46#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
49pub enum SmpteRate {
50 Fps24,
52 Fps25,
54 Fps29Drop,
56 Fps30,
58}
59
60impl SmpteRate {
61 pub(crate) const fn from_header_byte(value: u8) -> Option<Self> {
62 match value as i8 {
63 -24 => Some(Self::Fps24),
64 -25 => Some(Self::Fps25),
65 -29 => Some(Self::Fps29Drop),
66 -30 => Some(Self::Fps30),
67 _ => None,
68 }
69 }
70
71 pub(crate) const fn header_byte(self) -> u8 {
72 match self {
73 Self::Fps24 => (-24_i8) as u8,
74 Self::Fps25 => (-25_i8) as u8,
75 Self::Fps29Drop => (-29_i8) as u8,
76 Self::Fps30 => (-30_i8) as u8,
77 }
78 }
79
80 pub const fn frames_per_second_ratio(self) -> (u32, u32) {
82 match self {
83 Self::Fps24 => (24, 1),
84 Self::Fps25 => (25, 1),
85 Self::Fps29Drop => (30_000, 1_001),
86 Self::Fps30 => (30, 1),
87 }
88 }
89
90 const fn nominal_frames_per_second(self) -> u32 {
91 match self {
92 Self::Fps24 => 24,
93 Self::Fps25 => 25,
94 Self::Fps29Drop | Self::Fps30 => 30,
95 }
96 }
97}
98
99#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
101pub enum SmfDivision {
102 Metrical {
104 ticks_per_quarter: NonZeroU16,
106 },
107 Smpte {
109 frames_per_second: SmpteRate,
111 ticks_per_frame: NonZeroU8,
113 },
114}
115
116impl SmfDivision {
117 pub const fn metrical(ticks_per_quarter: u16) -> Option<Self> {
120 if ticks_per_quarter >= 0x8000 {
121 return None;
122 }
123 match NonZeroU16::new(ticks_per_quarter) {
124 Some(ticks_per_quarter) => Some(Self::Metrical { ticks_per_quarter }),
125 None => None,
126 }
127 }
128
129 pub const fn smpte(frames_per_second: SmpteRate, ticks_per_frame: u8) -> Option<Self> {
131 match NonZeroU8::new(ticks_per_frame) {
132 Some(ticks_per_frame) => Some(Self::Smpte {
133 frames_per_second,
134 ticks_per_frame,
135 }),
136 None => None,
137 }
138 }
139
140 pub const fn ticks_per_quarter(self) -> Option<NonZeroU16> {
142 match self {
143 Self::Metrical { ticks_per_quarter } => Some(ticks_per_quarter),
144 Self::Smpte { .. } => None,
145 }
146 }
147
148 pub const fn ticks_per_second_ratio(self) -> Option<(u32, u32)> {
151 match self {
152 Self::Metrical { .. } => None,
153 Self::Smpte {
154 frames_per_second,
155 ticks_per_frame,
156 } => {
157 let (numerator, denominator) = frames_per_second.frames_per_second_ratio();
158 Some((numerator * ticks_per_frame.get() as u32, denominator))
159 }
160 }
161 }
162
163 pub const fn event_time_base(self) -> u32 {
170 match self {
171 Self::Metrical { ticks_per_quarter } => ticks_per_quarter.get() as u32,
172 Self::Smpte {
173 frames_per_second,
174 ticks_per_frame,
175 } => frames_per_second.nominal_frames_per_second() * ticks_per_frame.get() as u32,
176 }
177 }
178
179 pub(crate) const fn header_word(self) -> u16 {
180 match self {
181 Self::Metrical { ticks_per_quarter } => ticks_per_quarter.get(),
182 Self::Smpte {
183 frames_per_second,
184 ticks_per_frame,
185 } => u16::from_be_bytes([frames_per_second.header_byte(), ticks_per_frame.get()]),
186 }
187 }
188}
189
190#[derive(Clone, Debug, PartialEq, Eq)]
192pub struct SmfTrack {
193 pub events: Vec<MidiEvent>,
195}
196
197#[derive(Clone, Debug, PartialEq, Eq)]
199pub struct SmfFile {
200 pub format: SmfFormat,
202 pub division: SmfDivision,
204 pub tracks: Vec<SmfTrack>,
206}
207
208#[derive(Clone, Debug, PartialEq, Eq)]
214pub enum SmfTempoMaps {
215 Shared(MidiTempoMap),
217 Independent(Vec<MidiTempoMap>),
219}
220
221#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
223pub struct SmfWriteOptions {
224 pub running_status: bool,
227}
228
229pub struct SmfMergeCursor<'a> {
235 file: &'a SmfFile,
236 next_index: Vec<usize>,
237}
238
239impl SmfFile {
240 pub const fn ticks_per_quarter(&self) -> Option<u32> {
243 match self.division.ticks_per_quarter() {
244 Some(value) => Some(value.get() as u32),
245 None => None,
246 }
247 }
248
249 pub fn tempo_maps(&self) -> Result<SmfTempoMaps, MidiError> {
255 let tpq = self
256 .ticks_per_quarter()
257 .ok_or(MidiError::MetricalTempoRequired)?;
258 if self.format == SmfFormat::Independent {
259 let maps = self
260 .tracks
261 .iter()
262 .map(|track| MidiTempoMap::from_ordered_events(tpq, &track.events))
263 .collect::<Result<Vec<_>, _>>()?;
264 return Ok(SmfTempoMaps::Independent(maps));
265 }
266
267 let mut events = self
268 .tracks
269 .iter()
270 .enumerate()
271 .flat_map(|(track, contents)| {
272 contents
273 .events
274 .iter()
275 .enumerate()
276 .map(move |(index, event)| (track, index, event))
277 })
278 .collect::<Vec<_>>();
279 events.sort_by(|left, right| {
280 compare_event_order(left.2, left.0, right.2, right.0).then_with(|| left.1.cmp(&right.1))
281 });
282 Ok(SmfTempoMaps::Shared(MidiTempoMap::from_ordered_events(
283 tpq,
284 events.into_iter().map(|(_, _, event)| event),
285 )?))
286 }
287
288 pub fn canonicalize(&mut self) {
291 for track in &mut self.tracks {
292 canonicalize_track(track, self.division.event_time_base());
293 }
294 }
295
296 pub fn merge_cursor(&self) -> Result<SmfMergeCursor<'_>, SmfError> {
301 if self.format.time_semantics() == SmfTimeSemantics::IndependentPatterns {
302 return Err(SmfError::IndependentPatternsCannotMerge);
303 }
304 Ok(SmfMergeCursor {
305 file: self,
306 next_index: vec![0; self.tracks.len()],
307 })
308 }
309
310 pub fn merged_events(&self) -> Result<Vec<TrackedMidiEvent>, SmfError> {
316 let mut merged = Vec::new();
317 for event in self.merge_cursor()? {
318 merged.push(event);
319 }
320 Ok(merged)
321 }
322}
323
324impl<'a> Iterator for SmfMergeCursor<'a> {
325 type Item = TrackedMidiEvent;
326
327 fn next(&mut self) -> Option<Self::Item> {
328 let mut best: Option<(usize, &MidiEvent)> = None;
329 for (track_idx, track) in self.file.tracks.iter().enumerate() {
330 let Some(event) = track.events.get(self.next_index[track_idx]) else {
331 continue;
332 };
333 match best {
334 None => best = Some((track_idx, event)),
335 Some((best_track, best_event)) => {
336 if compare_event_order(event, track_idx, best_event, best_track)
337 == Ordering::Less
338 {
339 best = Some((track_idx, event));
340 }
341 }
342 }
343 }
344 let (track_idx, event) = best?;
345 self.next_index[track_idx] += 1;
346 Some(TrackedMidiEvent {
347 last_track: track_idx,
348 event: event.clone(),
349 })
350 }
351}
352
353pub(crate) fn canonicalize_track(track: &mut SmfTrack, tpq: u32) {
354 track.events.sort_by(compare_events_same_track);
355 if !track
356 .events
357 .iter()
358 .any(|event| matches!(event.payload, MidiPayload::Meta(MetaEvent::EndOfTrack)))
359 {
360 let last_ticks = track
361 .events
362 .last()
363 .map(|event| event.time.ticks)
364 .unwrap_or(0);
365 track.events.push(MidiEvent {
366 time: TickTime::new(last_ticks, tpq).unwrap_or(TickTime::ZERO),
367 origin: synthetic_origin(),
368 payload: MidiPayload::Meta(MetaEvent::EndOfTrack),
369 });
370 }
371 track.events.sort_by(compare_events_same_track);
372}
373
374pub(crate) fn compare_event_order(
375 left: &MidiEvent,
376 left_track: usize,
377 right: &MidiEvent,
378 right_track: usize,
379) -> Ordering {
380 compare_time(left.time, right.time)
381 .then_with(|| event_priority(left).cmp(&event_priority(right)))
382 .then_with(|| left_track.cmp(&right_track))
383}
384
385fn compare_events_same_track(left: &MidiEvent, right: &MidiEvent) -> Ordering {
386 compare_time(left.time, right.time)
387 .then_with(|| event_priority(left).cmp(&event_priority(right)))
388}
389
390fn compare_time(left: TickTime, right: TickTime) -> Ordering {
391 let left_scaled = i128::from(left.ticks) * i128::from(right.tpq);
392 let right_scaled = i128::from(right.ticks) * i128::from(left.tpq);
393 left_scaled.cmp(&right_scaled)
394}
395
396fn event_priority(event: &MidiEvent) -> u8 {
397 match event.payload {
398 MidiPayload::Meta(MetaEvent::EndOfTrack) => 4,
399 MidiPayload::Meta(_) => 0,
400 MidiPayload::Channel(ChannelMessage::NoteOff { .. }) => 1,
401 MidiPayload::Channel(ChannelMessage::NoteOn { .. }) => 2,
402 _ => 3,
403 }
404}