Skip to main content

midi_controller/
routing.rs

1//! MIDI routing types: port bitmask and tagged output messages.
2//!
3//! The controller uses these to express routing decisions.
4//! Firmware maps port bits to physical hardware (UART, USB, BLE).
5
6use bitflags::bitflags;
7
8bitflags! {
9    /// Bitmask of MIDI ports. Extensible — new ports are new bits.
10    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
11    pub struct MidiPort: u8 {
12        /// DIN 5-pin MIDI (UART)
13        const DIN = 0x01;
14        /// USB MIDI
15        const USB = 0x02;
16        /// Bluetooth LE MIDI (future)
17        const BLE = 0x04;
18    }
19}
20
21impl MidiPort {
22    /// All currently defined ports.
23    pub const ALL: Self = Self::DIN.union(Self::USB);
24}
25
26/// A MIDI message tagged with destination port(s).
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct MidiOut {
29    /// Raw MIDI bytes (fits MIDI 1.0 and future MIDI 2.0 UMP).
30    pub data: [u8; 8],
31    /// Number of valid bytes in `data`.
32    pub len: u8,
33    /// Destination ports (bitmask).
34    pub dest: MidiPort,
35}
36
37impl MidiOut {
38    /// Create a MIDI 1.0 channel message (up to 3 bytes) for the given destination(s).
39    pub fn new(data: &[u8], dest: MidiPort) -> Self {
40        let mut buf = [0u8; 8];
41        let len = data.len().min(8);
42        buf[..len].copy_from_slice(&data[..len]);
43        Self {
44            data: buf,
45            len: len as u8,
46            dest,
47        }
48    }
49
50    /// The message bytes.
51    pub fn bytes(&self) -> &[u8] {
52        &self.data[..self.len as usize]
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn port_single_bit() {
62        assert_eq!(MidiPort::DIN.bits(), 0x01);
63        assert_eq!(MidiPort::USB.bits(), 0x02);
64        assert_eq!(MidiPort::BLE.bits(), 0x04);
65    }
66
67    #[test]
68    fn port_union() {
69        let both = MidiPort::DIN | MidiPort::USB;
70        assert!(both.contains(MidiPort::DIN));
71        assert!(both.contains(MidiPort::USB));
72        assert!(!both.contains(MidiPort::BLE));
73    }
74
75    #[test]
76    fn port_all_includes_din_and_usb() {
77        assert!(MidiPort::ALL.contains(MidiPort::DIN));
78        assert!(MidiPort::ALL.contains(MidiPort::USB));
79    }
80
81    #[test]
82    fn port_empty() {
83        let empty = MidiPort::empty();
84        assert!(!empty.contains(MidiPort::DIN));
85        assert!(!empty.contains(MidiPort::USB));
86        assert!(empty.is_empty());
87    }
88
89    #[test]
90    fn midi_out_new_3byte() {
91        let msg = MidiOut::new(&[0x90, 60, 127], MidiPort::USB);
92        assert_eq!(msg.len, 3);
93        assert_eq!(msg.bytes(), &[0x90, 60, 127]);
94        assert_eq!(msg.dest, MidiPort::USB);
95    }
96
97    #[test]
98    fn midi_out_new_multi_dest() {
99        let msg = MidiOut::new(&[0xB0, 7, 100], MidiPort::DIN | MidiPort::USB);
100        assert!(msg.dest.contains(MidiPort::DIN));
101        assert!(msg.dest.contains(MidiPort::USB));
102        assert!(!msg.dest.contains(MidiPort::BLE));
103    }
104
105    #[test]
106    fn midi_out_8byte_ump() {
107        let data = [0x40, 0x90, 0x3C, 0x00, 0x7F, 0x00, 0x00, 0x00];
108        let msg = MidiOut::new(&data, MidiPort::ALL);
109        assert_eq!(msg.len, 8);
110        assert_eq!(msg.bytes(), &data);
111    }
112
113    #[test]
114    fn midi_out_truncates_oversize() {
115        let data = [0u8; 16]; // larger than 8
116        let msg = MidiOut::new(&data, MidiPort::DIN);
117        assert_eq!(msg.len, 8); // capped at 8
118    }
119}