midi_io/midi/
pitch_bend.rs1use crate::midi::data_byte::DataByte;
2use crate::ValueError;
3
4pub(crate) const PITCH_BEND_CENTER: i16 = 8192;
5
6#[repr(transparent)]
7#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
8pub struct PitchBend(u16);
9
10impl PitchBend {
11 pub(crate) fn from_msb_lsb(msb: DataByte, lsb: DataByte) -> Self {
12 Self(((msb.get() as u16) << 7) | lsb.get() as u16)
13 }
14
15 pub fn from_signed(value: i16) -> Result<Self, ValueError> {
16 if !(-PITCH_BEND_CENTER..PITCH_BEND_CENTER).contains(&value) {
17 Err(ValueError::PitchBend(value as i32))
18 } else {
19 Ok(Self((value as i32 + PITCH_BEND_CENTER as i32) as u16))
20 }
21 }
22
23 pub const fn raw(self) -> u16 {
24 self.0
25 }
26
27 pub const fn signed(self) -> i16 {
28 self.0 as i16 - PITCH_BEND_CENTER
29 }
30
31 pub const fn lsb(self) -> u8 {
32 (self.0 & 0x7F) as u8
33 }
34
35 pub const fn msb(self) -> u8 {
36 ((self.0 >> 7) & 0x7F) as u8
37 }
38}
39
40impl TryFrom<u16> for PitchBend {
41 type Error = ValueError;
42
43 fn try_from(value: u16) -> Result<Self, Self::Error> {
44 if value > 0x3FFF {
45 Err(ValueError::PitchBend(value as i32))
46 } else {
47 Ok(Self(value))
48 }
49 }
50}
51
52impl std::fmt::Display for PitchBend {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 write!(f, "{}", self.signed())
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61
62 #[test]
63 fn pitch_bend_center_is_zero_signed() {
64 let center = PitchBend::from_signed(0).unwrap();
65 assert_eq!(center.raw(), 8192);
66 assert_eq!(center.signed(), 0);
67 assert_eq!(center.lsb(), 0);
68 assert_eq!(center.msb(), 64);
69 }
70
71 #[test]
72 fn pitch_bend_signed_roundtrip() {
73 for value in [-8192i16, -4096, -1, 0, 1, 4096, 8191] {
74 assert_eq!(PitchBend::from_signed(value).unwrap().signed(), value);
75 }
76 }
77
78 #[test]
79 fn pitch_bend_from_signed_rejects_out_of_range() {
80 assert_eq!(
81 PitchBend::from_signed(-8192).map(PitchBend::signed),
82 Ok(-8192)
83 );
84 assert_eq!(
85 PitchBend::from_signed(8191).map(PitchBend::signed),
86 Ok(8191)
87 );
88 assert_eq!(
89 PitchBend::from_signed(8192),
90 Err(ValueError::PitchBend(8192))
91 );
92 assert_eq!(
93 PitchBend::from_signed(-8193),
94 Err(ValueError::PitchBend(-8193))
95 );
96 assert_eq!(
97 PitchBend::from_signed(i16::MAX),
98 Err(ValueError::PitchBend(32767))
99 );
100 assert_eq!(
101 PitchBend::from_signed(i16::MIN),
102 Err(ValueError::PitchBend(-32768))
103 );
104 }
105
106 #[test]
107 fn pitch_bend_try_from_rejects_out_of_range() {
108 assert_eq!(PitchBend::try_from(0).map(PitchBend::raw), Ok(0));
109 assert_eq!(PitchBend::try_from(0x3FFF).map(PitchBend::raw), Ok(0x3FFF));
110 assert_eq!(
111 PitchBend::try_from(0x4000),
112 Err(ValueError::PitchBend(0x4000))
113 );
114 assert_eq!(
115 PitchBend::try_from(0xFFFF),
116 Err(ValueError::PitchBend(0xFFFF))
117 );
118 }
119
120 #[test]
121 fn pitch_bend_is_two_bytes() {
122 assert_eq!(std::mem::size_of::<PitchBend>(), 2);
123 }
124}