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