1use crate::{Channel, ChannelMessage, MidiError, U7, U14};
7
8pub fn encode_channel(message: &ChannelMessage) -> (u8, Vec<u8>) {
12 match *message {
13 ChannelMessage::NoteOff { ch, key, vel } => (0x80 | ch.0, vec![key.0, vel.0]),
14 ChannelMessage::NoteOn { ch, key, vel } => (0x90 | ch.0, vec![key.0, vel.0]),
15 ChannelMessage::PolyAftertouch { ch, key, pressure } => {
16 (0xa0 | ch.0, vec![key.0, pressure.0])
17 }
18 ChannelMessage::ControlChange { ch, cc, value } => (0xb0 | ch.0, vec![cc.0, value.0]),
19 ChannelMessage::ProgramChange { ch, program } => (0xc0 | ch.0, vec![program.0]),
20 ChannelMessage::ChanAftertouch { ch, pressure } => (0xd0 | ch.0, vec![pressure.0]),
21 ChannelMessage::PitchBend { ch, value } => (
22 0xe0 | ch.0,
23 vec![(value.0 & 0x7f) as u8, ((value.0 >> 7) & 0x7f) as u8],
24 ),
25 }
26}
27
28pub fn decode_channel(status: u8, data: &[u8]) -> Result<ChannelMessage, MidiError> {
38 let ch = Channel::new(status & 0x0f)?;
39 let message = match status & 0xf0 {
40 0x80 => ChannelMessage::NoteOff {
41 ch,
42 key: data_u7(data, 0)?,
43 vel: data_u7(data, 1)?,
44 },
45 0x90 => ChannelMessage::NoteOn {
46 ch,
47 key: data_u7(data, 0)?,
48 vel: data_u7(data, 1)?,
49 },
50 0xa0 => ChannelMessage::PolyAftertouch {
51 ch,
52 key: data_u7(data, 0)?,
53 pressure: data_u7(data, 1)?,
54 },
55 0xb0 => ChannelMessage::ControlChange {
56 ch,
57 cc: data_u7(data, 0)?,
58 value: data_u7(data, 1)?,
59 },
60 0xc0 => ChannelMessage::ProgramChange {
61 ch,
62 program: data_u7(data, 0)?,
63 },
64 0xd0 => ChannelMessage::ChanAftertouch {
65 ch,
66 pressure: data_u7(data, 0)?,
67 },
68 0xe0 => {
69 let value = u16::from(data_u7(data, 0)?.0) | (u16::from(data_u7(data, 1)?.0) << 7);
70 ChannelMessage::PitchBend {
71 ch,
72 value: U14::try_from(value)?,
73 }
74 }
75 _ => return Err(MidiError::NotChannelStatus(status)),
76 };
77 Ok(message)
78}
79
80fn data_u7(data: &[u8], index: usize) -> Result<U7, MidiError> {
83 let byte = *data.get(index).ok_or(MidiError::TruncatedChannel)?;
84 U7::try_from(u16::from(byte))
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 fn ch(value: u8) -> Channel {
92 Channel::new(value).expect("channel in range")
93 }
94
95 fn every_variant() -> Vec<ChannelMessage> {
98 vec![
99 ChannelMessage::NoteOff {
100 ch: ch(0),
101 key: U7(60),
102 vel: U7(0),
103 },
104 ChannelMessage::NoteOn {
105 ch: ch(15),
106 key: U7(127),
107 vel: U7(100),
108 },
109 ChannelMessage::PolyAftertouch {
110 ch: ch(3),
111 key: U7(64),
112 pressure: U7(90),
113 },
114 ChannelMessage::ControlChange {
115 ch: ch(9),
116 cc: U7(7),
117 value: U7(127),
118 },
119 ChannelMessage::ProgramChange {
120 ch: ch(1),
121 program: U7(42),
122 },
123 ChannelMessage::ChanAftertouch {
124 ch: ch(12),
125 pressure: U7(55),
126 },
127 ChannelMessage::PitchBend {
128 ch: ch(0),
129 value: U14(0),
130 },
131 ChannelMessage::PitchBend {
132 ch: ch(7),
133 value: U14(8192),
134 },
135 ChannelMessage::PitchBend {
136 ch: ch(15),
137 value: U14(16_383),
138 },
139 ]
140 }
141
142 #[test]
143 fn encode_then_decode_round_trips_every_variant() {
144 for message in every_variant() {
145 let (status, data) = encode_channel(&message);
146 let decoded = decode_channel(status, &data).expect("decode");
147 assert_eq!(decoded, message, "round trip for {message:?}");
148 }
149 }
150
151 #[test]
152 fn non_channel_status_is_rejected() {
153 assert_eq!(
154 decode_channel(0xf0, &[0x00]),
155 Err(MidiError::NotChannelStatus(0xf0))
156 );
157 }
158
159 #[test]
160 fn truncated_data_is_rejected() {
161 assert_eq!(
163 decode_channel(0x90, &[60]),
164 Err(MidiError::TruncatedChannel)
165 );
166 assert_eq!(decode_channel(0xc0, &[]), Err(MidiError::TruncatedChannel));
168 }
169
170 #[test]
171 fn data_byte_above_7_bits_is_rejected() {
172 assert_eq!(
173 decode_channel(0x90, &[200, 0]),
174 Err(MidiError::InvalidU7(200))
175 );
176 }
177}