Skip to content

Commit

Permalink
Merge pull request #23 from webern/documentation
Browse files Browse the repository at this point in the history
add documentation
  • Loading branch information
webern authored Sep 15, 2024
2 parents 30cdc86 + bc84254 commit 9dc8306
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 4 deletions.
26 changes: 26 additions & 0 deletions src/core/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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 {
Expand All @@ -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),
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/file/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
}
Expand Down
17 changes: 17 additions & 0 deletions src/file/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
/// `<Header Chunk> = <chunk type><length><format><ntrks><division>`
///
/// As described above, <chunk type> is the four ASCII characters 'MThd'; <length> 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, <format>, specifies the overall organisation of the file.
#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Header {
format: Format,
Expand Down Expand Up @@ -47,6 +58,12 @@ impl Header {
}
}

/// The first word, <format>, specifies the overall organisation of the file. Only three values of
/// `<format>` 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 {
Expand Down
16 changes: 16 additions & 0 deletions src/file/meta_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
30 changes: 30 additions & 0 deletions src/file/sysex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,40 @@ impl SysexEvent {
}
}

/// `<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, 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,
}
24 changes: 24 additions & 0 deletions src/file/track.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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):
///
/// `<Track Chunk> = <chunk type><length><MTrk event>+`
#[derive(Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Track {
events: Vec<TrackEvent>,
}

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()
}
Expand All @@ -34,19 +46,22 @@ 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
.insert(index as usize, TrackEvent::new(delta_time, event));
Ok(())
}

/// Replace the event at `index`.
pub fn replace_event(
&mut self,
index: u32,
Expand All @@ -59,6 +74,7 @@ impl Track {
Ok(())
}

/// Add, or replace, the track name at the beginning of a track.
pub fn set_name<S: Into<String>>(&mut self, name: S) -> crate::Result<()> {
let name = Text::new(name);
let meta = Event::Meta(MetaEvent::TrackName(name.clone()));
Expand All @@ -80,6 +96,7 @@ impl Track {
Ok(())
}

/// Add, or replace, the instrument name at the beginning of a track.
pub fn set_instrument_name<S: Into<String>>(&mut self, name: S) -> crate::Result<()> {
let name = Text::new(name);
let meta = Event::Meta(MetaEvent::InstrumentName(name.clone()));
Expand All @@ -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,
Expand Down Expand Up @@ -128,6 +146,7 @@ impl Track {
Ok(())
}

/// Add a time signature.
pub fn push_time_signature(
&mut self,
delta_time: u32,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -170,6 +191,7 @@ impl Track {
Ok(())
}

/// Add a note off message.
pub fn push_note_off(
&mut self,
delta_time: u32,
Expand All @@ -185,11 +207,13 @@ impl Track {
self.push_event(delta_time, note_off)
}

/// Add a lyric.
pub fn push_lyric<S: Into<String>>(&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,
Expand Down
16 changes: 12 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down Expand Up @@ -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)
}
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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<Track> {
ensure!(
index < self.tracks_len(),
Expand Down
3 changes: 3 additions & 0 deletions src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,17 +68,20 @@ impl From<Text> for String {
}

impl Text {
/// Create a new `Text` object.
pub fn new<S: Into<String>>(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(),
Text::Other(b) => b.as_slice(),
}
}

/// 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()),
Expand Down

0 comments on commit 9dc8306

Please sign in to comment.