Skip to main content

midi_io/midi/
song_position.rs

1use crate::midi::data_byte::DataByte;
2use crate::ValueError;
3
4#[repr(transparent)]
5#[derive(Copy, Clone, PartialEq, Eq, Hash)]
6pub struct SongPosition(u16);
7
8impl SongPosition {
9    pub(crate) fn from_msb_lsb(msb: DataByte, lsb: DataByte) -> Self {
10        Self(((msb.get() as u16) << 7) | lsb.get() as u16)
11    }
12
13    pub const fn get(self) -> u16 {
14        self.0
15    }
16
17    pub const fn lsb(self) -> u8 {
18        (self.0 & 0x7F) as u8
19    }
20
21    pub const fn msb(self) -> u8 {
22        ((self.0 >> 7) & 0x7F) as u8
23    }
24}
25
26impl TryFrom<u16> for SongPosition {
27    type Error = ValueError;
28
29    fn try_from(value: u16) -> Result<Self, Self::Error> {
30        if value > 0x3FFF {
31            Err(ValueError::SongPosition(value))
32        } else {
33            Ok(Self(value))
34        }
35    }
36}
37
38impl std::fmt::Debug for SongPosition {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        write!(f, "{}", self.0)
41    }
42}
43
44impl std::fmt::Display for SongPosition {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        write!(f, "{}", self.0)
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn song_position_splits_into_bytes() {
56        let pos = SongPosition::try_from(256).unwrap();
57        assert_eq!(pos.get(), 256);
58        assert_eq!(pos.lsb(), 0);
59        assert_eq!(pos.msb(), 2);
60    }
61
62    #[test]
63    fn song_position_try_from_rejects_out_of_range() {
64        assert_eq!(SongPosition::try_from(0).map(SongPosition::get), Ok(0));
65        assert_eq!(
66            SongPosition::try_from(0x3FFF).map(SongPosition::get),
67            Ok(0x3FFF)
68        );
69        assert_eq!(
70            SongPosition::try_from(0x4000),
71            Err(ValueError::SongPosition(0x4000))
72        );
73        assert_eq!(
74            SongPosition::try_from(0xFFFF),
75            Err(ValueError::SongPosition(0xFFFF))
76        );
77    }
78}