midi_io/midi/
song_position.rs1use 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}