use super::{event::IteratorWrapper, TrackError};
use crate::{chunk::track::MTrkEvent, reader::Yieldable};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MetaEvent {
SequenceNumber(u16),
Text(String),
Copyright(String),
TrackName(String),
InstrumentName(String),
Lyric(String),
Marker(String),
CuePoint(Vec<u8>),
MidiChannelPrefix(u8),
EndOfTrack,
Tempo(u32),
SmpteOffset(SmpteOffset),
TimeSignature(TimeSignature),
KeySignature(KeySignature),
SequencerSpecific(Vec<u8>),
UnknownRaw(u8, Vec<u8>),
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct KeySignature {
sharps_flats: i8,
major_minor: bool,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SmpteOffset {
hours: u8,
minutes: u8,
seconds: u8,
frames: u8,
subframes: u8,
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct TimeSignature {
numerator: u8,
denominator: u32,
clocks_per_tick: u8,
thirty_second_notes_per_quarter: u8,
}
impl<ITER> TryFrom<IteratorWrapper<&mut ITER>> for MetaEvent
where
ITER: Iterator<Item = u8>,
{
type Error = TrackError;
fn try_from(value: IteratorWrapper<&mut ITER>) -> Result<Self, Self::Error> {
let prefix = value.0.next().ok_or(TrackError::OutOfSpace)?;
if prefix != 0xFF {
return Err(TrackError::InvalidMetaEventData);
}
let event_tag = value.0.next().ok_or(TrackError::OutOfSpace)?;
let length = MTrkEvent::try_get_delta_time(value.0).ok_or(TrackError::OutOfSpace)?;
let data = value.0.get(length as usize);
macro_rules! meta_event {
($len: expr_2021, $name: expr_2021, $value: expr_2021) => {{
if data.len() != $len {
return Err(TrackError::InvalidMetaEventData);
}
Ok($name($value))
}};
}
match event_tag {
0x00 => meta_event!(
2,
MetaEvent::SequenceNumber,
u16::from_be_bytes([data[0], data[1]])
),
0x01 => Ok(MetaEvent::Text(String::from_utf8(data)?)),
0x02 => Ok(MetaEvent::Copyright(String::from_utf8(data)?)),
0x03 => Ok(MetaEvent::TrackName(String::from_utf8(data)?)),
0x04 => Ok(MetaEvent::InstrumentName(String::from_utf8(data)?)),
0x05 => Ok(MetaEvent::Lyric(String::from_utf8(data)?)),
0x06 => Ok(MetaEvent::Marker(String::from_utf8(data)?)),
0x07 => Ok(MetaEvent::CuePoint(data)),
0x20 => meta_event!(1, MetaEvent::MidiChannelPrefix, data[0]),
0x2F => Ok(MetaEvent::EndOfTrack),
0x51 => meta_event!(
3,
MetaEvent::Tempo,
((data[0] as u32) << 16) | ((data[1] as u32) << 8) | (data[2] as u32)
),
0x54 => meta_event!(
5,
MetaEvent::SmpteOffset,
SmpteOffset {
hours: data[0],
minutes: data[1],
seconds: data[2],
frames: data[3],
subframes: data[4]
}
),
0x58 => meta_event!(
4,
MetaEvent::TimeSignature,
TimeSignature {
numerator: data[0],
denominator: 2u32.pow(data[1] as u32),
clocks_per_tick: data[2],
thirty_second_notes_per_quarter: data[3],
}
),
0x59 => meta_event!(
2,
MetaEvent::KeySignature,
KeySignature {
sharps_flats: data[0] as i8,
major_minor: data[1] != 0
}
),
0x7F => Ok(MetaEvent::SequencerSpecific(data)),
_ => Ok(MetaEvent::UnknownRaw(event_tag, data)),
}
}
}
#[cfg(test)]
mod tests {
use crate::chunk::track::{
event::IteratorWrapper,
meta::{KeySignature, MetaEvent, SmpteOffset, TimeSignature},
TrackError,
};
#[test]
fn test_sequence_number() {
let data = vec![0xFF, 0x00, 0x02, 0x00, 0x01]; let result = MetaEvent::try_from(IteratorWrapper(&mut data.into_iter())).unwrap();
assert_eq!(result, MetaEvent::SequenceNumber(1));
}
#[test]
fn test_text_event() {
let data = vec![0xFF, 0x01, 0x05, b'H', b'e', b'l', b'l', b'o']; let result = MetaEvent::try_from(IteratorWrapper(&mut data.into_iter())).unwrap();
assert_eq!(result, MetaEvent::Text("Hello".to_string()));
}
#[test]
fn test_copyright_event() {
let data = vec![
0xFF, 0x02, 0x0A, b'C', b'o', b'p', b'y', b'r', b'i', b'g', b'h', b't',
];
let result = MetaEvent::try_from(IteratorWrapper(&mut data.into_iter())).unwrap();
assert_eq!(result, MetaEvent::Copyright("Copyright".to_string()));
}
#[test]
fn test_tempo_event() {
let data = vec![0xFF, 0x51, 0x03, 0x07, 0xA1, 0x20]; let result = MetaEvent::try_from(IteratorWrapper(&mut data.into_iter())).unwrap();
assert_eq!(result, MetaEvent::Tempo(500_000));
}
#[test]
fn test_time_signature_event() {
let data = vec![0xFF, 0x58, 0x04, 0x04, 0x02, 0x18, 0x08]; let result = MetaEvent::try_from(IteratorWrapper(&mut data.into_iter())).unwrap();
assert_eq!(
result,
MetaEvent::TimeSignature(TimeSignature {
numerator: 4,
denominator: 4, clocks_per_tick: 24,
thirty_second_notes_per_quarter: 8,
})
);
}
#[test]
fn test_key_signature_event() {
let data = vec![0xFF, 0x59, 0x02, 0x00, 0x00]; let result = MetaEvent::try_from(IteratorWrapper(&mut data.into_iter())).unwrap();
assert_eq!(
result,
MetaEvent::KeySignature(KeySignature {
sharps_flats: 0,
major_minor: false,
})
);
}
#[test]
fn test_smpte_offset_event() {
let data = vec![0xFF, 0x54, 0x05, 0x01, 0x20, 0x15, 0x10, 0x00]; let result = MetaEvent::try_from(IteratorWrapper(&mut data.into_iter())).unwrap();
assert_eq!(
result,
MetaEvent::SmpteOffset(SmpteOffset {
hours: 1,
minutes: 32,
seconds: 21,
frames: 16,
subframes: 0,
})
);
}
#[test]
fn test_end_of_track_event() {
let data = vec![0xFF, 0x2F, 0x00]; let result = MetaEvent::try_from(IteratorWrapper(&mut data.into_iter())).unwrap();
assert_eq!(result, MetaEvent::EndOfTrack);
}
#[test]
fn test_unknown_event() {
let data = vec![0xFF, 0x99, 0x03, 0x01, 0x02, 0x03]; let result = MetaEvent::try_from(IteratorWrapper(&mut data.into_iter())).unwrap();
assert_eq!(result, MetaEvent::UnknownRaw(0x99, vec![0x01, 0x02, 0x03]));
}
#[test]
fn test_invalid_length() {
let data = vec![0xFF, 0x00, 0x02, 0x02]; let result = MetaEvent::try_from(IteratorWrapper(&mut data.into_iter()));
assert_eq!(result, Err(TrackError::InvalidMetaEventData));
}
#[test]
fn test_out_of_space() {
let data = vec![]; let result = MetaEvent::try_from(IteratorWrapper(&mut data.into_iter()));
assert_eq!(result, Err(TrackError::OutOfSpace));
}
}