diff --git a/src/core/message.rs b/src/core/message.rs index 41de608..c57100f 100644 --- a/src/core/message.rs +++ b/src/core/message.rs @@ -149,6 +149,16 @@ pub enum OnOff { Off = 0, } +/// A parameter found on most MIDI keyboards that have built-in sounds. Local Control can be set on +/// or off, and is normally found in the global parameters for a particular instrument. When enabled +/// the keyboard is electronically connected to the internal sounds of the instrument. This is the +/// “normal” or default mode for most instruments. When turned off the keyboard only transmits MIDI +/// to the MIDI out jack. The built-in sounds can then only be accessed from a MIDI input (or an +/// internal sequencer where applicable). When people use keyboards with external sequencing +/// equipment local control is normally turned off, and the sounds are just driven through the +/// sequencer. This prevents a phenomenon known as MIDI echo, where a sound is triggered directly by +/// the keyboard, and then a very short time later the same note is played again due to the data +/// being passed through the sequencer. #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct LocalControlValue { channel: Channel, @@ -173,6 +183,8 @@ impl LocalControlValue { } } +/// When Mono mode is selected, a single voice is assigned per MIDI Channel. This means that only +/// one note can be played on a given Channel at a given time. #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct MonoModeOnValue { channel: Channel, @@ -230,6 +242,12 @@ pub enum SystemRealtimeMessage { SystemReset = 0xff, } +/// MIDI System Messages are classified as being System Common Messages, System Real Time Messages, +/// or System Exclusive Messages. System Common messages are intended for all receivers in the +/// system. System Real Time messages are used for synchronization between clock-based MIDI +/// components. System Exclusive messages include a Manufacturer's Identification (ID) code, and are +/// used to transfer any number of data bytes in a format specified by the referenced manufacturer. +// TODO - add system exclusive messages (sysex) #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] #[allow(dead_code)] pub enum SystemMessage { @@ -243,7 +261,14 @@ impl Default for SystemMessage { } } +/// A MIDI message is made up of an eight-bit status byte which is generally followed by one or two +/// data bytes. There are a number of different types of MIDI messages. At the highest level, MIDI +/// messages are classified as being either Channel Messages or System Messages. Channel messages +/// are those which apply to a specific Channel, and the Channel number is included in the status +/// byte for these messages. System messages are not Channel specific, and no Channel number is +/// indicated in their status bytes. #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[allow(missing_docs)] pub enum Message { NoteOff(NoteMessage), NoteOn(NoteMessage), @@ -520,6 +545,7 @@ where /// byte. `Control` values greater than 31 are for the Lsb in these two-byte messages. #[repr(u8)] #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Default)] +#[allow(missing_docs)] pub enum Control { #[default] BankSelect = 0, diff --git a/src/file/event.rs b/src/file/event.rs index 9daaf2d..5cfa4a0 100644 --- a/src/file/event.rs +++ b/src/file/event.rs @@ -77,6 +77,7 @@ pub struct TrackEvent { } impl TrackEvent { + /// Create a new track event. pub fn new(delta_time: u32, event: Event) -> Self { Self { delta_time, event } } diff --git a/src/file/header.rs b/src/file/header.rs index fcfa365..59aae28 100644 --- a/src/file/header.rs +++ b/src/file/header.rs @@ -5,6 +5,17 @@ use snafu::ResultExt; use std::convert::TryFrom; use std::io::Write; +/// 2.1 - Header Chunks +/// The header chunk at the beginning of the file specifies some basic information about the data in +/// the file. Here's the syntax of the complete chunk: +/// `
= ` +/// +/// As described above, is the four ASCII characters 'MThd'; is a 32-bit +/// representation of the number 6 (high byte first). +/// +/// The data section contains three 16-bit words, stored most-significant byte first. +/// +/// The first word, , specifies the overall organisation of the file. #[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Hash)] pub struct Header { format: Format, @@ -47,6 +58,12 @@ impl Header { } } +/// The first word, , specifies the overall organisation of the file. Only three values of +/// `` are specified: +/// +/// 0-the file contains a single multichannel track +/// 1-the file contains one or more simultaneous tracks (or MIDI outputs) of a sequence +/// 2-the file contains one or more sequentially independent single-track patterns #[repr(u16)] #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash, Default)] pub enum Format { diff --git a/src/file/meta_event.rs b/src/file/meta_event.rs index afd1d1e..024b6f9 100644 --- a/src/file/meta_event.rs +++ b/src/file/meta_event.rs @@ -340,6 +340,22 @@ pub(crate) const LEN_META_SMTPE_OFFSET: u8 = 5; pub(crate) const LEN_META_TIME_SIG: u8 = 4; pub(crate) const LEN_META_KEY_SIG: u8 = 2; +/// FF 58 04 nn dd cc bb Time Signature +/// The time signature is expressed as four numbers. nn and dd represent the numerator and +/// denominator of the time signature as it would be notated. The denominator is a negative power +/// of two: 2 represents a quarter-note, 3 represents an eighth-note, etc. The cc parameter +/// expresses the number of MIDI clocks in a metronome click. The bb parameter expresses the number +/// of notated 32nd-notes in a MIDI quarter-note (24 MIDI clocks). This was added because there are +/// already multiple programs which allow a user to specify that what MIDI thinks of as a +/// quarter-note (24 clocks) is to be notated as, or related to in terms of, something else. +/// +/// Therefore, the complete event for 6/8 time, where the metronome clicks every three eighth-notes, +/// but there are 24 clocks per quarter-note, 72 to the bar, would be (in hex): +/// +/// FF 58 04 06 03 24 08 +/// +/// That is, 6/8 time (8 is 2 to the 3rd power, so this is 06 03), 36 MIDI clocks per dotted-quarter +/// (24 hex!), and eight notated 32nd-notes per quarter-note. #[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Hash)] pub struct TimeSignatureValue { /// The upper part of a time signature. For example, in 6/8, the `numerator` is 6. diff --git a/src/file/sysex.rs b/src/file/sysex.rs index d91e4a2..51750dc 100644 --- a/src/file/sysex.rs +++ b/src/file/sysex.rs @@ -25,10 +25,40 @@ impl SysexEvent { } } +/// `` 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, Debug, Eq, Ord, PartialEq, PartialOrd, Hash, Default)] pub enum SysexEventType { + /// 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 + /// + /// 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, } diff --git a/src/file/track.rs b/src/file/track.rs index b8515a9..73ba68f 100644 --- a/src/file/track.rs +++ b/src/file/track.rs @@ -14,16 +14,28 @@ use snafu::ResultExt; use std::convert::TryFrom; use std::io::{Read, Write}; +/// 2.3 - Track Chunks +/// The track chunks (type MTrk) are where actual song data is stored. Each track chunk is simply a +/// stream of MIDI events (and non-MIDI events), preceded by delta-time values. The format for Track +/// Chunks (described below) is exactly the same for all three formats (0, 1, and 2: see "Header +/// Chunk" above) of MIDI Files. +/// +/// Here is the syntax of an MTrk chunk (the + means "one or more": at least one MTrk event must be +/// present): +/// +/// ` = +` #[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Hash)] pub struct Track { events: Vec, } impl Track { + /// Returns `true` if the track has no events. pub fn is_empty(&self) -> bool { self.events.is_empty() } + /// The number of events in the track. pub fn events_len(&self) -> usize { self.events.len() } @@ -34,12 +46,14 @@ impl Track { self.events.iter() } + /// Add an event to the end. pub fn push_event(&mut self, delta_time: u32, event: Event) -> crate::Result<()> { // TODO check length is not bigger than u32 self.events.push(TrackEvent::new(delta_time, event)); Ok(()) } + /// Add event at `index` and shift everything after it. pub fn insert_event(&mut self, index: u32, delta_time: u32, event: Event) -> crate::Result<()> { // TODO check length is not bigger than u32, index is in range, etc self.events @@ -47,6 +61,7 @@ impl Track { Ok(()) } + /// Replace the event at `index`. pub fn replace_event( &mut self, index: u32, @@ -59,6 +74,7 @@ impl Track { Ok(()) } + /// Add, or replace, the track name at the beginning of a track. pub fn set_name>(&mut self, name: S) -> crate::Result<()> { let name = Text::new(name); let meta = Event::Meta(MetaEvent::TrackName(name.clone())); @@ -80,6 +96,7 @@ impl Track { Ok(()) } + /// Add, or replace, the instrument name at the beginning of a track. pub fn set_instrument_name>(&mut self, name: S) -> crate::Result<()> { let name = Text::new(name); let meta = Event::Meta(MetaEvent::InstrumentName(name.clone())); @@ -101,6 +118,7 @@ impl Track { Ok(()) } + /// Add, or replace, the general midi program at the beginning of a track. pub fn set_general_midi(&mut self, channel: Channel, value: GeneralMidi) -> crate::Result<()> { let program_change = Event::Midi(Message::ProgramChange(ProgramChangeValue { channel, @@ -128,6 +146,7 @@ impl Track { Ok(()) } + /// Add a time signature. pub fn push_time_signature( &mut self, delta_time: u32, @@ -140,6 +159,7 @@ impl Track { self.push_event(delta_time, event) } + /// Add a tempo message. pub fn push_tempo( &mut self, delta_time: u32, @@ -154,6 +174,7 @@ impl Track { self.push_event(delta_time, event) } + /// Add a note on message. pub fn push_note_on( &mut self, delta_time: u32, @@ -170,6 +191,7 @@ impl Track { Ok(()) } + /// Add a note off message. pub fn push_note_off( &mut self, delta_time: u32, @@ -185,11 +207,13 @@ impl Track { self.push_event(delta_time, note_off) } + /// Add a lyric. pub fn push_lyric>(&mut self, delta_time: u32, lyric: S) -> crate::Result<()> { let lyric = Event::Meta(MetaEvent::Lyric(Text::new(lyric))); self.push_event(delta_time, lyric) } + /// Add a pitch bend value. pub fn push_pitch_bend( &mut self, delta_time: u32, diff --git a/src/lib.rs b/src/lib.rs index c4b3286..58ac8eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,16 @@ +/*! + +A library for reading, writing and creating MIDI files. + +!*/ + // one per line to simplify commenting certain ones out during development #![deny(arithmetic_overflow)] #![deny(clippy::complexity)] #![deny(clippy::perf)] #![deny(clippy::style)] -// TODO - maybe document all pub(crate) types -// #![deny(missing_crate_level_docs)] -// TODO - document all -// #![deny(missing_docs)] +#![warn(missing_crate_level_docs)] +#![warn(missing_docs)] #![deny(nonstandard_style)] #![deny(rust_2018_idioms)] #![deny(unreachable_patterns)] @@ -183,6 +187,7 @@ impl MidiFile { self.write(&mut scribe) } + /// The number of tracks, i.e. the length of the vector of tracks. pub fn tracks_len(&self) -> u32 { u32::try_from(self.tracks.len()).unwrap_or(u32::MAX) } @@ -201,6 +206,7 @@ impl MidiFile { self.tracks.get(i) } + /// Add a track to the file. pub fn push_track(&mut self, track: Track) -> Result<()> { ensure!( self.tracks_len() < u32::MAX, @@ -213,6 +219,7 @@ impl MidiFile { Ok(()) } + /// Insert a track at a certain place in the vector of tracks. pub fn insert_track(&mut self, index: u32, track: Track) -> Result<()> { ensure!( self.tracks_len() < u32::MAX, @@ -232,6 +239,7 @@ impl MidiFile { Ok(()) } + /// Remove a track from the file. Same behavior as `vec.remove(index)`. pub fn remove_track(&mut self, index: u32) -> Result { ensure!( index < self.tracks_len(), diff --git a/src/text.rs b/src/text.rs index 0093255..192a0c9 100644 --- a/src/text.rs +++ b/src/text.rs @@ -68,10 +68,12 @@ impl From for String { } impl Text { + /// Create a new `Text` object. pub fn new>(s: S) -> Self { Text::Utf8(s.into()) } + /// Get the exact bytes of the text. pub fn as_bytes(&self) -> &[u8] { match self { Text::Utf8(s) => s.as_bytes(), @@ -79,6 +81,7 @@ impl Text { } } + /// Get a UTF-8 representation of the string (lossy if non UTF-8-encoded). pub fn as_str(&self) -> Cow<'_, str> { match self { Text::Utf8(s) => Cow::Borrowed(s.as_str()),