use thiserror::Error;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct HeaderChunk {
format: Format,
ntrks: u16,
division: Division,
}
impl TryFrom<(u16, u16, u16)> for HeaderChunk {
type Error = InvalidFormat;
fn try_from(value: (u16, u16, u16)) -> Result<Self, Self::Error> {
let (format, ntrks, division) = value;
Ok(Self {
format: format.try_into()?,
ntrks,
division: division.into(),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Format {
Zero,
One,
Two,
}
#[derive(Error, Debug, Clone, Copy, PartialEq, Eq)]
#[error("Invalid header format")]
pub struct InvalidFormat;
impl TryFrom<u16> for Format {
type Error = InvalidFormat;
fn try_from(value: u16) -> Result<Self, Self::Error> {
match value {
0 => Ok(Format::Zero),
1 => Ok(Format::One),
2 => Ok(Format::Two),
_ => Err(InvalidFormat),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Division {
Metrical(u16),
TimeCodeBased(SmpteTicks),
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SmpteTicks {
smpte: i8,
tpf: u8,
}
impl From<u16> for Division {
fn from(value: u16) -> Self {
const MASK: u16 = 0x7FFF;
let msb = value >> 15;
let remaining = value & MASK;
match msb {
0 => Division::Metrical(remaining),
1 => {
let tpf = remaining as u8;
let smpte = (remaining >> 8) as i8;
let smpte = if smpte & 0x8 != 0 {
smpte | !0x7F
} else {
smpte
};
let ticks = SmpteTicks { smpte, tpf };
Division::TimeCodeBased(ticks)
}
_ => unreachable!("Only msb is checked and can therefore only be 1 or 0"),
}
}
}
#[cfg(test)]
mod tests {
const HEADER_CHUNK_RAW: Chunk = Chunk {
chunk_type: HEADER_CHUNK,
length: 6,
};
use crate::{
chunk::{
chunk_types::HEADER_CHUNK,
header::{Division, Format, HeaderChunk, SmpteTicks},
},
reader::{MidiReadable, MidiStream},
Chunk,
};
#[test]
fn parsing_division_to_metrical_works() {
let test: Division = (0x000au16).into();
let expected = Division::Metrical(10);
assert_eq!(test, expected)
}
#[test]
fn parsing_division_to_timecode_works() {
let test: Division = (0x80FFu16).into();
let expected = Division::TimeCodeBased(SmpteTicks { smpte: 0, tpf: 255 });
assert_eq!(test, expected);
let test: Division = (0xFFE8u16).into();
let expected = Division::TimeCodeBased(SmpteTicks {
smpte: -1,
tpf: 232,
});
assert_eq!(test, expected);
let test: Division = (0x8bFFu16).into();
let expected = Division::TimeCodeBased(SmpteTicks {
smpte: -117,
tpf: 255,
});
assert_eq!(test, expected)
}
#[test]
fn header_chunk_reads_properly() {
let mut data = "test/run.mid"
.get_midi_bytes()
.expect("Get `run.midi` file and stream bytes");
let (header, payload) = data.read_chunk_data_pair().expect("Get chunk and data");
let header: Chunk = header.into();
assert_eq!(header, HEADER_CHUNK_RAW);
let mut payload = payload.iter();
let mut packets = vec![];
while let Some(first) = payload.next() {
if let Some(second) = payload.next() {
let bytes = [*first, *second];
let packet = u16::from_be_bytes(bytes);
packets.push(packet);
}
}
assert!(packets.len() == 3);
let header_chunk = HeaderChunk::try_from((packets[0], packets[1], packets[2]))
.expect("Parse header chunk from payload packets");
let expected = HeaderChunk {
format: Format::One,
ntrks: 10,
division: Division::Metrical(384),
};
assert_eq!(expected, header_chunk)
}
}