usbd_midi/packet/
code_index_number.rs

1//! Enum representing the code index number of a packet.
2
3use crate::packet::UsbMidiEventPacketError;
4
5/// The Code Index Number(CIN) indicates the classification
6/// of the bytes in the MIDI_x fields.
7/// Code Index Number classifications.
8#[derive(Debug, Clone, Copy, Eq, PartialEq)]
9#[repr(u8)]
10pub enum CodeIndexNumber {
11    /// Miscellaneous function codes. Reserved for future extensions.
12    MiscFunction = 0x00,
13    /// Cable events. Reserved for future expansion.
14    CableEvents = 0x1,
15    /// Two-byte System Common messages like MTC, SongSelect, etc.
16    SystemCommon2Bytes = 0x2,
17    /// Three-byte System Common messages like SPP, etc.
18    SystemCommon3Bytes = 0x3,
19    /// SysEx starts or continues.
20    SysexStartsOrContinues = 0x4,
21    /// Single-byte System Common Message or SysEx ends with following single byte.
22    SystemCommon1Byte = 0x5,
23    /// SysEx ends with following two bytes.
24    SysexEnds2Bytes = 0x6,
25    /// SysEx ends with following three bytes.
26    SysexEnds3Bytes = 0x7,
27    /// Note-off.
28    NoteOff = 0x8,
29    /// Note-on.
30    NoteOn = 0x9,
31    /// Poly-KeyPress.
32    PolyKeyPress = 0xA,
33    /// Control Change.
34    ControlChange = 0xB,
35    /// Program Change.
36    ProgramChange = 0xC,
37    /// Channel Pressure.
38    ChannelPressure = 0xD,
39    /// PitchBend Change.
40    PitchBendChange = 0xE,
41    /// Single Byte.
42    SingleByte = 0xF,
43}
44
45impl TryFrom<u8> for CodeIndexNumber {
46    type Error = UsbMidiEventPacketError;
47
48    fn try_from(value: u8) -> Result<Self, Self::Error> {
49        match value {
50            x if x == CodeIndexNumber::MiscFunction as u8 => Ok(CodeIndexNumber::MiscFunction),
51            x if x == CodeIndexNumber::CableEvents as u8 => Ok(CodeIndexNumber::CableEvents),
52            x if x == CodeIndexNumber::SystemCommon2Bytes as u8 => {
53                Ok(CodeIndexNumber::SystemCommon2Bytes)
54            }
55            x if x == CodeIndexNumber::SystemCommon3Bytes as u8 => {
56                Ok(CodeIndexNumber::SystemCommon3Bytes)
57            }
58            x if x == CodeIndexNumber::SysexStartsOrContinues as u8 => {
59                Ok(CodeIndexNumber::SysexStartsOrContinues)
60            }
61            x if x == CodeIndexNumber::SystemCommon1Byte as u8 => {
62                Ok(CodeIndexNumber::SystemCommon1Byte)
63            }
64            x if x == CodeIndexNumber::SysexEnds2Bytes as u8 => {
65                Ok(CodeIndexNumber::SysexEnds2Bytes)
66            }
67            x if x == CodeIndexNumber::SysexEnds3Bytes as u8 => {
68                Ok(CodeIndexNumber::SysexEnds3Bytes)
69            }
70            x if x == CodeIndexNumber::NoteOff as u8 => Ok(CodeIndexNumber::NoteOff),
71            x if x == CodeIndexNumber::NoteOn as u8 => Ok(CodeIndexNumber::NoteOn),
72            x if x == CodeIndexNumber::PolyKeyPress as u8 => Ok(CodeIndexNumber::PolyKeyPress),
73            x if x == CodeIndexNumber::ControlChange as u8 => Ok(CodeIndexNumber::ControlChange),
74            x if x == CodeIndexNumber::ProgramChange as u8 => Ok(CodeIndexNumber::ProgramChange),
75            x if x == CodeIndexNumber::ChannelPressure as u8 => {
76                Ok(CodeIndexNumber::ChannelPressure)
77            }
78            x if x == CodeIndexNumber::PitchBendChange as u8 => {
79                Ok(CodeIndexNumber::PitchBendChange)
80            }
81            x if x == CodeIndexNumber::SingleByte as u8 => Ok(CodeIndexNumber::SingleByte),
82            _ => Err(UsbMidiEventPacketError::InvalidCodeIndexNumber(value)),
83        }
84    }
85}
86
87impl CodeIndexNumber {
88    /// Creates a new number from a MIDI event payload.
89    ///
90    /// The detection is based on the content and ignores the slice length.
91    pub fn try_from_payload(payload: &[u8]) -> Result<Self, UsbMidiEventPacketError> {
92        let Some(status) = payload.first() else {
93            return Err(UsbMidiEventPacketError::EmptyPayload);
94        };
95
96        if *status < 0xF0 {
97            match status & 0xF0 {
98                0x80 => Ok(Self::NoteOff),
99                0x90 => Ok(Self::NoteOn),
100                0xA0 => Ok(Self::PolyKeyPress),
101                0xB0 => Ok(Self::ControlChange),
102                0xC0 => Ok(Self::ProgramChange),
103                0xD0 => Ok(Self::ChannelPressure),
104                0xE0 => Ok(Self::PitchBendChange),
105                _ => {
106                    if payload.len() > 1 && payload[1] == 0xF7 {
107                        Ok(Self::SysexEnds2Bytes)
108                    } else if payload.len() > 2 && payload[2] == 0xF7 {
109                        Ok(Self::SysexEnds3Bytes)
110                    } else if payload.len() == 1 {
111                        Ok(Self::SingleByte)
112                    } else if payload.len() > 2 {
113                        Ok(Self::SysexStartsOrContinues)
114                    } else {
115                        Err(UsbMidiEventPacketError::InvalidPayloadSize)
116                    }
117                }
118            }
119        } else {
120            match status {
121                0xF0 => {
122                    if payload.len() > 1 && payload[1] == 0xF7 {
123                        Ok(Self::SysexEnds2Bytes)
124                    } else if payload.len() > 2 && payload[2] == 0xF7 {
125                        Ok(Self::SysexEnds3Bytes)
126                    } else if payload.len() > 2 {
127                        Ok(Self::SysexStartsOrContinues)
128                    } else {
129                        Err(UsbMidiEventPacketError::InvalidPayloadSize)
130                    }
131                }
132                0xF1 | 0xF3 => Ok(Self::SystemCommon2Bytes),
133                0xF2 => Ok(Self::SystemCommon3Bytes),
134                0xF6 | 0xF7 => Ok(Self::SystemCommon1Byte),
135                0xF8 | 0xF9 | 0xFA | 0xFB | 0xFC | 0xFE | 0xFF => Ok(Self::SingleByte),
136                _ => Err(UsbMidiEventPacketError::InvalidPayloadStatus),
137            }
138        }
139    }
140
141    /// Returns the size of the MIDI_x event payload in bytes.
142    pub fn payload_size(&self) -> usize {
143        match self {
144            Self::SystemCommon1Byte | Self::SingleByte => 1,
145            Self::SystemCommon2Bytes
146            | Self::SysexEnds2Bytes
147            | Self::ProgramChange
148            | Self::ChannelPressure => 2,
149            Self::SystemCommon3Bytes
150            | Self::SysexEnds3Bytes
151            | Self::SysexStartsOrContinues
152            | Self::NoteOff
153            | Self::NoteOn
154            | Self::PolyKeyPress
155            | Self::ControlChange
156            | Self::PitchBendChange => 3,
157
158            // These variants are reserved for future use.
159            // We assume the maximum length of 3 bytes so that no data can get lost.
160            Self::MiscFunction | Self::CableEvents => 3,
161        }
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    macro_rules! encode_payload_test {
170        ($($id:ident: $value:expr,)*) => {
171            $(
172                #[test]
173                fn $id() {
174                    let (payload, expected) = $value;
175                    let cin = CodeIndexNumber::try_from_payload(&payload);
176                    assert_eq!(cin, expected);
177                }
178            )*
179        }
180    }
181
182    encode_payload_test! {
183        note_off: ([0x80, 60, 64], Ok(CodeIndexNumber::NoteOff)),
184        note_on: ([0x90, 60, 64], Ok(CodeIndexNumber::NoteOn)),
185        poly_key_press: ([0xA0, 48, 32], Ok(CodeIndexNumber::PolyKeyPress)),
186        control_change: ([0xB0, 10, 127], Ok(CodeIndexNumber::ControlChange)),
187        program_change: ([0xC0, 5], Ok(CodeIndexNumber::ProgramChange)),
188        channel_pressure: ([0xD0, 54], Ok(CodeIndexNumber::ChannelPressure)),
189        pitch_bend: ([0xE0, 32, 96], Ok(CodeIndexNumber::PitchBendChange)),
190        mtc_quarter_frame: ([0xF1, 12], Ok(CodeIndexNumber::SystemCommon2Bytes)),
191        song_position_pointer: ([0xF2, 3, 8], Ok(CodeIndexNumber::SystemCommon3Bytes)),
192        song_select: ([0xF3, 15], Ok(CodeIndexNumber::SystemCommon2Bytes)),
193        tune_request: ([0xF6], Ok(CodeIndexNumber::SystemCommon1Byte)),
194        timing_clock: ([0xF8], Ok(CodeIndexNumber::SingleByte)),
195        tick: ([0xF9], Ok(CodeIndexNumber::SingleByte)),
196        start: ([0xFA], Ok(CodeIndexNumber::SingleByte)),
197        continue_: ([0xFB], Ok(CodeIndexNumber::SingleByte)),
198        stop: ([0xFC], Ok(CodeIndexNumber::SingleByte)),
199        active_sensing: ([0xFE], Ok(CodeIndexNumber::SingleByte)),
200        system_reset: ([0xFF], Ok(CodeIndexNumber::SingleByte)),
201        sysex_starts: ([0xF0, 1, 2], Ok(CodeIndexNumber::SysexStartsOrContinues)),
202        sysex_starts_1byte: ([0xF0], Err(UsbMidiEventPacketError::InvalidPayloadSize)),
203        sysex_starts_2bytes: ([0xF0, 1], Err(UsbMidiEventPacketError::InvalidPayloadSize)),
204        sysex_continues_1byte: ([1], Ok(CodeIndexNumber::SingleByte)),
205        sysex_continues_2bytes: ([1, 2], Err(UsbMidiEventPacketError::InvalidPayloadSize)),
206        sysex_continues_3bytes: ([1, 2, 3], Ok(CodeIndexNumber::SysexStartsOrContinues)),
207        sysex_ends_1byte: ([0xF7], Ok(CodeIndexNumber::SystemCommon1Byte)),
208        sysex_ends_2bytes: ([1, 0xF7], Ok(CodeIndexNumber::SysexEnds2Bytes)),
209        sysex_ends_3bytes: ([1, 2, 0xF7], Ok(CodeIndexNumber::SysexEnds3Bytes)),
210        sysex_2bytes: ([0xF0, 0xF7], Ok(CodeIndexNumber::SysexEnds2Bytes)),
211        sysex_3bytes: ([0xF0, 1, 0xF7], Ok(CodeIndexNumber::SysexEnds3Bytes)),
212        undefined_f4: ([0xF4], Err(UsbMidiEventPacketError::InvalidPayloadStatus)),
213        undefined_f5: ([0xF5], Err(UsbMidiEventPacketError::InvalidPayloadStatus)),
214        empty: ([], Err(UsbMidiEventPacketError::EmptyPayload)),
215    }
216}