augmented_midi/
types.rs

1// Augmented Audio: Audio libraries and applications
2// Copyright (c) 2022 Pedro Tacla Yamada
3//
4// The MIT License (MIT)
5//
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to deal
8// in the Software without restriction, including without limitation the rights
9// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10// copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12//
13// The above copyright notice and this permission notice shall be included in
14// all copies or substantial portions of the Software.
15//
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22// THE SOFTWARE.
23use crate::{parse_midi_event, serialize_message, ParserState};
24use std::borrow::Borrow;
25
26/// Type of node-on or note-off contents
27#[derive(Eq, Ord, PartialEq, PartialOrd, Debug, Clone)]
28pub struct MIDIMessageNote {
29    pub channel: u8,
30    pub note: u8,
31    pub velocity: u8,
32}
33
34/// Represents a MIDI message
35#[derive(Eq, Ord, PartialEq, PartialOrd, Debug, Clone)]
36pub enum MIDIMessage<Buffer: Borrow<[u8]>> {
37    NoteOn(MIDIMessageNote),
38    NoteOff(MIDIMessageNote),
39    PolyphonicKeyPressure {
40        channel: u8,
41        note: u8,
42        pressure: u8,
43    },
44    ControlChange {
45        channel: u8,
46        controller_number: u8,
47        value: u8,
48    },
49    ProgramChange {
50        channel: u8,
51        program_number: u8,
52    },
53    ChannelPressure {
54        channel: u8,
55        pressure: u8,
56    },
57    PitchWheelChange {
58        channel: u8,
59        value: u16,
60    },
61    SysExMessage(MIDISysExEvent<Buffer>),
62    SongPositionPointer {
63        beats: u16,
64    },
65    SongSelect {
66        song: u8,
67    },
68    TuneRequest,
69    TimingClock,
70    Start,
71    Continue,
72    Stop,
73    ActiveSensing,
74    Reset,
75    Other {
76        status: u8,
77    },
78}
79
80impl<Buffer: Borrow<[u8]>> MIDIMessage<Buffer> {
81    /// Helper to construct `MIDIMessage::NoteOn`
82    pub fn note_on(channel: u8, note: u8, velocity: u8) -> Self {
83        MIDIMessage::NoteOn(MIDIMessageNote {
84            channel,
85            note,
86            velocity,
87        })
88    }
89
90    /// Helper to construct `MIDIMessage::NoteOff`
91    pub fn note_off(channel: u8, note: u8, velocity: u8) -> Self {
92        MIDIMessage::NoteOff(MIDIMessageNote {
93            channel,
94            note,
95            velocity,
96        })
97    }
98
99    pub fn control_change(channel: u8, controller_number: u8, value: u8) -> Self {
100        MIDIMessage::ControlChange {
101            channel,
102            controller_number,
103            value,
104        }
105    }
106
107    /// This returns the size in bytes of this message when serialised into MIDI.
108    pub fn size_hint(&self) -> usize {
109        match self {
110            MIDIMessage::NoteOn(_) => 3,
111            MIDIMessage::NoteOff(_) => 3,
112            MIDIMessage::PolyphonicKeyPressure { .. } => 3,
113            MIDIMessage::ControlChange { .. } => 3,
114            MIDIMessage::ProgramChange { .. } => 2,
115            MIDIMessage::ChannelPressure { .. } => 2,
116            MIDIMessage::PitchWheelChange { .. } => 3,
117            MIDIMessage::SysExMessage(inner) => 2 + inner.message.borrow().len(),
118            MIDIMessage::SongPositionPointer { .. } => 3,
119            MIDIMessage::SongSelect { .. } => 2,
120            MIDIMessage::TuneRequest => 1,
121            MIDIMessage::TimingClock => 1,
122            MIDIMessage::Start => 1,
123            MIDIMessage::Continue => 1,
124            MIDIMessage::Stop => 1,
125            MIDIMessage::ActiveSensing => 1,
126            MIDIMessage::Reset => 1,
127            MIDIMessage::Other { .. } => 1,
128        }
129    }
130}
131
132impl<'a> TryFrom<&'a [u8]> for MIDIMessage<&'a [u8]> {
133    type Error = nom::Err<nom::error::Error<&'a [u8]>>;
134
135    fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
136        let mut state = ParserState::default();
137        let (_, message) = parse_midi_event(value, &mut state)?;
138        Ok(message)
139    }
140}
141
142impl<B: Borrow<[u8]>> From<MIDIMessage<B>> for Vec<u8> {
143    fn from(msg: MIDIMessage<B>) -> Vec<u8> {
144        let mut output = vec![];
145        let _ = serialize_message(msg, &mut output);
146        output
147    }
148}
149
150pub type Input<'a> = &'a [u8];
151
152/// Describes how the file is organized
153#[derive(Debug, PartialEq, Eq)]
154pub enum MIDIFileFormat {
155    /// The file contains a single multi-channel track
156    ///
157    /// Represented by 0b0_000_0000_0000_0000
158    Single,
159    /// The file contains one or more simultaneous tracks (or MIDI outputs) of a
160    /// sequence
161    ///
162    /// Represented by 0b1_000_0000_0000_0000
163    Simultaneous,
164    /// The file contains one or more sequentially independent single-track patterns
165    ///
166    /// Represented by 0b2_000_0000_0000_0000
167    Sequential,
168    /// An unknown file format was found. Parse will continue if this is found in the header chunk,
169    /// it will possibly fail on other sections of the file.
170    Unknown,
171}
172
173#[derive(Debug, PartialEq, Eq)]
174pub enum MIDIFileDivision {
175    /// The `ticks_per_quarter_note` field contains how many [`MIDITrackEvent::delta_time`] ticks
176    /// make up a quarter-note.
177    ///
178    /// Represented by 0b0_000_0000_0000_0000
179    TicksPerQuarterNote { ticks_per_quarter_note: u16 },
180    /// Indicates [`MIDITrackEvent::delta_time`] are time based SMPTE offsets.
181    ///
182    /// Represented by 0b1_000_0000_0000_0000
183    SMPTE { format: u8, ticks_per_frame: u8 },
184}
185
186/// The header chunk's contents
187#[derive(Debug, PartialEq, Eq)]
188pub struct MIDIFileHeader {
189    /// How the file is organized
190    pub format: MIDIFileFormat,
191    /// The number of tracks in the file
192    pub num_tracks: u16,
193    /// Specifies the meaning of the delta times in events
194    pub division: MIDIFileDivision,
195}
196
197#[derive(Debug)]
198pub enum MIDIFileChunk<StringRepr: Borrow<str>, Buffer: Borrow<[u8]>> {
199    Header(MIDIFileHeader),
200    Track { events: Vec<MIDITrackEvent<Buffer>> },
201    Unknown { name: StringRepr, body: Buffer },
202}
203
204#[derive(Eq, Ord, PartialEq, PartialOrd, Debug, Clone)]
205pub enum MIDITrackInner<Buffer: Borrow<[u8]>> {
206    Message(MIDIMessage<Buffer>),
207    Meta(MIDIMetaEvent<Buffer>),
208}
209
210#[derive(Eq, Ord, PartialEq, PartialOrd, Debug, Clone)]
211pub struct MIDITrackEvent<Buffer: Borrow<[u8]>> {
212    pub delta_time: u32,
213    pub inner: MIDITrackInner<Buffer>,
214}
215
216impl<Buffer: Borrow<[u8]>> MIDITrackEvent<Buffer> {
217    pub fn new(delta_time: u32, event: MIDITrackInner<Buffer>) -> Self {
218        MIDITrackEvent {
219            delta_time,
220            inner: event,
221        }
222    }
223
224    pub fn delta_time(&self) -> u32 {
225        self.delta_time
226    }
227
228    pub fn message(&self) -> Option<&MIDIMessage<Buffer>> {
229        match &self.inner {
230            MIDITrackInner::Message(message) => Some(message),
231            _ => None,
232        }
233    }
234}
235
236#[derive(Eq, Ord, PartialEq, PartialOrd, Debug, Clone)]
237pub struct MIDIMetaEvent<Buffer: Borrow<[u8]>> {
238    pub meta_type: u8,
239    pub length: u32,
240    pub bytes: Buffer,
241}
242
243impl<Buffer: Borrow<[u8]>> MIDIMetaEvent<Buffer> {
244    pub fn new(meta_type: u8, length: u32, bytes: Buffer) -> Self {
245        MIDIMetaEvent {
246            meta_type,
247            length,
248            bytes,
249        }
250    }
251}
252
253#[derive(Eq, Ord, PartialEq, PartialOrd, Debug, Clone)]
254pub struct MIDISysExEvent<Buffer: Borrow<[u8]>> {
255    pub message: Buffer,
256}
257
258impl<Buffer: Borrow<[u8]>> MIDISysExEvent<Buffer> {
259    pub fn new(message: Buffer) -> Self {
260        MIDISysExEvent { message }
261    }
262}
263
264#[derive(Debug)]
265pub struct MIDIFile<StringRepr: Borrow<str>, Buffer: Borrow<[u8]>> {
266    pub chunks: Vec<MIDIFileChunk<StringRepr, Buffer>>,
267}
268
269impl<StringRepr: Borrow<str>, Buffer: Borrow<[u8]>> MIDIFile<StringRepr, Buffer> {
270    pub fn new(chunks: Vec<MIDIFileChunk<StringRepr, Buffer>>) -> Self {
271        Self { chunks }
272    }
273
274    pub fn chunks(&self) -> &Vec<MIDIFileChunk<StringRepr, Buffer>> {
275        &self.chunks
276    }
277
278    pub fn header(&self) -> Option<&MIDIFileHeader> {
279        self.chunks.iter().find_map(|chunk| match chunk {
280            MIDIFileChunk::Header(header) => Some(header),
281            _ => None,
282        })
283    }
284
285    pub fn track_chunks(&self) -> impl Iterator<Item = &MIDITrackEvent<Buffer>> {
286        self.chunks
287            .iter()
288            .filter_map(|chunk| match chunk {
289                MIDIFileChunk::Track { events } => Some(events),
290                _ => None,
291            })
292            .flatten()
293    }
294
295    pub fn ticks_per_quarter_note(&self) -> u16 {
296        if let Some(MIDIFileHeader {
297            division:
298                MIDIFileDivision::TicksPerQuarterNote {
299                    ticks_per_quarter_note,
300                },
301            ..
302        }) = self.header()
303        {
304            *ticks_per_quarter_note
305        } else {
306            0
307        }
308    }
309}