midi_file 0.2.0

For reading and writing MIDI files.
Documentation
use crate::byte_iter::ByteIter;
use crate::core::vlq::Vlq;
use crate::error::{Context, Result};
use crate::scribe::Scribe;
use std::convert::TryFrom;
use std::io::{Read, Write};

/// A sysex event in a MIDI file: an `F0` or `F7` status byte, a variable-length quantity giving
/// the number of data bytes, and the data bytes. See [`SysexEventType`] for the meaning of the two
/// forms.
#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct SysexEvent {
    t: SysexEventType,
    data: Vec<u8>,
}

impl SysexEvent {
    /// Create a new `SysexEvent`.
    pub fn new(t: SysexEventType, data: Vec<u8>) -> Self {
        Self { t, data }
    }

    /// Getter for the `t` field.
    pub fn sysex_type(&self) -> &SysexEventType {
        &self.t
    }

    /// Getter for the `data` field.
    pub fn data(&self) -> &[u8] {
        &self.data
    }

    pub(crate) fn parse<R: Read>(first_byte: u8, iter: &mut ByteIter<R>) -> Result<Self> {
        // the caller peeked the status byte; consume it
        iter.read_expect(first_byte).context(io!())?;
        let t = match first_byte {
            0xf0 => SysexEventType::F0,
            0xf7 => SysexEventType::F7,
            _ => invalid_file!("invalid sysex status byte {:#04X}", first_byte),
        };
        let length = iter.read_vlq_u32().context(io!())?;
        let data = iter.read_n(length as usize).context(io!())?;
        Ok(Self { t, data })
    }

    pub(crate) fn write<W: Write>(&self, w: &mut Scribe<W>) -> Result<()> {
        write_u8!(w, self.t as u8)?;
        let length =
            u32::try_from(self.data.len()).context(ctx!(crate::error::ErrorType::StringTooLong))?;
        w.write_all(&Vlq::new(length).to_bytes()).context(wr!())?;
        w.write_all(&self.data).context(wr!())?;
        Ok(())
    }
}

/// `<sysex event>` is used to specify a MIDI system exclusive message, either as one unit or in
/// packets, or as an "escape" to specify any arbitrary bytes to be transmitted. See Appendix 1 -
/// MIDI Messages. A normal complete system exclusive message is stored in a MIDI File in this way:
#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash, Default)]
pub enum SysexEventType {
    /// F0 `<length>` `<bytes to be transmitted after F0>`
    ///
    /// The length is stored as a variable-length quantity. It specifies the number of bytes which
    /// follow it, not including the F0 or the length itself. For instance, the transmitted message
    /// `F0 43 12 00 07 F7` would be stored in a MIDI File as `F0 05 43 12 00 07 F7`. It is required
    /// to include the `F7` at the end so that the reader of the MIDI File knows that it has read
    /// the entire message.
    #[default]
    F0 = 0xf0,

    /// F7 <length> <all bytes to be transmitted>
    ///
    /// Unfortunately, some synthesiser manufacturers specify that their system exclusive messages
    /// are to be transmitted as little packets. Each packet is only part of an entire syntactical
    /// system exclusive message, but the times they are transmitted are important. Examples of this
    /// are the bytes sent in a CZ patch dump, or the FB-01's "system exclusive mode" in which
    /// microtonal data can be transmitted. The F0 and F7 sysex events may be used together to break
    /// up syntactically complete system exclusive messages into timed packets.
    ///
    /// An F0 sysex event is used for the first packet in a series -- it is a message in which the
    /// F0 should be transmitted. An F7 sysex event is used for the remainder of the packets, which
    /// do not begin with F0. (Of course, the F7 is not considered part of the system exclusive
    /// message).
    ///
    /// A syntactic system exclusive message must always end with an F7, even if the real-life
    /// device didn't send one, so that you know when you've reached the end of an entire sysex
    /// message without looking ahead to the next event in the MIDI File. If it's stored in one
    /// complete F0 sysex event, the last byte must be an F7. There also must not be any
    /// transmittable MIDI events in between the packets of a multi-packet system exclusive message.
    F7 = 0xf7,
}