midi-io 0.1.1

Stream and send strictly-typed MIDI messages
Documentation
use crate::midi::data_byte::DataByte;
use crate::ValueError;

#[repr(transparent)]
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct SongPosition(u16);

impl SongPosition {
    pub(crate) fn from_msb_lsb(msb: DataByte, lsb: DataByte) -> Self {
        Self(((msb.get() as u16) << 7) | lsb.get() as u16)
    }

    pub const fn get(self) -> u16 {
        self.0
    }

    pub const fn lsb(self) -> u8 {
        (self.0 & 0x7F) as u8
    }

    pub const fn msb(self) -> u8 {
        ((self.0 >> 7) & 0x7F) as u8
    }
}

impl TryFrom<u16> for SongPosition {
    type Error = ValueError;

    fn try_from(value: u16) -> Result<Self, Self::Error> {
        if value > 0x3FFF {
            Err(ValueError::SongPosition(value))
        } else {
            Ok(Self(value))
        }
    }
}

impl std::fmt::Debug for SongPosition {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl std::fmt::Display for SongPosition {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn song_position_splits_into_bytes() {
        let pos = SongPosition::try_from(256).unwrap();
        assert_eq!(pos.get(), 256);
        assert_eq!(pos.lsb(), 0);
        assert_eq!(pos.msb(), 2);
    }

    #[test]
    fn song_position_try_from_rejects_out_of_range() {
        assert_eq!(SongPosition::try_from(0).map(SongPosition::get), Ok(0));
        assert_eq!(
            SongPosition::try_from(0x3FFF).map(SongPosition::get),
            Ok(0x3FFF)
        );
        assert_eq!(
            SongPosition::try_from(0x4000),
            Err(ValueError::SongPosition(0x4000))
        );
        assert_eq!(
            SongPosition::try_from(0xFFFF),
            Err(ValueError::SongPosition(0xFFFF))
        );
    }
}