use std::fmt;
use std::path::PathBuf;
pub const MAX_SONG_SECONDS: f64 = 24.0 * 3600.0;
pub const MAX_MIDI_FILE_BYTES: u64 = 64 * 1024 * 1024;
#[derive(Debug)]
#[non_exhaustive]
pub enum MidiError {
#[non_exhaustive]
Io {
path: PathBuf,
source: std::io::Error,
},
NotMidi,
#[non_exhaustive]
UnsupportedFormat {
format: u16,
},
UnsupportedTimeDivision,
#[non_exhaustive]
MissingTrack {
index: u16,
},
#[non_exhaustive]
BadStatusByte {
status: u8,
},
UnexpectedEof,
OverlongVlq,
#[non_exhaustive]
TooLong {
seconds: f64,
},
#[non_exhaustive]
TooLarge {
bytes: u64,
},
}
impl fmt::Display for MidiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MidiError::Io { path, source } => write!(f, "{}: {source}", path.display()),
MidiError::NotMidi => f.write_str("not a MIDI file (no MThd header)"),
MidiError::UnsupportedFormat { format } => {
write!(
f,
"unsupported SMF format {format} (only 0 and 1 are supported)"
)
}
MidiError::UnsupportedTimeDivision => {
f.write_str("SMPTE time division is not supported")
}
MidiError::MissingTrack { index } => write!(f, "track {index}: missing MTrk header"),
MidiError::BadStatusByte { status } => write!(f, "bad status byte {status:#04x}"),
MidiError::UnexpectedEof => f.write_str("unexpected end of file"),
MidiError::OverlongVlq => {
f.write_str("a variable-length quantity is longer than the four bytes SMF allows")
}
MidiError::TooLong { seconds } => write!(
f,
"song is {seconds:.0} s long, which exceeds the {MAX_SONG_SECONDS:.0} s limit \
(the tempo map is probably malformed)"
),
MidiError::TooLarge { bytes } => write!(
f,
"MIDI file is {bytes} bytes, which exceeds the {MAX_MIDI_FILE_BYTES} byte \
limit (read the bytes yourself and call `parse` if this is genuine)"
),
}
}
}
impl std::error::Error for MidiError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
MidiError::Io { source, .. } => Some(source),
_ => None,
}
}
}
const _: () = {
const fn assert_send_sync<T: Send + Sync + 'static>() {}
assert_send_sync::<MidiError>();
assert_send_sync::<crate::live::RealtimeError>();
};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn midi_error_is_a_std_error_with_a_source_chain() {
let io = MidiError::Io {
path: PathBuf::from("song.mid"),
source: std::io::Error::new(std::io::ErrorKind::NotFound, "no such file"),
};
assert_eq!(io.to_string(), "song.mid: no such file");
assert!(std::error::Error::source(&io).is_some());
let boxed: Box<dyn std::error::Error + Send + Sync> = Box::new(io);
assert!(boxed.to_string().contains("song.mid"));
assert!(std::error::Error::source(&MidiError::NotMidi).is_none());
assert_eq!(
MidiError::UnsupportedFormat { format: 2 }.to_string(),
"unsupported SMF format 2 (only 0 and 1 are supported)"
);
assert_eq!(
MidiError::BadStatusByte { status: 0xF7 }.to_string(),
"bad status byte 0xf7"
);
assert_eq!(
MidiError::MissingTrack { index: 3 }.to_string(),
"track 3: missing MTrk header"
);
}
}