midi_msg/
file.rs

1use alloc::fmt;
2use alloc::format;
3use alloc::string::{String, ToString};
4use alloc::vec;
5use alloc::vec::Vec;
6use core::ops;
7use core::str;
8
9#[cfg(not(feature = "std"))]
10#[allow(unused_imports)] // This will be unused unless on an embedded target
11use micromath::F32Ext;
12
13use core::error;
14
15use super::{
16    Channel, HighResTimeCode, MidiMsg, ParseError, ReceiverContext, SystemExclusiveMsg,
17    TimeCodeType, util::*,
18};
19
20// Standard Midi File 1.0 (SMF): RP-001 support
21
22/// Errors that can occur when parsing a [`MidiFile`]
23#[derive(Debug, PartialEq)]
24pub struct MidiFileParseError {
25    pub error: ParseError,
26    pub file: MidiFile,
27    pub offset: usize,
28    pub parsing: String,
29    pub remaining_bytes: usize,
30    pub next_bytes: Vec<u8>,
31}
32
33impl error::Error for MidiFileParseError {}
34
35impl fmt::Display for MidiFileParseError {
36    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
37        write!(
38            f,
39            "Error parsing MIDI file at position {}: {}",
40            &self.offset, &self.error
41        )?;
42        write!(
43            f,
44            "\nEncountered this error while parsing: {}",
45            &self.parsing
46        )?;
47        write!(
48            f,
49            "\nThe incomplete MidiFile that managed to be parsed: {:?}",
50            &self.file
51        )?;
52        write!(
53            f,
54            "\n\n{} bytes remain in the file. These are the next ones: {:x?}",
55            &self.remaining_bytes, &self.next_bytes
56        )?;
57
58        Ok(())
59    }
60}
61
62/// A Standard Midi File (SMF)
63#[derive(Debug, Clone, PartialEq, Default)]
64pub struct MidiFile {
65    /// The header chunk: Contains the file format, number of tracks, and division
66    pub header: Header,
67    /// The track chunks: Contains the actual midi events
68    pub tracks: Vec<Track>,
69}
70
71#[derive(Debug)]
72struct ParseCtx<'a, 'b> {
73    input: &'a [u8],
74    offset: usize,
75    parsing: String,
76    file: &'b mut MidiFile,
77    track_end: usize,
78}
79
80impl<'a, 'b> ParseCtx<'a, 'b> {
81    fn new(input: &'a [u8], file: &'b mut MidiFile) -> Self {
82        Self {
83            input,
84            offset: 0,
85            parsing: "header".into(),
86            file,
87            track_end: 0,
88        }
89    }
90
91    fn advance(&mut self, offset: usize) {
92        self.offset += offset;
93    }
94
95    fn data(&self) -> &[u8] {
96        &self.input[self.offset..]
97    }
98
99    fn slice(&self, range: ops::Range<usize>) -> &[u8] {
100        &self.input[range.start + self.offset..range.end + self.offset]
101    }
102
103    fn remaining(&self) -> usize {
104        self.input.len() - self.offset
105    }
106
107    fn parsing<S: Into<String>>(&mut self, s: S) {
108        self.parsing = s.into();
109    }
110
111    fn add_track(&mut self, track: Track) {
112        self.file.tracks.push(track);
113    }
114
115    fn track_length(&mut self, len: usize) {
116        self.track_end = self.offset + len;
117    }
118
119    fn extend_track(&mut self, event: TrackEvent) {
120        self.file.tracks.last_mut().unwrap().extend(event);
121    }
122}
123
124impl MidiFile {
125    /// Turn a series of bytes into a `MidiFile`.
126    pub fn from_midi(v: &[u8]) -> Result<Self, MidiFileParseError> {
127        let mut file = MidiFile {
128            header: Header::default(),
129            tracks: vec![],
130        };
131        let mut ctx = ParseCtx::new(v, &mut file);
132        match Header::parse_midi_file(&mut ctx) {
133            Ok(_) => (),
134            Err(error) => {
135                let offset = ctx.offset;
136                let parsing = ctx.parsing.clone();
137                let remaining_bytes = ctx.remaining();
138                let next_bytes = ctx.slice(0..(20.min(ctx.remaining()))).to_vec();
139                return Err(MidiFileParseError {
140                    error,
141                    file,
142                    offset,
143                    parsing,
144                    remaining_bytes,
145                    next_bytes,
146                });
147            }
148        }
149
150        for i in 0..ctx.file.header.num_tracks {
151            match Track::parse_midi_file(&mut ctx, i) {
152                Ok(_) => (),
153                Err(error) => {
154                    let offset = ctx.offset;
155                    let parsing = ctx.parsing.clone();
156                    let remaining_bytes = ctx.remaining();
157                    let next_bytes = ctx.slice(0..(20.min(ctx.remaining()))).to_vec();
158                    return Err(MidiFileParseError {
159                        error,
160                        file,
161                        offset,
162                        parsing,
163                        remaining_bytes,
164                        next_bytes,
165                    });
166                }
167            }
168        }
169        Ok(file)
170    }
171
172    /// Turn a `MidiFile` into a series of bytes.
173    pub fn to_midi(&self) -> Vec<u8> {
174        let mut r: Vec<u8> = vec![];
175        self.header.extend_midi(&mut r);
176        for track in &self.tracks {
177            track.extend_midi(&mut r);
178        }
179        r
180    }
181
182    /// Add a track to the file. Increments the `num_tracks` field in the header.
183    pub fn add_track(&mut self, track: Track) {
184        self.tracks.push(track);
185        self.header.num_tracks += 1;
186    }
187
188    /// Remove a track from the file. Decrements the `num_tracks` field in the header.
189    pub fn remove_track(&mut self, track_num: usize) {
190        self.tracks.remove(track_num);
191        self.header.num_tracks -= 1;
192    }
193
194    /// Add a midi event to a track in the file, given its absolute beat or frame time. The event delta time is calculated from the previous event in the track and the time division of the file.
195    pub fn extend_track(&mut self, track_num: usize, event: MidiMsg, beat_or_frame: f32) {
196        match &mut self.tracks[track_num] {
197            Track::Midi(events) => {
198                let last_beat_or_frame = events.last().map(|e| e.beat_or_frame).unwrap_or(0.0);
199                let last_event_tick = self
200                    .header
201                    .division
202                    .beat_or_frame_to_tick(last_beat_or_frame);
203                let this_event_tick = self.header.division.beat_or_frame_to_tick(beat_or_frame);
204                events.push(TrackEvent {
205                    delta_time: this_event_tick - last_event_tick,
206                    event,
207                    beat_or_frame,
208                })
209            }
210
211            Track::AlienChunk(_) => panic!("Cannot extend an alien chunk"),
212        }
213    }
214}
215
216/// The header chunk of a Standard Midi File
217#[derive(Debug, Clone, PartialEq, Default)]
218pub struct Header {
219    /// The format of the file
220    pub format: SMFFormat,
221    /// How many tracks are in the file
222    pub num_tracks: u16,
223    /// The "division" of the file, which specifies the meaning of the delta times in the file
224    pub division: Division,
225}
226
227impl Header {
228    // We pass the file to the from_midi function, so that we can have a running context of what's been parsed so far.
229    fn parse_midi_file(ctx: &mut ParseCtx) -> Result<(), ParseError> {
230        if ctx.remaining() < 14 {
231            return Err(ParseError::UnexpectedEnd);
232        }
233        if str::from_utf8(ctx.slice(0..4)) != Ok("MThd") {
234            return Err(ParseError::Invalid("Invalid header"));
235        }
236        ctx.advance(4);
237        if u32_from_midi(ctx.slice(0..4)) != Ok(6) {
238            return Err(ParseError::Invalid("Invalid header length"));
239        }
240        ctx.advance(4);
241        let v = ctx.data();
242
243        let (format, _) = SMFFormat::from_midi(v)?;
244        let num_tracks = u16::from_be_bytes([v[2], v[3]]);
245        let division = if v[4] & 0b1000_0000 == 0 {
246            Division::TicksPerQuarterNote(u16::from_be_bytes([v[4], v[5]]))
247        } else {
248            Division::TimeCode {
249                frames_per_second: match v[4] & 0b0111_1111 {
250                    0 => TimeCodeType::FPS24,
251                    1 => TimeCodeType::FPS25,
252                    2 => TimeCodeType::DF30,
253                    3 => TimeCodeType::NDF30,
254                    _ => return Err(ParseError::Invalid("Invalid time code type")),
255                },
256                ticks_per_frame: v[5],
257            }
258        };
259        ctx.advance(6);
260        ctx.file.header = Self {
261            format,
262            num_tracks,
263            division,
264        };
265        Ok(())
266    }
267
268    fn extend_midi(&self, v: &mut Vec<u8>) {
269        v.extend_from_slice(b"MThd");
270        push_u32(6, v); // Length of header, always 6 bytes
271
272        self.format.extend_midi(v);
273        push_u16(self.num_tracks, v);
274        match self.division {
275            Division::TicksPerQuarterNote(tpqn) => {
276                push_u16(tpqn, v);
277            }
278            Division::TimeCode {
279                frames_per_second,
280                ticks_per_frame,
281            } => {
282                v.push(0b1000_0000 | frames_per_second as u8);
283                v.push(ticks_per_frame);
284            }
285        }
286    }
287}
288
289/// The format of a Standard Midi File
290#[derive(Debug, Clone, PartialEq, Default)]
291pub enum SMFFormat {
292    /// A single track file
293    SingleTrack,
294    /// The file contains multiple tracks, but they are all meant to be played simultaneously
295    #[default]
296    MultiTrack,
297    /// The file contains multiple tracks, but they are independent of each other
298    MultiSong,
299}
300
301impl SMFFormat {
302    fn from_midi(v: &[u8]) -> Result<(Self, usize), ParseError> {
303        if v.len() < 2 {
304            return Err(ParseError::UnexpectedEnd);
305        }
306        Ok((
307            // Big endian 16 bit value: Only the LSB is used
308            match v[1] {
309                0 => SMFFormat::SingleTrack,
310                1 => SMFFormat::MultiTrack,
311                2 => SMFFormat::MultiSong,
312                _ => return Err(ParseError::Invalid("Invalid SMF format")),
313            },
314            2,
315        ))
316    }
317
318    fn extend_midi(&self, v: &mut Vec<u8>) {
319        match self {
320            SMFFormat::SingleTrack => v.extend_from_slice(&[0, 0]),
321            SMFFormat::MultiTrack => v.extend_from_slice(&[0, 1]),
322            SMFFormat::MultiSong => v.extend_from_slice(&[0, 2]),
323        }
324    }
325}
326
327/// The division of a Standard Midi File, which specifies the meaning of the delta times in the file
328#[derive(Debug, Clone, Copy, PartialEq)]
329pub enum Division {
330    /// Metrical time. Number of "ticks" per quarter note.
331    TicksPerQuarterNote(u16),
332    /// Time code based time.
333    TimeCode {
334        frames_per_second: TimeCodeType,
335        ticks_per_frame: u8,
336    },
337}
338
339impl Default for Division {
340    fn default() -> Self {
341        Division::TicksPerQuarterNote(96)
342    }
343}
344
345impl Division {
346    /// Convert a beat or frame to the number of "ticks" in a file with this division.
347    pub fn beat_or_frame_to_tick(&self, beat_or_frame: f32) -> u32 {
348        match self {
349            Division::TicksPerQuarterNote(tpqn) => (beat_or_frame * *tpqn as f32) as u32,
350            Division::TimeCode {
351                ticks_per_frame, ..
352            } => (beat_or_frame * *ticks_per_frame as f32) as u32,
353        }
354    }
355
356    /// Convert a number of file "ticks" to a beat or frame in a file with this division.
357    pub fn ticks_to_beats_or_frames(&self, ticks: u32) -> f32 {
358        match self {
359            Division::TicksPerQuarterNote(tpqn) => ticks as f32 / *tpqn as f32,
360            Division::TimeCode {
361                ticks_per_frame, ..
362            } => ticks as f32 / *ticks_per_frame as f32,
363        }
364    }
365}
366
367/// A track in a Standard Midi File
368#[derive(Debug, Clone, PartialEq)]
369pub enum Track {
370    /// A standard "MTrk" chunk
371    Midi(Vec<TrackEvent>),
372    /// Any other chunk data.
373    ///
374    /// This includes the entire chuck data, include whatever chunk type and length.
375    AlienChunk(Vec<u8>),
376}
377
378impl Default for Track {
379    fn default() -> Self {
380        Track::Midi(vec![])
381    }
382}
383
384impl Track {
385    /// Get the number of events in the track, or the length in bytes of an `AlienChunk`.
386    pub fn len(&self) -> usize {
387        match self {
388            Track::Midi(events) => events.len(),
389            Track::AlienChunk(data) => data.len(),
390        }
391    }
392
393    #[must_use]
394    pub fn is_empty(&self) -> bool {
395        self.len() == 0
396    }
397
398    /// Get the [`TrackEvent`] events in the track. Will be empty for an `AlienChunk`.
399    pub fn events(&self) -> &[TrackEvent] {
400        match self {
401            Track::Midi(events) => events,
402            Track::AlienChunk(_) => &[],
403        }
404    }
405
406    fn extend(&mut self, event: TrackEvent) {
407        match self {
408            Track::Midi(events) => events.push(event),
409            Track::AlienChunk(_) => panic!("Cannot extend an alien chunk"),
410        }
411    }
412
413    fn parse_midi_file(ctx: &mut ParseCtx, track_num: u16) -> Result<(), ParseError> {
414        if ctx.remaining() < 8 {
415            return Err(ParseError::UnexpectedEnd);
416        }
417        ctx.parsing(format!("track {}", track_num));
418        let len = u32_from_midi(ctx.slice(4..8))? as usize;
419        if ctx.remaining() < len + 8 {
420            return Err(ParseError::UnexpectedEnd);
421        }
422        if str::from_utf8(ctx.slice(0..4)) != Ok("MTrk") {
423            ctx.add_track(Self::AlienChunk(ctx.slice(0..len + 8).to_vec()));
424            ctx.advance(len + 8);
425            return Ok(());
426        }
427        ctx.add_track(Self::Midi(vec![]));
428        ctx.advance(8);
429        ctx.track_length(len);
430        let reciever_ctx = &mut ReceiverContext::default().parsing_smf();
431
432        let mut i = 0;
433        let mut last_beat_or_frame = 0.0;
434        while ctx.offset < ctx.track_end {
435            ctx.parsing(format!("track {} event {}", track_num, i));
436            let (event, event_len) = TrackEvent::from_midi(
437                ctx.data(),
438                reciever_ctx,
439                &ctx.file.header.division,
440                last_beat_or_frame,
441            )?;
442            last_beat_or_frame = event.beat_or_frame;
443            ctx.extend_track(event);
444            ctx.advance(event_len);
445            i += 1;
446        }
447        if ctx.offset > ctx.track_end {
448            return Err(ParseError::Invalid(
449                "Track length exceeded the provided length",
450            ));
451        }
452        Ok(())
453    }
454
455    fn extend_midi(&self, v: &mut Vec<u8>) {
456        match self {
457            Track::Midi(events) => {
458                v.extend_from_slice(b"MTrk");
459                let s = v.len();
460                push_u32(0, v); // We will fill this in after we know the length
461
462                for event in events {
463                    event.extend_midi(v);
464                }
465                let e = v.len();
466                // Fill in the length
467                v[s..s + 4].copy_from_slice(&(e as u32 - s as u32 - 4).to_be_bytes());
468            }
469            Track::AlienChunk(data) => {
470                v.extend_from_slice(data);
471            }
472        }
473    }
474}
475
476/// An event occurring in a track in a Standard Midi File
477#[derive(Debug, Clone, PartialEq)]
478pub struct TrackEvent {
479    /// The time since the last event. The meaning of this value is determined by the file header's [`Division`].
480    pub delta_time: u32,
481    /// The actual midi event.
482    pub event: MidiMsg,
483    /// Given the file's [`Division`], the time in beats or frames at which this event occurs.
484    ///
485    /// When deserializing, this is derived from the `delta_time` and the previous event's `beat_or_frame`.
486    ///
487    /// When manually constructing `TrackEvent`s (i.e. when not using the [`MidiFile::extend_track`] convenience function), this field can set to any value, as it is not used when serializing the file.
488    pub beat_or_frame: f32,
489}
490
491impl TrackEvent {
492    fn from_midi(
493        v: &[u8],
494        ctx: &mut ReceiverContext,
495        division: &Division,
496        last_beat_or_frame: f32,
497    ) -> Result<(Self, usize), ParseError> {
498        let (delta_time, time_offset) = read_vlq(v)?;
499        let beat_or_frame = last_beat_or_frame + division.ticks_to_beats_or_frames(delta_time);
500        match v[time_offset..].first() {
501            Some(b) => match b >> 4 {
502                0xF => match b & 0b0000_1111 {
503                    0x0 => {
504                        let (len, len_offset) = read_vlq(&v[time_offset + 1..])?;
505                        let p = time_offset + len_offset + 1;
506                        ctx.is_smf_sysex = true;
507                        let event = match SystemExclusiveMsg::from_midi(&v[p..], ctx) {
508                            Ok((event, event_len)) => {
509                                // len is not the length of the entire message, since we don't have the status byte
510                                if event_len - 1 != len as usize {
511                                    return Err(ParseError::Invalid(
512                                        "Invalid system exclusive message",
513                                    ));
514                                }
515                                MidiMsg::SystemExclusive { msg: event }
516                            }
517                            Err(e) => MidiMsg::Invalid {
518                                bytes: v[p..p + len as usize].to_vec(),
519                                error: e,
520                            },
521                        };
522                        Ok((
523                            Self {
524                                delta_time,
525                                event,
526                                beat_or_frame,
527                            },
528                            p + len as usize,
529                        ))
530                    }
531                    0x7 => {
532                        let (len, len_offset) = read_vlq(&v[time_offset + 1..])?;
533                        let p = time_offset + len_offset + 1;
534                        ctx.is_smf_sysex = false;
535                        let event = match MidiMsg::from_midi_with_context(&v[p..], ctx) {
536                            Ok((event, event_len)) => {
537                                // len _is_ the length of the entire message
538                                if event_len != len as usize {
539                                    return Err(ParseError::Invalid(
540                                        "Invalid system exclusive message",
541                                    ));
542                                }
543                                event
544                            }
545                            Err(e) => MidiMsg::Invalid {
546                                bytes: v[p..p + len as usize].to_vec(),
547                                error: e,
548                            },
549                        };
550
551                        Ok((
552                            Self {
553                                delta_time,
554                                event,
555                                beat_or_frame,
556                            },
557                            p + len as usize,
558                        ))
559                    }
560                    0xF => {
561                        let p = time_offset + 1;
562                        let (event, event_len) = Meta::from_midi(&v[p..])?;
563                        Ok((
564                            Self {
565                                delta_time,
566                                event: MidiMsg::Meta { msg: event },
567                                beat_or_frame,
568                            },
569                            p + event_len,
570                        ))
571                    }
572                    _ => Err(ParseError::Invalid("Invalid track event")),
573                },
574                _ => {
575                    ctx.is_smf_sysex = false;
576                    let (event, event_len) =
577                        MidiMsg::from_midi_with_context(&v[time_offset..], ctx)?;
578                    Ok((
579                        Self {
580                            delta_time,
581                            event,
582                            beat_or_frame,
583                        },
584                        time_offset + event_len,
585                    ))
586                }
587            },
588            None => Err(ParseError::UnexpectedEnd),
589        }
590    }
591
592    fn extend_midi(&self, v: &mut Vec<u8>) {
593        if matches!(
594            self.event,
595            MidiMsg::SystemRealTime {
596                msg: crate::SystemRealTimeMsg::SystemReset,
597            }
598        ) {
599            #[cfg(feature = "std")]
600            log::warn!("SMF contains System Reset event, which is not valid. Skipping.");
601            return;
602        } else if self.event.is_invalid() {
603            return;
604        }
605
606        push_vlq(self.delta_time, v);
607        // TODO this doesn't handle running-status events
608        let event = self.event.to_midi();
609
610        let is_meta = matches!(self.event, MidiMsg::Meta { .. });
611        // Any kind of system event
612        let is_system = matches!(
613            self.event,
614            MidiMsg::SystemExclusive { .. }
615                | MidiMsg::SystemCommon { .. }
616                | MidiMsg::SystemRealTime { .. }
617        );
618        if is_meta {
619            v.push(0xFF);
620        } else if is_system {
621            // We always use the 0xF7 format for system events, since it can be used for all system events, not just system exclusive
622            v.push(0xF7);
623            push_vlq(event.len() as u32, v);
624        }
625        v.extend_from_slice(&event);
626    }
627}
628
629/// A meta event in a Standard Midi File
630#[derive(Debug, Clone, PartialEq)]
631pub enum Meta {
632    /// Must occur at the start of a track, and specifies the sequence number of the track. In a MultiSong file, this is the "pattern" number that identifies the song for cueing purposes.
633    SequenceNumber(u16),
634    /// Any text, describing anything
635    Text(String),
636    /// A copyright notice
637    Copyright(String),
638    /// The name of the track
639    TrackName(String),
640    /// The name of the instrument used in the track
641    InstrumentName(String),
642    /// A lyric. See RP-017 for guidance on the use of this meta event.
643    Lyric(String),
644    /// Normally only used in a SingleTrack file, or the first track of a MultiTrack file. Used to mark significant points in the music.
645    Marker(String),
646    /// A description of something happening at a point in time
647    CuePoint(String),
648    /// The MIDI channel that the following track events are intended for. Effective until the next event that specifies a channel.
649    ChannelPrefix(Channel),
650    /// Marks the end of a track. This event is not optional. It must be the last event in every track.
651    EndOfTrack,
652    /// The tempo in microseconds per quarter note.
653    SetTempo(u32),
654    /// If present, the time at which the track is supposed to start. Should be present at the start of the track.
655    SmpteOffset(HighResTimeCode),
656    /// A time signature.
657    TimeSignature(FileTimeSignature),
658    /// A key signature.
659    KeySignature(KeySignature),
660    /// A chunk of data that is specific to the sequencer that created the file.
661    SequencerSpecific(Vec<u8>),
662    // TODO: RP-32
663    // TODO: RP-19
664    /// Any other meta event that is not recognized
665    Unknown { meta_type: u8, data: Vec<u8> },
666}
667
668impl Meta {
669    // We do not extend with 0xFF, as this is done in TrackEvent::extend_midi
670    pub(crate) fn from_midi(v: &[u8]) -> Result<(Self, usize), ParseError> {
671        if v.len() < 2 {
672            return Err(ParseError::UnexpectedEnd);
673        }
674        let meta_type = v[0];
675        let (len, len_offset) = read_vlq(&v[1..])?;
676        if v.len() < len as usize + len_offset + 1 {
677            return Err(ParseError::UnexpectedEnd);
678        }
679        let end = len as usize + len_offset + 1;
680        let data = &v[len_offset + 1..end];
681        match meta_type {
682            0x00 => {
683                if data.len() != 2 {
684                    return Err(ParseError::Invalid(
685                        "Sequence number meta event must have exactly 2 bytes",
686                    ));
687                }
688                Ok((
689                    Self::SequenceNumber(u16::from_be_bytes([data[0], data[1]])),
690                    end,
691                ))
692            }
693            0x01 => Ok((Self::Text(String::from_utf8_lossy(data).to_string()), end)),
694            0x02 => Ok((
695                Self::Copyright(String::from_utf8_lossy(data).to_string()),
696                end,
697            )),
698            0x03 => Ok((
699                Self::TrackName(String::from_utf8_lossy(data).to_string()),
700                end,
701            )),
702            0x04 => Ok((
703                Self::InstrumentName(String::from_utf8_lossy(data).to_string()),
704                end,
705            )),
706            0x05 => Ok((Self::Lyric(String::from_utf8_lossy(data).to_string()), end)),
707            0x06 => Ok((Self::Marker(String::from_utf8_lossy(data).to_string()), end)),
708            0x07 => Ok((
709                Self::CuePoint(String::from_utf8_lossy(data).to_string()),
710                end,
711            )),
712            0x20 => {
713                if data.len() != 1 {
714                    return Err(ParseError::Invalid(
715                        "ChannelPrefix meta event must have exactly 1 byte",
716                    ));
717                }
718                Ok((Self::ChannelPrefix(Channel::from_u8(data[0])), end))
719            }
720            0x2F => Ok((Self::EndOfTrack, end)),
721            0x51 => {
722                if data.len() != 3 {
723                    return Err(ParseError::Invalid(
724                        "SetTempo meta event must have exactly 3 bytes",
725                    ));
726                }
727                Ok((
728                    Self::SetTempo(u32::from_be_bytes([0, data[0], data[1], data[2]])),
729                    end,
730                ))
731            }
732            0x54 => {
733                if data.len() != 5 {
734                    return Err(ParseError::Invalid(
735                        "SmpteOffset meta event must have exactly 5 bytes",
736                    ));
737                }
738                let (time, _) = HighResTimeCode::from_midi(data)?;
739                Ok((Self::SmpteOffset(time), end))
740            }
741            0x58 => {
742                if data.len() != 4 {
743                    return Err(ParseError::Invalid(
744                        "TimeSignature meta event must have exactly 4 bytes",
745                    ));
746                }
747                Ok((
748                    Self::TimeSignature(FileTimeSignature::from_midi(data)?),
749                    end,
750                ))
751            }
752            0x59 => {
753                if data.len() != 2 {
754                    return Err(ParseError::Invalid(
755                        "KeySignature meta event must have exactly 2 bytes",
756                    ));
757                }
758                Ok((Self::KeySignature(KeySignature::from_midi(data)?), end))
759            }
760            0x7F => Ok((Self::SequencerSpecific(data.to_vec()), end)),
761            _ => Ok((
762                Self::Unknown {
763                    meta_type,
764                    data: data.to_vec(),
765                },
766                end,
767            )),
768        }
769    }
770
771    pub(crate) fn extend_midi(&self, v: &mut Vec<u8>) {
772        match self {
773            Meta::SequenceNumber(n) => {
774                v.push(0x00);
775                push_vlq(2, v);
776                v.extend_from_slice(&n.to_be_bytes());
777            }
778            Meta::Text(s) => {
779                v.push(0x01);
780                let bytes = s.as_bytes();
781                push_vlq(bytes.len() as u32, v);
782                v.extend_from_slice(bytes);
783            }
784            Meta::Copyright(s) => {
785                v.push(0x02);
786                let bytes = s.as_bytes();
787                push_vlq(bytes.len() as u32, v);
788                v.extend_from_slice(bytes);
789            }
790            Meta::TrackName(s) => {
791                v.push(0x03);
792                let bytes = s.as_bytes();
793                push_vlq(bytes.len() as u32, v);
794                v.extend_from_slice(bytes);
795            }
796            Meta::InstrumentName(s) => {
797                v.push(0x04);
798                let bytes = s.as_bytes();
799                push_vlq(bytes.len() as u32, v);
800                v.extend_from_slice(bytes);
801            }
802            Meta::Lyric(s) => {
803                v.push(0x05);
804                let bytes = s.as_bytes();
805                push_vlq(bytes.len() as u32, v);
806                v.extend_from_slice(bytes);
807            }
808            Meta::Marker(s) => {
809                v.push(0x06);
810                let bytes = s.as_bytes();
811                push_vlq(bytes.len() as u32, v);
812                v.extend_from_slice(bytes);
813            }
814            Meta::CuePoint(s) => {
815                v.push(0x07);
816                let bytes = s.as_bytes();
817                push_vlq(bytes.len() as u32, v);
818                v.extend_from_slice(bytes);
819            }
820            Meta::ChannelPrefix(n) => {
821                v.push(0x20);
822                push_vlq(1, v);
823                v.push(*n as u8);
824            }
825            Meta::EndOfTrack => {
826                v.push(0x2F);
827                push_vlq(0, v);
828            }
829            Meta::SetTempo(n) => {
830                v.push(0x51);
831                push_vlq(3, v);
832                v.extend_from_slice(&n.to_be_bytes()[1..]);
833            }
834            Meta::SmpteOffset(t) => {
835                v.push(0x54);
836                push_vlq(5, v);
837                t.extend_midi(v);
838            }
839            Meta::TimeSignature(s) => {
840                v.push(0x58);
841                push_vlq(4, v);
842                s.extend_midi(v);
843            }
844            Meta::KeySignature(k) => {
845                v.push(0x59);
846                push_vlq(2, v);
847                k.extend_midi(v);
848            }
849            Meta::SequencerSpecific(d) => {
850                v.push(0x7F);
851                push_vlq(d.len() as u32, v);
852                v.extend_from_slice(d);
853            }
854            Meta::Unknown { meta_type, data } => {
855                v.push(*meta_type);
856                push_vlq(data.len() as u32, v);
857                v.extend_from_slice(data);
858            }
859        }
860    }
861}
862
863/// A time signature occurring in a Standard Midi File.
864#[derive(Debug, Clone, PartialEq)]
865pub struct FileTimeSignature {
866    /// The numerator of the time signature, as it would be notated.
867    pub numerator: u8,
868    /// The denominator of the time signature, as it would be notated.
869    ///
870    /// This is tranformed from the power-of-two representation used in the file.
871    pub denominator: u16,
872    /// The number of MIDI clocks per metronome tick.
873    pub clocks_per_metronome_tick: u8,
874    /// How many 32nd notes are in a MIDI quarter note, which should usually be 8.
875    pub thirty_second_notes_per_24_clocks: u8,
876}
877
878impl FileTimeSignature {
879    pub(crate) fn from_midi(m: &[u8]) -> Result<Self, ParseError> {
880        if m.len() < 4 {
881            return Err(ParseError::UnexpectedEnd);
882        }
883        Ok(Self {
884            numerator: m[0],
885            denominator: u16::pow(2, m[1] as u32),
886            clocks_per_metronome_tick: m[2],
887            thirty_second_notes_per_24_clocks: m[3],
888        })
889    }
890
891    pub(crate) fn extend_midi(&self, v: &mut Vec<u8>) {
892        v.push(self.numerator);
893        v.push((self.denominator as f32).log2() as u8);
894        v.push(self.clocks_per_metronome_tick);
895        v.push(self.thirty_second_notes_per_24_clocks);
896    }
897}
898
899/// A key signature occurring in a Standard Midi File.
900#[derive(Debug, Clone, PartialEq)]
901pub struct KeySignature {
902    /// Negative for number of flats, positive for number of sharps
903    pub key: i8,
904    /// 0 for major, 1 for minor
905    pub scale: u8,
906}
907
908impl KeySignature {
909    pub(crate) fn from_midi(m: &[u8]) -> Result<Self, ParseError> {
910        if m.len() < 2 {
911            return Err(ParseError::UnexpectedEnd);
912        }
913        Ok(Self {
914            key: m[0] as i8,
915            scale: m[1],
916        })
917    }
918
919    pub(crate) fn extend_midi(&self, v: &mut Vec<u8>) {
920        v.push(self.key as u8);
921        v.push(self.scale);
922    }
923}
924
925#[cfg(test)]
926mod tests {
927    use super::*;
928
929    #[test]
930    fn test_file_time_signature() {
931        let midi_data = vec![4, 2, 24, 8];
932        let time_sig = FileTimeSignature::from_midi(&midi_data).unwrap();
933
934        assert_eq!(time_sig.numerator, 4);
935        assert_eq!(time_sig.denominator, 4);
936        assert_eq!(time_sig.clocks_per_metronome_tick, 24);
937        assert_eq!(time_sig.thirty_second_notes_per_24_clocks, 8);
938
939        let mut output = Vec::new();
940        time_sig.extend_midi(&mut output);
941        assert_eq!(output, midi_data);
942    }
943
944    #[test]
945    fn test_file_time_signature_error() {
946        let midi_data = vec![4, 2, 24];
947        assert!(matches!(
948            FileTimeSignature::from_midi(&midi_data),
949            Err(ParseError::UnexpectedEnd)
950        ));
951    }
952
953    #[test]
954    fn test_key_signature() {
955        let midi_data = vec![2, 0];
956        let key_sig = KeySignature::from_midi(&midi_data).unwrap();
957
958        assert_eq!(key_sig.key, 2);
959        assert_eq!(key_sig.scale, 0);
960
961        let mut output = Vec::new();
962        key_sig.extend_midi(&mut output);
963        assert_eq!(output, midi_data);
964    }
965
966    #[test]
967    fn test_key_signature_error() {
968        let midi_data = vec![2];
969        assert!(matches!(
970            KeySignature::from_midi(&midi_data),
971            Err(ParseError::UnexpectedEnd)
972        ));
973    }
974
975    #[test]
976    fn test_file_serde() {
977        use crate::Channel;
978        use crate::ChannelVoiceMsg;
979        use crate::message::MidiMsg;
980
981        // Create a simple MIDI file
982        let mut file = MidiFile::default();
983        // Set the division
984        file.header.division = Division::TicksPerQuarterNote(480);
985
986        file.add_track(Track::default());
987
988        // Add some events to the track
989        file.extend_track(
990            0,
991            MidiMsg::Meta {
992                msg: Meta::TrackName("Test Track".to_string()),
993            },
994            0.0,
995        );
996
997        file.extend_track(
998            0,
999            MidiMsg::Meta {
1000                msg: Meta::TimeSignature(FileTimeSignature {
1001                    numerator: 4,
1002                    denominator: 4,
1003                    clocks_per_metronome_tick: 24,
1004                    thirty_second_notes_per_24_clocks: 8,
1005                }),
1006            },
1007            0.0,
1008        );
1009
1010        file.extend_track(
1011            0,
1012            MidiMsg::ChannelVoice {
1013                channel: Channel::Ch1,
1014                msg: ChannelVoiceMsg::NoteOn {
1015                    note: 60,
1016                    velocity: 64,
1017                },
1018            },
1019            0.0,
1020        );
1021
1022        file.extend_track(
1023            0,
1024            MidiMsg::ChannelVoice {
1025                channel: Channel::Ch1,
1026                msg: ChannelVoiceMsg::NoteOff {
1027                    note: 60,
1028                    velocity: 64,
1029                },
1030            },
1031            1.0,
1032        );
1033        file.extend_track(
1034            0,
1035            MidiMsg::Meta {
1036                msg: Meta::EndOfTrack,
1037            },
1038            2.0,
1039        );
1040
1041        // Convert the file to bytes
1042        let bytes = file.to_midi();
1043
1044        // Assert that we've created a valid MIDI file
1045        assert!(bytes.starts_with(b"MThd"));
1046        assert!(bytes[14..].starts_with(b"MTrk"));
1047
1048        let deserialized_file = MidiFile::from_midi(&bytes).unwrap();
1049        assert_eq!(deserialized_file.tracks.len(), 1);
1050        assert_eq!(deserialized_file.tracks[0].events().len(), 5);
1051        assert_eq!(
1052            deserialized_file.header.division,
1053            Division::TicksPerQuarterNote(480)
1054        );
1055        assert_eq!(deserialized_file, file);
1056    }
1057
1058    #[test]
1059    fn test_file_system_reset() {
1060        let mut file = MidiFile::default();
1061        file.add_track(Track::default());
1062        file.extend_track(
1063            0,
1064            MidiMsg::SystemRealTime {
1065                msg: crate::SystemRealTimeMsg::SystemReset,
1066            },
1067            0.0,
1068        );
1069        let bytes = file.to_midi();
1070
1071        let deserialized_file = MidiFile::from_midi(&bytes).unwrap();
1072        assert_eq!(deserialized_file.tracks.len(), 1);
1073        // The system reset message should not be included in the track, since it is not a valid MIDI file message
1074        assert_eq!(deserialized_file.tracks[0].events().len(), 0);
1075    }
1076}