use std::{error, io};
use std::fmt::{self, Debug, Display, Formatter, Result, Write};
#[derive(Debug)]
pub enum SoundFontError {
IoError(io::Error),
RiffChunkNotFound,
InvalidRiffChunkType {
expected: FourCC,
actual: FourCC,
},
ListChunkNotFound,
InvalidListChunkType {
expected: FourCC,
actual: FourCC,
},
ListContainsUnknownId(FourCC),
SampleDataNotFound,
UnsupportedSampleFormat,
SubChunkNotFound(FourCC),
InvalidPresetList,
InvalidInstrumentId {
preset_id: usize,
instrument_id: usize,
},
InvalidPreset(usize),
PresetNotFound,
InvalidInstrumentList,
InvalidSampleId {
instrument_id: usize,
sample_id: usize,
},
InvalidInstrument(usize),
InstrumentNotFound,
InvalidSampleHeaderList,
InvalidZoneList,
ZoneNotFound,
InvalidGeneratorList,
RegionCheckFailed {
inst_name: String,
region_idx: usize,
msg: String,
},
RegionSampleOutOfBounds {
inst_name: String,
region_idx: usize,
},
}
impl error::Error for SoundFontError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
SoundFontError::IoError(err) => Some(err),
_ => None,
}
}
}
impl fmt::Display for SoundFontError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
SoundFontError::IoError(err) => Display::fmt(&err, f),
SoundFontError::RiffChunkNotFound => write!(f, "the RIFF chunk was not found"),
SoundFontError::InvalidRiffChunkType { expected, actual } => write!(
f,
"the type of the RIFF chunk must be '{}', but was '{}'",
expected, actual
),
SoundFontError::ListChunkNotFound => write!(f, "the LIST chunk was not found"),
SoundFontError::InvalidListChunkType { expected, actual } => write!(
f,
"the type of the LIST chunk must be '{}', but was '{}'",
expected, actual
),
SoundFontError::ListContainsUnknownId(id) => {
write!(f, "the INFO list contains an unknown ID '{id}'")
}
SoundFontError::SampleDataNotFound => write!(f, "no valid sample data was found"),
SoundFontError::UnsupportedSampleFormat => write!(f, "SoundFont3 is not yet supported"),
SoundFontError::SubChunkNotFound(id) => {
write!(f, "the '{}' sub-chunk was not found", id)
}
SoundFontError::InvalidPresetList => write!(f, "the preset list is invalid"),
SoundFontError::InvalidInstrumentId {
preset_id,
instrument_id,
} => write!(
f,
"the preset with the ID '{preset_id}' contains an invalid instrument ID '{instrument_id}'"
),
SoundFontError::InvalidPreset(preset_id) => {
write!(f, "the preset with the ID '{preset_id}' has no zone")
}
SoundFontError::PresetNotFound => write!(f, "no valid preset was found"),
SoundFontError::InvalidInstrumentList => write!(f, "the instrument list is invalid"),
SoundFontError::InvalidSampleId {
instrument_id,
sample_id,
} => write!(
f,
"the instrument with the ID '{instrument_id}' contains an invalid sample ID '{sample_id}'"
),
SoundFontError::InvalidInstrument(instrument_id) => {
write!(
f,
"the instrument with the ID '{instrument_id}' has no zone"
)
}
SoundFontError::InstrumentNotFound => write!(f, "no valid instrument was found"),
SoundFontError::InvalidSampleHeaderList => {
write!(f, "the sample header list is invalid")
}
SoundFontError::InvalidZoneList => write!(f, "the zone list is invalid"),
SoundFontError::ZoneNotFound => write!(f, "no valid zone was found"),
SoundFontError::InvalidGeneratorList => write!(f, "the generator list is invalid"),
SoundFontError::RegionCheckFailed {
inst_name,
region_idx,
msg,
} => {
write!(f, "Error at inst {inst_name}, zone {region_idx}: {msg}")
}
SoundFontError::RegionSampleOutOfBounds {
inst_name,
region_idx,
} => {
write!(
f,
"Error at inst {inst_name}, zone {region_idx}: Sample out of bounds"
)
}
}
}
}
impl From<io::Error> for SoundFontError {
fn from(err: io::Error) -> Self {
SoundFontError::IoError(err)
}
}
#[derive(PartialEq, Eq, Clone, Copy)]
pub struct FourCC([u8; 4]);
impl FourCC {
pub(crate) const fn from_bytes(mut bytes: [u8; 4]) -> Self {
bytes[0] = replace_with_question_mark_if_non_ascii(bytes[0]);
bytes[1] = replace_with_question_mark_if_non_ascii(bytes[1]);
bytes[2] = replace_with_question_mark_if_non_ascii(bytes[2]);
bytes[3] = replace_with_question_mark_if_non_ascii(bytes[3]);
Self(bytes)
}
pub const fn as_bytes(&self) -> &[u8; 4] {
&self.0
}
}
impl Display for FourCC {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
for byte in &self.0 {
f.write_char(*byte as char)?;
}
Ok(())
}
}
impl Debug for FourCC {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
f.write_char('"')?;
for byte in &self.0 {
f.write_char(*byte as char)?;
}
f.write_char('"')?;
Ok(())
}
}
impl PartialEq<&[u8; 4]> for FourCC {
fn eq(&self, other: &&[u8; 4]) -> bool {
&self.0 == *other
}
}
impl PartialEq<[u8; 4]> for FourCC {
fn eq(&self, other: &[u8; 4]) -> bool {
&self.0 == other
}
}
const fn is_ascii_graphic_or_space(byte: u8) -> bool {
byte.is_ascii_graphic() || byte == b' '
}
const fn replace_with_question_mark_if_non_ascii(byte: u8) -> u8 {
if is_ascii_graphic_or_space(byte) {
byte
} else {
b'?'
}
}