nom_midi/parser/
header.rs

1use crate::types::{Division, Fps, MidiFormat, MidiHeader};
2use nom::{
3    error::{make_error, ErrorKind},
4    Err, IResult,
5};
6
7pub fn parse_format(i: &[u8]) -> IResult<&[u8], MidiFormat> {
8    use nom::number::streaming::be_u16;
9    let (i, format) = be_u16(i)?;
10    match format {
11        0 => {
12            let (i, num_tracks) = be_u16(i)?;
13            if num_tracks != 1 {
14                Err(Err::Error(make_error(i, ErrorKind::Digit)))
15            } else {
16                Ok((i, MidiFormat::SingleTrack))
17            }
18        }
19        1 => {
20            let (i, num_tracks) = be_u16(i)?;
21            Ok((i, MidiFormat::MultipleTrack(num_tracks)))
22        }
23        2 => {
24            let (i, num_tracks) = be_u16(i)?;
25            Ok((i, MidiFormat::MultipleSong(num_tracks)))
26        }
27        _ => Err(Err::Error(make_error(i, ErrorKind::Digit))),
28    }
29}
30
31pub fn parse_division(i: &[u8]) -> IResult<&[u8], Division> {
32    use nom::{bytes::streaming::take, number::streaming::be_u16};
33    let (i, bytes) = take(2usize)(i)?;
34
35    // Test first bit for type
36    let division = if bytes[0] & 0x80 == 0x80 {
37        // we are using timecode (2's complement notation negative numbers)
38        let fps = match bytes[1] {
39            0xE8 => Fps::TwentyFour,
40            0xE7 => Fps::TwentyFive,
41            0xE3 => Fps::TwentyNine,
42            0xE2 => Fps::Thirty,
43            _ => return Err(Err::Error(make_error(i, ErrorKind::Digit))),
44        };
45        let res = bytes[0] & 0x7F;
46        Division::Timecode { fps, res }
47    } else {
48        // we are using metrical timing
49        let (_, note_div) = be_u16(bytes)?;
50        Division::Metrical(note_div & 0x7FFF)
51    };
52    Ok((i, division))
53}
54
55pub fn parse_header_chunk(i: &[u8]) -> IResult<&[u8], MidiHeader> {
56    use nom::bytes::streaming::tag;
57    use nom::number::streaming::be_u32;
58    let (i, _) = tag("MThd")(i)?;
59    let (i, hdr_len) = be_u32(i)?;
60    // The header length must always be 6
61    if hdr_len != 6 {
62        return Err(Err::Error(make_error(i, ErrorKind::Digit)));
63    }
64    let (i, format) = parse_format(i)?;
65    let (i, division) = parse_division(i)?;
66    Ok((i, MidiHeader { format, division }))
67}
68
69#[test]
70fn test_header_chunk() {
71    let midi_file = [77u8, 84, 104, 100, 0, 0, 0, 6, 0, 1, 0, 5, 1, 0];
72    assert_eq!(
73        parse_header_chunk(&midi_file[..]),
74        Ok((
75            &b""[..],
76            MidiHeader {
77                format: MidiFormat::MultipleTrack(5),
78                division: Division::Metrical(256),
79            }
80        ))
81    );
82}