midi_m8_core/
midi_file.rs

1use byteorder::{BigEndian, ByteOrder};
2use midi_msg::*;
3
4// Midi file handling
5#[inline]
6pub fn u32_to_bytes(x: u32) -> [u8; 4] {
7    let mut buf = [0; 4];
8    BigEndian::write_u32(&mut buf, x);
9    buf
10}
11
12#[inline]
13pub fn push_u32(x: u32, v: &mut Vec<u8>) {
14    let [b1, b2, b3, b4] = u32_to_bytes(x);
15    v.push(b1);
16    v.push(b2);
17    v.push(b3);
18    v.push(b4);
19}
20
21#[inline]
22pub fn u16_to_bytes(x: u16) -> [u8; 2] {
23    let mut buf = [0; 2];
24    BigEndian::write_u16(&mut buf, x);
25    buf
26}
27
28#[inline]
29pub fn push_u16(x: u16, v: &mut Vec<u8>) {
30    let [b1, b2] = u16_to_bytes(x);
31    v.push(b1);
32    v.push(b2);
33}
34
35pub fn push_vari(x: u32, v: &mut Vec<u8>) {
36    if x < 0x00000080 {
37        v.push(x as u8 & 0b01111111);
38    } else if x < 0x00004000 {
39        v.push(((x >> 7) as u8 & 0b01111111) + 0b10000000);
40        v.push(x as u8 & 0b01111111);
41    } else if x < 0x00200000 {
42        v.push(((x >> 14) as u8 & 0b01111111) + 0b10000000);
43        v.push(((x >> 7) as u8 & 0b01111111) + 0b10000000);
44        v.push(x as u8 & 0b01111111);
45    } else if x <= 0x0FFFFFFF {
46        v.push(((x >> 21) as u8 & 0b01111111) + 0b10000000);
47        v.push(((x >> 14) as u8 & 0b01111111) + 0b10000000);
48        v.push(((x >> 7) as u8 & 0b01111111) + 0b10000000);
49        v.push(x as u8 & 0b01111111);
50    } else {
51        panic!("Cannot use such a large number as a variable quantity")
52    }
53}
54
55#[derive(Debug, Copy, Clone)]
56#[repr(u16)]
57pub enum MidiFileFormat {
58    SingleTrack = 0,
59    SimultaniousTracks = 1,
60    IndependantTracks = 2,
61}
62
63#[derive(Debug)]
64pub struct MidiFile {
65    pub ticks_per_quarter_note: u16,
66    // TODO support subdivision-of-second delta-times
67    pub format: MidiFileFormat,
68    pub tracks: Vec<MidiFileTrack>,
69}
70
71impl MidiFile {
72    pub fn to_midi(&self) -> Vec<u8> {
73        let mut r: Vec<u8> = vec![];
74        self.extend_midi(&mut r);
75        r
76    }
77
78    pub fn track_to_midi(&self, track_num: usize) -> Vec<u8> {
79        let mut v: Vec<u8> = vec![];
80        v.extend_from_slice(b"MThd");
81        push_u32(6, &mut v); // Length of header, always 6 bytes
82        push_u16(self.format as u16, &mut v);
83        push_u16(1_u16, &mut v); // num tracks
84        if self.ticks_per_quarter_note > 0x7FFF {
85            panic!("Ticks per quarter note must be less than {}", 0x7FFF);
86        }
87        push_u16(self.ticks_per_quarter_note, &mut v);
88        self.tracks[track_num].extend_midi(&mut v);
89
90        v
91    }
92
93    pub fn extend_midi(&self, v: &mut Vec<u8>) {
94        v.extend_from_slice(b"MThd");
95        push_u32(6, v); // Length of header, always 6 bytes
96        push_u16(self.format as u16, v);
97        push_u16(self.tracks.len() as u16, v); // num tracks
98        if self.ticks_per_quarter_note > 0x7FFF {
99            panic!("Ticks per quarter note must be less than {}", 0x7FFF);
100        }
101        push_u16(self.ticks_per_quarter_note, v);
102        for track in self.tracks.iter() {
103            track.extend_midi(v);
104        }
105    }
106}
107
108#[derive(Debug)]
109pub struct MidiFileTrack {
110    /// A vector of tick/Midi event tuples.
111    /// Unlike in the Midi file representation, the ticks in the tuple represent
112    /// absolute time since the start of the track. Events must be in order.
113    pub events: Vec<(u32, MidiMsg)>,
114    /// Name of the track
115    pub name: Option<String>,
116    /// Length of the track in ticks
117    pub n_ticks: u32,
118}
119impl MidiFileTrack {
120    pub fn extend_midi(&self, v: &mut Vec<u8>) {
121        v.extend_from_slice(b"MTrk");
122        let mut events: Vec<u8> = vec![];
123
124        if let Some(name) = &self.name {
125            let len = name.len().min(127);
126            events.extend_from_slice(&[0x00, 0xFF, 0x03, len as u8]);
127            events.extend_from_slice(&name.as_bytes()[..len]);
128        }
129
130        let mut last_tick = 0;
131        for (ticks, msg) in self.events.iter() {
132            push_vari(*ticks - last_tick, &mut events);
133            msg.extend_midi(&mut events);
134            last_tick = *ticks;
135        }
136        push_vari(self.n_ticks - last_tick + 1, &mut events);
137        events.extend_from_slice(&[0xFF, 0x2F, 0x00]);
138        push_u32(events.len() as u32, v);
139        v.extend_from_slice(&events);
140    }
141
142    /// Sort events by tick time
143    pub fn sort_events(&mut self) {
144        self.events.sort_by(|a, b| a.0.cmp(&b.0));
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use crate::midi_file::*;
151
152    #[test]
153    fn test_u32_to_bytes() {
154        let mut v: Vec<u8> = vec![];
155        push_u32(6, &mut v);
156        assert_eq!(v, vec![0, 0, 0, 6]);
157    }
158
159    fn validate_vari(n: u32, bytes: Vec<u8>) {
160        let mut v: Vec<u8> = vec![];
161        push_vari(n, &mut v);
162        assert_eq!(
163            v, bytes,
164            "{:#02X} should have been {:#02X?} not {:#02X?}",
165            n, &bytes, &v
166        );
167    }
168
169    #[test]
170    fn test_push_vari() {
171        validate_vari(6, vec![6]);
172        validate_vari(0x7F, vec![0x7F]);
173        validate_vari(0x80, vec![0x81, 0x00]);
174        validate_vari(0xE89, vec![0x9D, 0x09]);
175        validate_vari(0x3C0, vec![0x87, 0x40]);
176        validate_vari(0x2000, vec![0xC0, 0x00]);
177        validate_vari(0x3FFF, vec![0xFF, 0x7F]);
178        validate_vari(0x4000, vec![0x81, 0x80, 0x00]);
179        validate_vari(0x1FFFFF, vec![0xFF, 0xFF, 0x7F]);
180        validate_vari(0x0FFFFFFF, vec![0xFF, 0xFF, 0xFF, 0x7F]);
181    }
182
183    #[test]
184    fn test_midi_file() {
185        let quarter_note: u32 = 960;
186        let midi_file = MidiFile {
187            format: MidiFileFormat::SimultaniousTracks,
188            ticks_per_quarter_note: quarter_note as u16,
189            tracks: vec![
190                MidiFileTrack {
191                    name: Some("Track 1".to_string()),
192                    events: vec![
193                        (
194                            0,
195                            MidiMsg::ChannelVoice {
196                                channel: Channel::Ch1,
197                                msg: ChannelVoiceMsg::NoteOn {
198                                    note: 72,
199                                    velocity: 100,
200                                },
201                            },
202                        ),
203                        (
204                            quarter_note / 8,
205                            MidiMsg::ChannelVoice {
206                                channel: Channel::Ch1,
207                                msg: ChannelVoiceMsg::NoteOff {
208                                    note: 72,
209                                    velocity: 100,
210                                },
211                            },
212                        ),
213                    ],
214                    n_ticks: quarter_note * 4,
215                },
216                MidiFileTrack {
217                    name: Some("Track 2".to_string()),
218                    events: vec![
219                        (
220                            0,
221                            MidiMsg::ChannelVoice {
222                                channel: Channel::Ch1,
223                                msg: ChannelVoiceMsg::NoteOn {
224                                    note: 72,
225                                    velocity: 100,
226                                },
227                            },
228                        ),
229                        (
230                            quarter_note / 8,
231                            MidiMsg::ChannelVoice {
232                                channel: Channel::Ch1,
233                                msg: ChannelVoiceMsg::NoteOff {
234                                    note: 72,
235                                    velocity: 100,
236                                },
237                            },
238                        ),
239                    ],
240                    n_ticks: quarter_note * 4,
241                },
242            ],
243        }
244        .to_midi();
245
246        assert_eq!(
247            &midi_file,
248            &[
249                0x4d, 0x54, 0x68, 0x64, 0x0, 0x0, 0x0, 0x6, 0x0, 0x1, 0x0, 0x2, 0x3, 0xc0, 0x4d,
250                0x54, 0x72, 0x6b, 0x0, 0x0, 0x0, 0x18, 0x0, 0xff, 0x3, 0x7, 0x54, 0x72, 0x61, 0x63,
251                0x6b, 0x20, 0x31, 0x0, 0x90, 0x48, 0x64, 0x78, 0x80, 0x48, 0x64, 0x9d, 0x9, 0xff,
252                0x2f, 0x0, 0x4d, 0x54, 0x72, 0x6b, 0x0, 0x0, 0x0, 0x18, 0x0, 0xff, 0x3, 0x7, 0x54,
253                0x72, 0x61, 0x63, 0x6b, 0x20, 0x32, 0x0, 0x90, 0x48, 0x64, 0x78, 0x80, 0x48, 0x64,
254                0x9d, 0x9, 0xff, 0x2f, 0x0
255            ]
256        );
257    }
258}