midi_reader_writer/midly_0_5.rs
1// Facilitate reading and writing midi files.
2//
3// Copyright (C) 2021 Pieter Penninckx
4//
5// `midi-reader-writer` is licensed under the Apache License, Version 2.0
6// or the MIT license, at your option.
7//
8// For the application of the MIT license, the examples included in the doc comments are not
9// considered "substantial portions of this Software".
10//
11// License texts can be found:
12// * for the Apache License, Version 2.0: <LICENSE-APACHE.txt> or
13// <http://www.apache.org/licenses/LICENSE-2.0>
14// * for the MIT license: <LICENSE-MIT.txt> or
15// <http://opensource.org/licenses/MIT>.
16//
17//
18
19//! Everything specific to using this crate with the [`midly`](https://crates.io/crates/midly) crate,
20//! version 0.5.x, behind the `engine-midly-0-5` feature.
21//!
22//! This module also adds some extra functionality to be used with the `midly` crate:
23//! * [`merge_tracks`]: Create an iterator over all tracks, merged (behind the `read` feature)
24//! * [`TrackSeparator`]: Separate the tracks again.
25
26/// Re-exports from the [`midly`](https://crates.io/crates/midly) crate, version 0.5.x.
27pub mod exports {
28 pub use midly_0_5::*;
29}
30
31use self::exports::{num::u28, MetaMessage, Track, TrackEvent, TrackEventKind};
32#[cfg(all(test, feature = "convert-time"))]
33use self::exports::{
34 num::{u15, u24},
35 Format,
36};
37#[cfg(all(test, any(feature = "read", feature = "convert-time")))]
38use self::exports::{
39 num::{u4, u7},
40 MidiMessage,
41};
42#[cfg(feature = "convert-time")]
43use self::exports::{Fps, Header, Timing};
44#[cfg(feature = "convert-time")]
45use crate::{
46 ConvertMicroSecondsToTicks, ConvertTicksToMicroseconds, MidiEvent, TimeConversionError,
47};
48#[cfg(feature = "read")]
49use itertools::Itertools; // TODO: remove if not needed.
50use std::iter::FromIterator;
51#[cfg(feature = "convert-time")]
52use std::num::NonZeroU64;
53use std::num::TryFromIntError;
54use std::{
55 convert::TryFrom,
56 error::Error,
57 fmt::{Display, Formatter},
58};
59#[cfg(feature = "convert-time")]
60use timestamp_stretcher::TimestampStretcher;
61
62#[cfg(feature = "convert-time")]
63const MICROSECONDS_PER_SECOND: u64 = 1_000_000;
64#[cfg(feature = "convert-time")]
65const SECONDS_PER_MINUTE: u64 = 60;
66#[cfg(feature = "convert-time")]
67const MICROSECONDS_PER_MINUTE: u64 = SECONDS_PER_MINUTE * MICROSECONDS_PER_SECOND;
68#[cfg(feature = "convert-time")]
69const DEFAULT_BEATS_PER_MINUTE: u64 = 120;
70
71#[cfg(feature = "read")]
72/// Create an iterator over all the tracks, merged.
73/// The item has type `(u64, usize, TrackEventKind)`,
74/// where the first element of the triple is the timing of the event relative to the beginning
75/// of the tracks, in ticks,
76/// the second item of the triple is the track index and the last item is the event itself.
77///
78/// # Example
79/// ```
80/// use midi_reader_writer::midly_0_5::{
81/// merge_tracks,
82/// exports::{
83/// MidiMessage,
84/// TrackEvent,
85/// TrackEventKind,
86/// num::{u15, u4, u7, u28}
87/// }
88/// };
89///
90/// // Create a note on event with the given channel
91/// fn note_on_with_channel(channel: u8) -> TrackEventKind<'static> {
92/// // ...
93/// # TrackEventKind::Midi {
94/// # channel: u4::new(channel),
95/// # message: MidiMessage::NoteOn {
96/// # key: u7::new(1),
97/// # vel: u7::new(1),
98/// # },
99/// # }
100/// }
101///
102/// // Create a note on event with the given delta and channel.
103/// fn note_on_with_delta_and_channel(delta: u32, channel: u8) -> TrackEvent<'static> {
104/// // ...
105/// # TrackEvent {
106/// # delta: u28::new(delta),
107/// # kind: note_on_with_channel(channel),
108/// # }
109/// }
110///
111/// let tracks = vec![
112/// vec![
113/// note_on_with_delta_and_channel(2, 0),
114/// note_on_with_delta_and_channel(100, 1),
115/// ],
116/// vec![note_on_with_delta_and_channel(30, 2)],
117/// ];
118/// let result: Vec<_> = merge_tracks(&tracks[..]).collect();
119/// assert_eq!(
120/// result,
121/// vec![
122/// (2, 0, note_on_with_channel(0)),
123/// (30, 1, note_on_with_channel(2)),
124/// (102, 0, note_on_with_channel(1)),
125/// ]
126/// )
127/// ```
128pub fn merge_tracks<'a, 'b>(
129 tracks: &'b [Track<'a>],
130) -> impl Iterator<Item = (u64, usize, TrackEventKind<'a>)> + 'b {
131 let mut track_index = 0;
132 tracks
133 .iter()
134 .map(|t| {
135 let mut offset = 0;
136 let result = t.iter().map(move |e| {
137 offset += e.delta.as_int() as u64;
138 (offset, track_index, e.kind)
139 });
140 track_index += 1;
141 result
142 })
143 .kmerge_by(|(t1, _, _), (t2, _, _)| t1 < t2)
144}
145
146#[cfg(feature = "read")]
147#[test]
148fn merge_tracks_has_sufficiently_flexible_lifetime_annotation() {
149 fn wrap(bytes: &[u8]) -> Track {
150 let event = TrackEvent {
151 delta: u28::new(123),
152 kind: TrackEventKind::SysEx(bytes),
153 };
154 merge_tracks(&[vec![event]])
155 .map(|(_, _, kind)| TrackEvent {
156 delta: u28::new(0),
157 kind,
158 })
159 .collect()
160 }
161
162 let bytes = "abc".to_string(); // Force a non-static lifetime.
163 wrap(bytes.as_bytes());
164}
165
166#[cfg(feature = "read")]
167#[test]
168fn merge_tracks_works() {
169 fn kind(channel: u8) -> TrackEventKind<'static> {
170 TrackEventKind::Midi {
171 channel: u4::new(channel),
172 message: MidiMessage::NoteOn {
173 key: u7::new(1),
174 vel: u7::new(1),
175 },
176 }
177 }
178 fn track_event(delta: u32, channel: u8) -> TrackEvent<'static> {
179 TrackEvent {
180 delta: u28::new(delta),
181 kind: kind(channel),
182 }
183 }
184
185 let tracks = vec![
186 vec![track_event(1, 0), track_event(2, 1), track_event(4, 2)],
187 vec![track_event(2, 3), track_event(2, 4), track_event(5, 5)],
188 ];
189 let result: Vec<_> = merge_tracks(&tracks[..]).collect();
190 assert_eq!(
191 result,
192 vec![
193 (1, 0, kind(0)),
194 (2, 1, kind(3)),
195 (3, 0, kind(1)),
196 (4, 1, kind(4)),
197 (7, 0, kind(2)),
198 (9, 1, kind(5))
199 ]
200 )
201}
202
203#[cfg(feature = "convert-time")]
204impl TryFrom<Header> for ConvertTicksToMicroseconds {
205 type Error = TimeConversionError;
206
207 /// Create a new `ConvertTicksToMicrosecond` with the given header.
208 ///
209 /// The parameter `header` is used to determine the meaning of "tick", since this is stored
210 /// in the header in a midi file.
211 fn try_from(header: Header) -> Result<Self, Self::Error> {
212 let time_stretcher;
213 let ticks_per_beat;
214 use TimeConversionError::*;
215 match header.timing {
216 Timing::Metrical(t) => {
217 let tpb = NonZeroU64::new(t.as_int() as u64).ok_or(ZeroTicksPerBeatNotSupported)?;
218 time_stretcher = TimestampStretcher::new(
219 MICROSECONDS_PER_MINUTE / DEFAULT_BEATS_PER_MINUTE,
220 tpb,
221 );
222 ticks_per_beat = Some(tpb);
223 }
224 Timing::Timecode(Fps::Fps29, ticks_per_frame) => {
225 // Frames per second = 30 / 1.001 = 30000 / 1001
226 // microseconds = ticks * microseconds_per_second / (ticks_per_frame * frames_per_second) ;
227 time_stretcher = TimestampStretcher::new(
228 MICROSECONDS_PER_SECOND * 1001,
229 NonZeroU64::new((ticks_per_frame as u64) * 30000)
230 .ok_or(ZeroTicksPerFrameNotSupported)?,
231 );
232 ticks_per_beat = None;
233 }
234 Timing::Timecode(fps, ticks_per_frame) => {
235 // microseconds = ticks * microseconds_per_second / (ticks_per_frame * frames_per_second) ;
236 time_stretcher = TimestampStretcher::new(
237 MICROSECONDS_PER_SECOND,
238 NonZeroU64::new((fps.as_int() as u64) * (ticks_per_frame as u64))
239 .ok_or(ZeroTicksPerFrameNotSupported)?,
240 );
241 ticks_per_beat = None;
242 }
243 }
244 Ok(Self {
245 time_stretcher,
246 ticks_per_beat,
247 })
248 }
249}
250
251#[cfg(feature = "convert-time")]
252impl<'a> MidiEvent for TrackEventKind<'a> {
253 fn tempo(&self) -> Option<u32> {
254 if let TrackEventKind::Meta(MetaMessage::Tempo(tempo)) = self {
255 Some(tempo.as_int())
256 } else {
257 None
258 }
259 }
260}
261
262#[cfg(feature = "convert-time")]
263#[test]
264pub fn convert_ticks_to_microseconds_works_with_one_event() {
265 // 120 beats per minute
266 // = 120 beats per 60 seconds
267 // = 120 beats per 60 000 000 microseconds
268 // so the tempo is
269 // 60 000 000 / 120 microseconds per beat
270 // = 10 000 000 / 20 microseconds per beat
271 // = 500 000 microseconds per beat
272 let tempo_in_microseconds_per_beat = 500000;
273 let ticks_per_beat = 32;
274 // One event after 1 second.
275 // One second corresponds to two beats, so to 64 ticks.
276 let event_time_in_ticks: u64 = 64;
277 let header = Header {
278 timing: Timing::Metrical(u15::from(ticks_per_beat)),
279 format: Format::SingleTrack,
280 };
281 let mut converter =
282 ConvertTicksToMicroseconds::try_from(header).expect("No error expected at this point.");
283 let microseconds = converter.convert(
284 0,
285 &TrackEventKind::Meta(MetaMessage::Tempo(u24::from(
286 tempo_in_microseconds_per_beat,
287 ))),
288 );
289 assert_eq!(microseconds, 0);
290 let microseconds = converter.convert(
291 event_time_in_ticks,
292 &TrackEventKind::Midi {
293 channel: u4::from(0),
294 message: MidiMessage::NoteOn {
295 key: u7::from(60),
296 vel: u7::from(90),
297 },
298 },
299 );
300 assert_eq!(microseconds, 1000000);
301}
302
303#[cfg(feature = "convert-time")]
304impl From<Header> for ConvertMicroSecondsToTicks {
305 /// Create a new `ConvertMicroSecondsToTicks` with the given header.
306 ///
307 /// The parameter `header` is used to determine the meaning of "tick", since this is stored
308 /// in the header in a midi file.
309 fn from(header: Header) -> Self {
310 let time_stretcher;
311 let ticks_per_beat;
312 match header.timing {
313 Timing::Metrical(t) => {
314 let tpb = t.as_int() as u64;
315 time_stretcher = TimestampStretcher::new(
316 tpb,
317 NonZeroU64::new(MICROSECONDS_PER_MINUTE / DEFAULT_BEATS_PER_MINUTE)
318 .expect("Bug: MICROSECONDS_PER_MINUTE / DEFAULT_BEATS_PER_MINUTE should not be zero."),
319 );
320 ticks_per_beat = Some(tpb);
321 }
322 Timing::Timecode(Fps::Fps29, ticks_per_frame) => {
323 // Frames per second = 30 / 1.001 = 30000 / 1001
324 // ticks = microseconds * ticks_per_frame * frames_per_second / microseconds_per_second
325 time_stretcher = TimestampStretcher::new(
326 (ticks_per_frame as u64) * 30000,
327 NonZeroU64::new(MICROSECONDS_PER_SECOND * 1001)
328 .expect("Bug: MICROSECONDS_PER_SECOND * 1001 should not be zero."),
329 );
330 ticks_per_beat = None;
331 }
332 Timing::Timecode(fps, ticks_per_frame) => {
333 // ticks = microseconds * ticks_per_frame * frames_per_second / microseconds_per_second
334 time_stretcher = TimestampStretcher::new(
335 (fps.as_int() as u64) * (ticks_per_frame as u64),
336 NonZeroU64::new(MICROSECONDS_PER_SECOND)
337 .expect("Bug: MICROSECONDS_PER_SECOND should not be zero."),
338 );
339 ticks_per_beat = None;
340 }
341 }
342 Self {
343 time_stretcher,
344 ticks_per_beat,
345 }
346 }
347}
348
349#[cfg(feature = "convert-time")]
350#[test]
351pub fn convert_microseconds_to_ticks_works_with_one_event() {
352 // 120 beats per minute
353 // = 120 beats per 60 seconds
354 // = 120 beats per 60 000 000 microseconds
355 // so the tempo is
356 // 60 000 000 / 120 microseconds per beat
357 // = 10 000 000 / 20 microseconds per beat
358 // = 500 000 microseconds per beat
359 let tempo_in_microseconds_per_beat = 500000;
360 let ticks_per_beat = 32;
361 // One event after 1 second.
362 // One second corresponds to two beats, so to 64 ticks.
363 let expected_vent_time_in_ticks: u64 = 64;
364 let event_time_in_microseconds: u64 = 1000000;
365 let header = Header {
366 timing: Timing::Metrical(u15::from(ticks_per_beat)),
367 format: Format::SingleTrack,
368 };
369 let mut converter = ConvertMicroSecondsToTicks::from(header);
370 let microseconds = converter
371 .convert(
372 0,
373 &TrackEventKind::Meta(MetaMessage::Tempo(u24::from(
374 tempo_in_microseconds_per_beat,
375 ))),
376 )
377 .expect("No error expected at this point.");
378 assert_eq!(microseconds, 0);
379 let observed_event_time_in_ticks = converter
380 .convert(
381 event_time_in_microseconds,
382 &TrackEventKind::Midi {
383 channel: u4::from(0),
384 message: MidiMessage::NoteOn {
385 key: u7::from(60),
386 vel: u7::from(90),
387 },
388 },
389 )
390 .expect("No error expected at this point");
391 assert_eq!(expected_vent_time_in_ticks, observed_event_time_in_ticks);
392}
393
394/// The error type returned by the [`TrackSeparator::push()`] method.
395#[derive(Debug)]
396#[non_exhaustive]
397pub enum SeparateTracksError {
398 /// The specified time overflows what can be specified.
399 TimeOverflow,
400 /// Time decreased from one event to a next event.
401 TimeCanOnlyIncrease,
402}
403
404impl From<TryFromIntError> for SeparateTracksError {
405 fn from(_: TryFromIntError) -> Self {
406 SeparateTracksError::TimeOverflow
407 }
408}
409
410impl Display for SeparateTracksError {
411 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
412 match self {
413 SeparateTracksError::TimeOverflow => {
414 write!(
415 f,
416 "the time overflows what can be represented in a midi file."
417 )
418 }
419 SeparateTracksError::TimeCanOnlyIncrease => {
420 write!(f, "subsequent events with decreasing time found.")
421 }
422 }
423 }
424}
425
426impl Error for SeparateTracksError {
427 fn source(&self) -> Option<&(dyn Error + 'static)> {
428 None
429 }
430}
431
432/// Write to a track, keeping ... well, keeping "track" of the timing relative to the beginning.
433#[derive(Clone)]
434struct TrackWriter<'a> {
435 track: Track<'a>,
436 ticks: u64,
437}
438
439impl<'a> TrackWriter<'a> {
440 fn new() -> Self {
441 TrackWriter {
442 track: Vec::new(),
443 ticks: 0,
444 }
445 }
446
447 fn push(&mut self, ticks: u64, kind: TrackEventKind<'a>) -> Result<(), SeparateTracksError> {
448 use SeparateTracksError::*;
449 if self.ticks > ticks {
450 return Err(TimeCanOnlyIncrease);
451 }
452 let delta = ticks - self.ticks;
453 let delta = u28::try_from(u32::try_from(delta)?).ok_or(TimeOverflow)?;
454 self.ticks = ticks;
455 self.track.push(TrackEvent { delta, kind });
456 Ok(())
457 }
458}
459
460/// Separate events into different tracks.
461pub struct TrackSeparator<'a> {
462 tracks: Vec<TrackWriter<'a>>,
463}
464
465impl<'a> TrackSeparator<'a> {
466 /// Create a new `TrackSeparator`.
467 ///
468 /// # Example
469 /// ```
470 /// use midi_reader_writer::midly_0_5::TrackSeparator;
471 /// let track_separator = TrackSeparator::new();
472 /// let tracks : Vec<_> = track_separator.collect();
473 /// assert!(tracks.is_empty());
474 /// ```
475 #[inline]
476 pub fn new() -> Self {
477 TrackSeparator { tracks: Vec::new() }
478 }
479
480 /// Push a new event.
481 ///
482 /// # Parameters
483 /// * `ticks`: the time in midi ticks, relative to the beginning
484 /// of the tracks
485 /// * `track_index`: the index of the track to which the event belongs
486 /// * `event`: the event
487 ///
488 /// # Example
489 /// ```
490 /// use midi_reader_writer::midly_0_5::{TrackSeparator, exports::{TrackEventKind, TrackEvent, MetaMessage}};
491 /// # use midi_reader_writer::midly_0_5::exports::{
492 /// # MidiMessage,
493 /// # num::{u4, u7, u28}
494 /// # };
495 ///
496 /// // Create a note on event with the given channel
497 /// fn note_on_with_channel(channel: u8) -> TrackEventKind<'static> {
498 /// // ...
499 /// # TrackEventKind::Midi {
500 /// # channel: u4::new(channel),
501 /// # message: MidiMessage::NoteOn {
502 /// # key: u7::new(1),
503 /// # vel: u7::new(1),
504 /// # },
505 /// # }
506 /// }
507 ///
508 /// // Create a note on event with the given delta and channel.
509 /// fn note_on_with_delta_and_channel(delta: u32, channel: u8) -> TrackEvent<'static> {
510 /// // ...
511 /// # TrackEvent {
512 /// # delta: u28::new(delta),
513 /// # kind: note_on_with_channel(channel),
514 /// # }
515 /// }
516 ///
517 /// // Create an end-of-track event with the given delta.
518 /// fn end_of_track(delta: u32) -> TrackEvent<'static> {
519 /// // ...
520 /// # TrackEvent {
521 /// # delta: u28::new(delta),
522 /// # kind: TrackEventKind::Meta(MetaMessage::EndOfTrack)
523 /// # }
524 /// }
525 ///
526 /// let mut track_separator = TrackSeparator::new();
527 /// track_separator.push(5, 0, note_on_with_channel(0));
528 /// track_separator.push(0, 1, note_on_with_channel(1));
529 /// track_separator.push(10, 0, note_on_with_channel(2));
530 /// track_separator.push(3, 1, TrackEventKind::Meta(MetaMessage::EndOfTrack));
531 /// let tracks : Vec<_> = track_separator.collect();
532 /// assert_eq!(tracks.len(), 2);
533 /// assert_eq!(
534 /// tracks[0],
535 /// vec![
536 /// note_on_with_delta_and_channel(5, 0),
537 /// note_on_with_delta_and_channel(5, 2),
538 /// end_of_track(0)
539 /// ]
540 /// );
541 /// assert_eq!(tracks[1], vec![note_on_with_delta_and_channel(0, 1), end_of_track(3)]);
542 /// ```
543
544 #[inline]
545 pub fn push(
546 &mut self,
547 ticks: u64,
548 track_index: usize,
549 event: TrackEventKind<'a>,
550 ) -> Result<(), SeparateTracksError> {
551 if self.tracks.len() <= track_index {
552 self.tracks.resize(track_index + 1, TrackWriter::new());
553 }
554 self.tracks[track_index].push(ticks, event)
555 }
556
557 /// Append all events from an iterator of triples of type `(u64, usize, TrackEventKind)`,
558 /// where
559 /// * the first item of the triple (of type `u64`) is the time in midi ticks, relative to the beginning
560 /// of the tracks
561 /// * the second item of the triple (of type `usize`) is the track index.
562 /// * the last item of the triple is the event itself.
563 /// # Example
564 /// ```
565 /// use midi_reader_writer::midly_0_5::{TrackSeparator, exports::{TrackEventKind, TrackEvent}};
566 /// # use midi_reader_writer::midly_0_5::exports::{MetaMessage, MidiMessage, num::{u4, u7, u28}};
567 ///
568 /// fn note_on_with_channel(channel: u8) -> TrackEventKind<'static> {
569 /// // ...
570 /// # TrackEventKind::Midi {
571 /// # channel: u4::new(channel),
572 /// # message: MidiMessage::NoteOn {
573 /// # key: u7::new(1),
574 /// # vel: u7::new(1),
575 /// # },
576 /// # }
577 /// }
578 ///
579 /// fn note_on_with_channel_and_delta_time(delta: u32, channel: u8) -> TrackEvent<'static> {
580 /// // ...
581 /// # TrackEvent {
582 /// # delta: u28::new(delta),
583 /// # kind: note_on_with_channel(channel),
584 /// # }
585 /// }
586 /// // Create an end-of-track event with the given delta.
587 /// fn end_of_track(delta: u32) -> TrackEvent<'static> {
588 /// // ...
589 /// # TrackEvent {
590 /// # delta: u28::new(delta),
591 /// # kind: TrackEventKind::Meta(MetaMessage::EndOfTrack)
592 /// # }
593 /// }
594 ///
595 /// let events : Vec<(u64, usize, _)>= vec![
596 /// (1, 0, note_on_with_channel(0)),
597 /// (2, 1, note_on_with_channel(3)),
598 /// (3, 0, note_on_with_channel(1)),
599 /// (4, 1, note_on_with_channel(4)),
600 /// (7, 0, note_on_with_channel(2)),
601 /// (9, 1, note_on_with_channel(5)),
602 /// ];
603 ///
604 /// let mut separator = TrackSeparator::new();
605 /// separator.extend(events).expect("No error should occur here.");
606 /// let separated : Vec<_> = separator.collect();
607 ///
608 /// assert_eq!(
609 /// separated,
610 /// vec![
611 /// vec![
612 /// note_on_with_channel_and_delta_time(1, 0),
613 /// note_on_with_channel_and_delta_time(2, 1),
614 /// note_on_with_channel_and_delta_time(4, 2),
615 /// end_of_track(0)
616 /// ],
617 /// vec![
618 /// note_on_with_channel_and_delta_time(2, 3),
619 /// note_on_with_channel_and_delta_time(2, 4),
620 /// note_on_with_channel_and_delta_time(5, 5),
621 /// end_of_track(0)
622 /// ],
623 /// ]
624 /// );
625 /// ```
626 #[inline]
627 pub fn extend<I>(&mut self, iter: I) -> Result<(), SeparateTracksError>
628 where
629 I: IntoIterator<Item = (u64, usize, TrackEventKind<'a>)>,
630 {
631 for (ticks, track_index, event) in iter {
632 self.push(ticks, track_index, event)?
633 }
634 Ok(())
635 }
636
637 /// Create a collection containing all the tracks.
638 ///
639 /// The number of tracks is determined by the highest track index.
640 ///
641 /// An [`EndOfTrack`](exports::MetaMessage::EndOfTrack) event is added to the tracks that do not end with an `EndOfTrack` event.
642 pub fn collect<B>(self) -> B
643 where
644 B: FromIterator<Track<'a>>,
645 {
646 self.tracks
647 .into_iter()
648 .map(|t| {
649 let mut track = t.track;
650 let end_of_track_is_marked_with_event = if let Some(last_event) = track.last() {
651 last_event.kind == TrackEventKind::Meta(MetaMessage::EndOfTrack)
652 } else {
653 false
654 };
655 if !end_of_track_is_marked_with_event {
656 track.push(TrackEvent {
657 kind: TrackEventKind::Meta(MetaMessage::EndOfTrack),
658 delta: u28::new(0),
659 });
660 }
661 track
662 })
663 .collect()
664 }
665}