midly/
event.rs

1//! All sort of events and their parsers.
2
3use crate::{
4    live::{LiveEvent, SystemCommon},
5    prelude::*,
6    primitive::{read_varlen_slice, write_varlen_slice, SmpteTime},
7};
8
9/// Represents a parsed SMF track event.
10///
11/// Consists of a delta time (in MIDI ticks relative to the previous event) and the actual track
12/// event.
13#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
14pub struct TrackEvent<'a> {
15    /// How many MIDI ticks after the previous event should this event fire.
16    pub delta: u28,
17    /// The type of event along with event-specific data.
18    pub kind: TrackEventKind<'a>,
19}
20impl<'a> TrackEvent<'a> {
21    /// Advances the slice and updates `running_status`.
22    ///
23    /// In case of failure the slice might be left in the middle of an event!
24    pub(crate) fn read(
25        raw: &mut &'a [u8],
26        running_status: &mut Option<u8>,
27    ) -> Result<TrackEvent<'a>> {
28        let delta = u28::read_u7(raw).context(err_invalid!("failed to read event deltatime"))?;
29        let kind = TrackEventKind::read(raw, running_status)
30            .context(err_invalid!("failed to parse event"))?;
31        Ok(TrackEvent { delta, kind })
32    }
33
34    pub(crate) fn read_bytemap(
35        raw: &mut &'a [u8],
36        running_status: &mut Option<u8>,
37    ) -> Result<(&'a [u8], TrackEvent<'a>)> {
38        let delta = u28::read_u7(raw).context(err_invalid!("failed to read event deltatime"))?;
39        let old_raw = *raw;
40        let kind = TrackEventKind::read(raw, running_status)
41            .context(err_invalid!("failed to parse event"))?;
42        Ok((
43            &old_raw[..old_raw.len() - raw.len()],
44            TrackEvent { delta, kind },
45        ))
46    }
47
48    pub(crate) fn write<W: Write>(
49        &self,
50        running_status: &mut Option<u8>,
51        out: &mut W,
52    ) -> WriteResult<W> {
53        self.delta.write_varlen(out)?;
54        self.kind.write(running_status, out)?;
55        Ok(())
56    }
57
58    /// Remove any lifetimed data from this event to create a `TrackEvent` with `'static`
59    /// lifetime that can be stored and moved everywhere, solving borrow checker issues.
60    ///
61    /// WARNING: Any bytestrings in the input will be replaced by empty bytestrings.
62    pub fn to_static(&self) -> TrackEvent<'static> {
63        TrackEvent {
64            delta: self.delta,
65            kind: self.kind.to_static(),
66        }
67    }
68}
69
70/// Represents the different kinds of SMF events and their associated data.
71///
72/// It notably does *not* include the timing of the event; the `TrackEvent` struct is responsible
73/// for this.
74#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
75pub enum TrackEventKind<'a> {
76    /// A message associated to a MIDI channel carrying musical data.
77    ///
78    /// Usually, the bulk of MIDI data is these kind of messages.
79    Midi {
80        /// The MIDI channel that this event is associated with.
81        channel: u4,
82        /// The MIDI message type and associated data.
83        message: MidiMessage,
84    },
85    /// A System Exclusive message, carrying arbitrary data.
86    ///
87    /// The data bytes included here do not include the implicit `0xF0` prefix.
88    ///
89    /// Usually SysEx events end with an `0xF7` byte, but SysEx events that are split into several
90    /// small packets may only contain the `0xF7` byte in the last packet fragment.
91    SysEx(&'a [u8]),
92    /// An escape sequence, intended to send arbitrary data to the MIDI synthesizer.
93    Escape(&'a [u8]),
94    /// A meta-message, giving extra information for correct playback, like tempo, song name,
95    /// lyrics, etc...
96    Meta(MetaMessage<'a>),
97}
98impl<'a> TrackEventKind<'a> {
99    fn read(raw: &mut &'a [u8], running_status: &mut Option<u8>) -> Result<TrackEventKind<'a>> {
100        //Read status
101        let mut status = *raw.get(0).ok_or(err_invalid!("failed to read status"))?;
102        if status < 0x80 {
103            //Running status!
104            status = running_status.ok_or(err_invalid!(
105                "event missing status with no running status active"
106            ))?;
107        } else {
108            //Advance slice 1 byte to consume status. Note that because we already did `get()`, we
109            //can use panicking index here
110            *raw = &raw[1..];
111        }
112        //Delegate further parsing depending on status
113        let kind = match status {
114            0x80..=0xEF => {
115                *running_status = Some(status);
116                let data = MidiMessage::read_data_u8(status, raw)?;
117                let (channel, message) = MidiMessage::read(status, data);
118                TrackEventKind::Midi { channel, message }
119            }
120            0xFF => {
121                *running_status = None;
122                TrackEventKind::Meta(
123                    MetaMessage::read(raw).context(err_invalid!("failed to read meta event"))?,
124                )
125            }
126            0xF0 => {
127                *running_status = None;
128                TrackEventKind::SysEx(
129                    read_varlen_slice(raw).context(err_invalid!("failed to read sysex event"))?,
130                )
131            }
132            0xF7 => {
133                *running_status = None;
134                TrackEventKind::Escape(
135                    read_varlen_slice(raw).context(err_invalid!("failed to read escape event"))?,
136                )
137            }
138            0xF1..=0xF6 => bail!(err_invalid!(
139                "standard midi files cannot contain system common events"
140            )),
141            0xF8..=0xFE => bail!(err_invalid!(
142                "standard midi files cannot contain system realtime events"
143            )),
144            0x00..=0x7F => panic!("invalid running status without top bit set"),
145        };
146        Ok(kind)
147    }
148
149    /// Writes a single event to the given output writer.
150    ///
151    /// `running_status` keeps track of the last MIDI status, in order to make proper use of
152    /// running status. It should be shared between consecutive calls, and should initially be set
153    /// to `None`.
154    fn write<W: Write>(&self, running_status: &mut Option<u8>, out: &mut W) -> WriteResult<W> {
155        //Running Status rules:
156        // - MIDI Messages (0x80 ..= 0xEF) alter and use running status
157        // - System Exclusive (0xF0) cancels and cannot use running status
158        // - Escape (0xF7) cancels and cannot use running status
159        // - Meta Messages (0xFF) cancel and cannot use running status
160        match self {
161            TrackEventKind::Midi { channel, message } => {
162                let status = message.status_nibble() << 4 | channel.as_int();
163                if Some(status) != *running_status {
164                    //Explicitly write status
165                    out.write(&[status])?;
166                    *running_status = Some(status);
167                }
168                message.write(out)?;
169            }
170            TrackEventKind::SysEx(data) => {
171                *running_status = None;
172                out.write(&[0xF0])?;
173                write_varlen_slice(data, out)?;
174            }
175            TrackEventKind::Escape(data) => {
176                *running_status = None;
177                out.write(&[0xF7])?;
178                write_varlen_slice(data, out)?;
179            }
180            TrackEventKind::Meta(meta) => {
181                *running_status = None;
182                out.write(&[0xFF])?;
183                meta.write(out)?;
184            }
185        }
186        Ok(())
187    }
188
189    /// Lossy conversion from a track event to a live event.
190    ///
191    /// Only channel MIDI messages and not-split SysEx messages can be converted.
192    /// Meta messages and arbitrary escapes yield `None` when converted.
193    pub fn as_live_event(&self) -> Option<LiveEvent<'a>> {
194        match self {
195            TrackEventKind::Midi { channel, message } => Some(LiveEvent::Midi {
196                channel: *channel,
197                message: *message,
198            }),
199            TrackEventKind::SysEx(data) => {
200                if data.last() == Some(&0xF7) {
201                    let data_u7 = u7::slice_from_int(data);
202                    if data_u7.len() == data.len() - 1 {
203                        return Some(LiveEvent::Common(SystemCommon::SysEx(data_u7)));
204                    }
205                }
206                None
207            }
208            TrackEventKind::Escape(_data) => None,
209            TrackEventKind::Meta(_meta) => None,
210        }
211    }
212
213    /// Remove any lifetimed data from this event to create a `TrackEventKind` with `'static`
214    /// lifetime that can be stored and moved everywhere, solving borrow checker issues.
215    ///
216    /// WARNING: Any bytestrings in the input will be replaced by empty bytestrings.
217    pub fn to_static(&self) -> TrackEventKind<'static> {
218        use self::TrackEventKind::*;
219        match *self {
220            Midi { channel, message } => Midi { channel, message },
221            SysEx(_) => SysEx(b""),
222            Escape(_) => Escape(b""),
223            Meta(meta) => Meta(meta.to_static()),
224        }
225    }
226}
227
228/// Represents a MIDI message, usually associated to a MIDI channel.
229///
230/// If you wish to parse a MIDI message from a slice of raw MIDI bytes, use the
231/// [`LiveEvent::parse`](live/enum.LiveEvent.html#method.parse) method instead and ignore all
232/// variants except for [`LiveEvent::Midi`](live/enum.LiveEvent.html#variant.Midi).
233#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
234pub enum MidiMessage {
235    /// Stop playing a note.
236    NoteOff {
237        /// The MIDI key to stop playing.
238        key: u7,
239        /// The velocity with which to stop playing it.
240        vel: u7,
241    },
242    /// Start playing a note.
243    NoteOn {
244        /// The key to start playing.
245        key: u7,
246        /// The velocity (strength) with which to press it.
247        ///
248        /// Note that by convention a `NoteOn` message with a velocity of 0 is equivalent to a
249        /// `NoteOff`.
250        vel: u7,
251    },
252    /// Modify the velocity of a note after it has been played.
253    Aftertouch {
254        /// The key for which to modify its velocity.
255        key: u7,
256        /// The new velocity for the key.
257        vel: u7,
258    },
259    /// Modify the value of a MIDI controller.
260    Controller {
261        /// The controller to modify.
262        ///
263        /// See the MIDI spec for the meaning of each index.
264        controller: u7,
265        /// The value to set it to.
266        value: u7,
267    },
268    /// Change the program (also known as instrument) for a channel.
269    ProgramChange {
270        /// The new program (instrument) to use for the channel.
271        program: u7,
272    },
273    /// Change the note velocity of a whole channel at once, without starting new notes.
274    ChannelAftertouch {
275        /// The new velocity for all notes currently playing in the channel.
276        vel: u7,
277    },
278    /// Set the pitch bend value for the entire channel.
279    PitchBend {
280        /// The new pitch-bend value.
281        bend: PitchBend,
282    },
283}
284impl MidiMessage {
285    /// Midi messages have a known length.
286    pub(crate) fn msg_length(status: u8) -> usize {
287        const LENGTH_BY_STATUS: [u8; 16] = [0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 1, 1, 2, 0];
288        LENGTH_BY_STATUS[(status >> 4) as usize] as usize
289    }
290
291    /// Extract the data bytes from a raw slice.
292    pub(crate) fn read_data_u8(status: u8, raw: &mut &[u8]) -> Result<[u7; 2]> {
293        let len = Self::msg_length(status);
294        let data = raw
295            .split_checked(len)
296            .ok_or_else(|| err_invalid!("truncated midi message"))?;
297        Ok(match len {
298            1 => [u7::check_int(data[0])?, u7::from(0)],
299            2 => [u7::check_int(data[0])?, u7::check_int(data[1])?],
300            _ => [u7::from(0), u7::from(0)],
301        })
302    }
303
304    /// Get the data bytes from a databyte slice.
305    pub(crate) fn get_data_u7(status: u8, data: &[u7]) -> Result<[u7; 2]> {
306        let len = Self::msg_length(status);
307        ensure!(data.len() >= len, err_invalid!("truncated midi message"));
308        Ok(match len {
309            1 => [data[0], u7::from(0)],
310            2 => [data[0], data[1]],
311            _ => [u7::from(0), u7::from(0)],
312        })
313    }
314
315    /// Receives status byte and midi args separately.
316    ///
317    /// Panics if the `status` is not a MIDI message status (0x80..=0xEF).
318    pub(crate) fn read(status: u8, data: [u7; 2]) -> (u4, MidiMessage) {
319        let channel = u4::from(status);
320        let msg = match status >> 4 {
321            0x8 => MidiMessage::NoteOff {
322                key: data[0],
323                vel: data[1],
324            },
325            0x9 => MidiMessage::NoteOn {
326                key: data[0],
327                vel: data[1],
328            },
329            0xA => MidiMessage::Aftertouch {
330                key: data[0],
331                vel: data[1],
332            },
333            0xB => MidiMessage::Controller {
334                controller: data[0],
335                value: data[1],
336            },
337            0xC => MidiMessage::ProgramChange { program: data[0] },
338            0xD => MidiMessage::ChannelAftertouch { vel: data[0] },
339            0xE => {
340                //Note the little-endian order, contrasting with the default big-endian order of
341                //Standard Midi Files
342                let lsb = data[0].as_int() as u16;
343                let msb = data[1].as_int() as u16;
344                MidiMessage::PitchBend {
345                    bend: PitchBend(u14::from(msb << 7 | lsb)),
346                }
347            }
348            _ => panic!("parsed midi message before checking that status is in range"),
349        };
350        (channel, msg)
351    }
352    /// Get the raw status nibble for this MIDI message type.
353    pub(crate) fn status_nibble(&self) -> u8 {
354        match self {
355            MidiMessage::NoteOff { .. } => 0x8,
356            MidiMessage::NoteOn { .. } => 0x9,
357            MidiMessage::Aftertouch { .. } => 0xA,
358            MidiMessage::Controller { .. } => 0xB,
359            MidiMessage::ProgramChange { .. } => 0xC,
360            MidiMessage::ChannelAftertouch { .. } => 0xD,
361            MidiMessage::PitchBend { .. } => 0xE,
362        }
363    }
364    /// Write the data part of this message, not including the status.
365    pub(crate) fn write<W: Write>(&self, out: &mut W) -> WriteResult<W> {
366        match self {
367            MidiMessage::NoteOff { key, vel } => out.write(&[key.as_int(), vel.as_int()])?,
368            MidiMessage::NoteOn { key, vel } => out.write(&[key.as_int(), vel.as_int()])?,
369            MidiMessage::Aftertouch { key, vel } => out.write(&[key.as_int(), vel.as_int()])?,
370            MidiMessage::Controller { controller, value } => {
371                out.write(&[controller.as_int(), value.as_int()])?
372            }
373            MidiMessage::ProgramChange { program } => out.write(&[program.as_int()])?,
374            MidiMessage::ChannelAftertouch { vel } => out.write(&[vel.as_int()])?,
375            MidiMessage::PitchBend { bend } => {
376                let raw = bend.0.as_int();
377                out.write(&[(raw & 0x7F) as u8, (raw >> 7) as u8])?
378            }
379        }
380        Ok(())
381    }
382}
383
384/// The value of a pitch bend, represented as 14 bits.
385///
386/// A value of `0x0000` indicates full bend downwards.
387/// A value of `0x2000` indicates no bend.
388/// A value of `0x3FFF` indicates full bend upwards.
389#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
390pub struct PitchBend(pub u14);
391impl PitchBend {
392    /// The minimum value of `0x0000`, indicating full bend downwards.
393    #[inline]
394    pub const fn min_raw_value() -> PitchBend {
395        PitchBend(u14::new(0x0000))
396    }
397
398    /// The middle value of `0x2000`, indicating no bend.
399    #[inline]
400    pub const fn mid_raw_value() -> PitchBend {
401        PitchBend(u14::new(0x2000))
402    }
403
404    /// The maximum value of `0x3FFF`, indicating full bend upwards.
405    #[inline]
406    pub const fn max_raw_value() -> PitchBend {
407        PitchBend(u14::new(0x3FFF))
408    }
409
410    /// Create a `PitchBend` value from an int in the range `[-0x2000, 0x1FFF]`.
411    ///
412    /// Integers outside this range will be clamped.
413    #[inline]
414    pub fn from_int(int: i16) -> PitchBend {
415        PitchBend(u14::new((int.max(-0x2000).min(0x1FFF) + 0x2000) as u16))
416    }
417
418    /// Create a `PitchBend` value from a number in the range `[-1.0, 1.0)`.
419    ///
420    /// Floats outside this range will be clamped.
421    #[inline]
422    pub fn from_f32(float: f32) -> PitchBend {
423        PitchBend::from_int((float.max(-1.0).min(1.0) * 0x2000 as f32) as i16)
424    }
425
426    /// Create a `PitchBend` value from a number in the range `[-1.0, 1.0)`.
427    ///
428    /// Floats outside this range will be clamped.
429    #[inline]
430    pub fn from_f64(float: f64) -> PitchBend {
431        PitchBend::from_int((float.max(-1.0).min(1.0) * 0x2000 as f64) as i16)
432    }
433
434    /// Returns an int in the range `[-0x2000, 0x1FFF]`.
435    #[inline]
436    pub fn as_int(self) -> i16 {
437        self.0.as_int() as i16 - 0x2000
438    }
439
440    /// Returns an `f32` in the range `[-1.0, 1.0)`.
441    #[inline]
442    pub fn as_f32(self) -> f32 {
443        self.as_int() as f32 * (1.0 / 0x2000 as f32)
444    }
445
446    /// Returns an `f64` in the range `[-1.0, 1.0)`.
447    #[inline]
448    pub fn as_f64(self) -> f64 {
449        self.as_int() as f64 * (1.0 / 0x2000 as f64)
450    }
451}
452
453/// A "meta message", as defined by the SMF spec.
454/// These events carry metadata about the track, such as tempo, time signature, copyright, etc...
455#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
456pub enum MetaMessage<'a> {
457    /// For `Format::Sequential` MIDI file types, `TrackNumber` can be empty, and defaults to
458    /// the track index.
459    TrackNumber(Option<u16>),
460    /// Arbitrary text associated to an instant.
461    Text(&'a [u8]),
462    /// A copyright notice.
463    Copyright(&'a [u8]),
464    /// Information about the name of the track.
465    TrackName(&'a [u8]),
466    /// Information about the name of the current instrument.
467    InstrumentName(&'a [u8]),
468    /// Arbitrary lyric information associated to an instant.
469    Lyric(&'a [u8]),
470    /// Arbitrary marker text associated to an instant.
471    Marker(&'a [u8]),
472    /// Arbitrary cue point text associated to an instant.
473    CuePoint(&'a [u8]),
474    /// Information about the name of the current program.
475    ProgramName(&'a [u8]),
476    /// Name of the device that this file was intended to be played with.
477    DeviceName(&'a [u8]),
478    /// Number of the MIDI channel that this file was intended to be played with.
479    MidiChannel(u4),
480    /// Number of the MIDI port that this file was intended to be played with.
481    MidiPort(u7),
482    /// Obligatory at track end.
483    EndOfTrack,
484    /// Amount of microseconds per beat (quarter note).
485    ///
486    /// Usually appears at the beggining of a track, before any midi events are sent, but there
487    /// are no guarantees.
488    Tempo(u24),
489    /// The MIDI SMPTE offset meta message specifies an offset for the starting point of a MIDI
490    /// track from the start of a sequence in terms of SMPTE time (hours:minutes:seconds:frames:subframes).
491    ///
492    /// [Reference](https://www.recordingblogs.com/wiki/midi-smpte-offset-meta-message)
493    SmpteOffset(SmpteTime),
494    /// In order of the MIDI specification, numerator, denominator, MIDI clocks per click, 32nd
495    /// notes per quarter
496    TimeSignature(u8, u8, u8, u8),
497    /// As in the MIDI specification, negative numbers indicate number of flats and positive
498    /// numbers indicate number of sharps.
499    /// `false` indicates a major scale, `true` indicates a minor scale.
500    KeySignature(i8, bool),
501    /// Arbitrary data intended for the sequencer.
502    /// This data is never sent to a device.
503    SequencerSpecific(&'a [u8]),
504    /// An unknown or malformed meta-message.
505    ///
506    /// The first `u8` is the raw meta-message identifier byte.
507    /// The slice is the actual payload of the meta-message.
508    Unknown(u8, &'a [u8]),
509}
510impl<'a> MetaMessage<'a> {
511    /// Remove any lifetimed data from this event to create a `MidiMessage` with `'static` lifetime
512    /// that can be stored and moved everywhere, solving borrow checker issues.
513    ///
514    /// WARNING: Any bytestrings in the input will be replaced by empty bytestrings.
515    pub fn to_static(&self) -> MetaMessage<'static> {
516        use self::MetaMessage::*;
517        match *self {
518            TrackNumber(v) => TrackNumber(v),
519            Text(_) => Text(b""),
520            Copyright(_) => Copyright(b""),
521            TrackName(_) => TrackName(b""),
522            InstrumentName(_) => InstrumentName(b""),
523            Lyric(_) => Lyric(b""),
524            Marker(_) => Marker(b""),
525            CuePoint(_) => CuePoint(b""),
526            ProgramName(_) => ProgramName(b""),
527            DeviceName(_) => DeviceName(b""),
528            MidiChannel(v) => MidiChannel(v),
529            MidiPort(v) => MidiPort(v),
530            EndOfTrack => EndOfTrack,
531            Tempo(v) => Tempo(v),
532            SmpteOffset(v) => SmpteOffset(v),
533            TimeSignature(v0, v1, v2, v3) => TimeSignature(v0, v1, v2, v3),
534            KeySignature(v0, v1) => KeySignature(v0, v1),
535            SequencerSpecific(_) => SequencerSpecific(b""),
536            Unknown(v, _) => Unknown(v, b""),
537        }
538    }
539
540    #[allow(clippy::len_zero)]
541    fn read(raw: &mut &'a [u8]) -> Result<MetaMessage<'a>> {
542        let type_byte = u8::read(raw).context(err_invalid!("failed to read meta message type"))?;
543        let mut data =
544            read_varlen_slice(raw).context(err_invalid!("failed to read meta message data"))?;
545        Ok(match type_byte {
546            0x00 => MetaMessage::TrackNumber({
547                if data.len() >= 2 {
548                    Some(u16::read(&mut data)?)
549                } else {
550                    None
551                }
552            }),
553            0x01 => MetaMessage::Text(data),
554            0x02 => MetaMessage::Copyright(data),
555            0x03 => MetaMessage::TrackName(data),
556            0x04 => MetaMessage::InstrumentName(data),
557            0x05 => MetaMessage::Lyric(data),
558            0x06 => MetaMessage::Marker(data),
559            0x07 => MetaMessage::CuePoint(data),
560            0x08 => MetaMessage::ProgramName(data),
561            0x09 => MetaMessage::DeviceName(data),
562            0x20 if data.len() >= 1 => MetaMessage::MidiChannel(u4::read(&mut data)?),
563            0x21 if data.len() >= 1 => MetaMessage::MidiPort(u7::read(&mut data)?),
564            0x2F => MetaMessage::EndOfTrack,
565            0x51 if data.len() >= 3 => MetaMessage::Tempo(u24::read(&mut data)?),
566            0x54 if data.len() >= 5 => MetaMessage::SmpteOffset(
567                SmpteTime::read(&mut data).context(err_invalid!("failed to read smpte time"))?,
568            ),
569            0x58 if data.len() >= 4 => MetaMessage::TimeSignature(
570                u8::read(&mut data)?,
571                u8::read(&mut data)?,
572                u8::read(&mut data)?,
573                u8::read(&mut data)?,
574            ),
575            0x59 => {
576                MetaMessage::KeySignature(u8::read(&mut data)? as i8, u8::read(&mut data)? != 0)
577            }
578            0x7F => MetaMessage::SequencerSpecific(data),
579            _ => MetaMessage::Unknown(type_byte, data),
580        })
581    }
582    fn write<W: Write>(&self, out: &mut W) -> WriteResult<W> {
583        let mut write_msg = |type_byte: u8, data: &[u8]| {
584            out.write(&[type_byte])?;
585            write_varlen_slice(data, out)?;
586            Ok(())
587        };
588        match self {
589            MetaMessage::TrackNumber(track_num) => match track_num {
590                None => write_msg(0x00, &[]),
591                Some(track_num) => write_msg(0x00, &track_num.to_be_bytes()[..]),
592            },
593            MetaMessage::Text(data) => write_msg(0x01, data),
594            MetaMessage::Copyright(data) => write_msg(0x02, data),
595            MetaMessage::TrackName(data) => write_msg(0x03, data),
596            MetaMessage::InstrumentName(data) => write_msg(0x04, data),
597            MetaMessage::Lyric(data) => write_msg(0x05, data),
598            MetaMessage::Marker(data) => write_msg(0x06, data),
599            MetaMessage::CuePoint(data) => write_msg(0x07, data),
600            MetaMessage::ProgramName(data) => write_msg(0x08, data),
601            MetaMessage::DeviceName(data) => write_msg(0x09, data),
602            MetaMessage::MidiChannel(chan) => write_msg(0x20, &[chan.as_int()]),
603            MetaMessage::MidiPort(port) => write_msg(0x21, &[port.as_int()]),
604            MetaMessage::EndOfTrack => write_msg(0x2F, &[]),
605            MetaMessage::Tempo(microsperbeat) => {
606                write_msg(0x51, &microsperbeat.as_int().to_be_bytes()[1..])
607            }
608            MetaMessage::SmpteOffset(smpte) => write_msg(0x54, &smpte.encode()[..]),
609            MetaMessage::TimeSignature(num, den, ticksperclick, thirtysecondsperquarter) => {
610                write_msg(
611                    0x58,
612                    &[*num, *den, *ticksperclick, *thirtysecondsperquarter],
613                )
614            }
615            MetaMessage::KeySignature(sharps, minor) => {
616                write_msg(0x59, &[*sharps as u8, *minor as u8])
617            }
618            MetaMessage::SequencerSpecific(data) => write_msg(0x7F, data),
619            MetaMessage::Unknown(type_byte, data) => write_msg(*type_byte, data),
620        }
621    }
622}