midi_msg/system_exclusive/
file_dump.rs

1use super::DeviceID;
2use crate::parse_error::*;
3use crate::util::*;
4use alloc::vec::Vec;
5use bstr::BString;
6
7/// Used to transmit general file data.
8/// Used by [`UniversalNonRealTimeMsg`](crate::UniversalNonRealTimeMsg).
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum FileDumpMsg {
11    /// Request that the file with `name` be sent.
12    Request {
13        requester_device: DeviceID,
14        file_type: FileType,
15        name: BString,
16    },
17    /// The header of the file about to be sent.
18    Header {
19        sender_device: DeviceID,
20        file_type: FileType,
21        /// Actual (un-encoded) file length, 28 bits (0-2684354561)
22        length: u32,
23        name: BString,
24    },
25    /// A packet of the file being sent.
26    ///
27    /// Use `FileDumpMsg::packet` to construct
28    Packet {
29        /// Running packet count, 0-127. Wraps back to 0
30        running_count: u8,
31        /// At most 112 bytes (full 8 bits may be used)
32        data: Vec<u8>,
33    },
34}
35
36impl FileDumpMsg {
37    pub(crate) fn extend_midi(&self, v: &mut Vec<u8>) {
38        match self {
39            Self::Header {
40                sender_device,
41                file_type,
42                length,
43                name,
44            } => {
45                v.push(0x1);
46                v.push(sender_device.to_u8());
47                file_type.extend_midi(v);
48                push_u28(*length, v);
49                v.extend_from_slice(name);
50            }
51            Self::Packet {
52                running_count,
53                data,
54                ..
55            } => {
56                v.push(0x2);
57                v.push(to_u7(*running_count));
58                let mut len = data.len().min(112);
59                // Add number of extra encoded bytes
60                // (/ 7 is -1 of actual number of encoded bytes, but it's sent as length - 1)
61                len += len / 7;
62                assert!(len < 128);
63                v.push(len as u8);
64                v.extend(Self::encode_data(data));
65                v.push(0); // Checksum <- Will be written over by `SystemExclusiveMsg.extend_midi`
66            }
67            Self::Request {
68                requester_device,
69                file_type,
70                name,
71            } => {
72                v.push(0x3);
73                v.push(requester_device.to_u8());
74                file_type.extend_midi(v);
75                v.extend_from_slice(name);
76            }
77        }
78    }
79
80    /// Construct a packet of up to 112 (full) bytes.
81    /// `num` is the number of this packet.
82    pub fn packet(num: u32, data: Vec<u8>) -> Self {
83        Self::Packet {
84            running_count: (num % 128) as u8,
85            data,
86        }
87    }
88
89    fn encode_data(data: &[u8]) -> Vec<u8> {
90        let mut r = Vec::with_capacity(128);
91        let mut d = 0; // Data position
92        let mut e = 0; // Encoded position
93        loop {
94            if e >= 128 || d >= data.len() {
95                break;
96            }
97            r.push(0); // First bits
98            let mut j = 0;
99            loop {
100                if j >= 7 || d + j >= data.len() {
101                    break;
102                }
103                r[e] += (data[d + j] >> 7) << (6 - j);
104                r.push(data[d + j] & 0b01111111);
105                j += 1;
106            }
107
108            e += 8;
109            d += j;
110        }
111        r
112    }
113
114    #[allow(dead_code)]
115    pub(crate) fn from_midi(_m: &[u8]) -> Result<(Self, usize), ParseError> {
116        Err(ParseError::NotImplemented("FileDumpMsg"))
117    }
118}
119
120/// A four-character file type used by [`FileDumpMsg`].
121#[derive(Debug, Copy, Clone, PartialEq, Eq)]
122pub enum FileType {
123    MIDI,
124    MIEX,
125    ESEQ,
126    TEXT,
127    BIN,
128    MAC,
129    Custom([u8; 4]),
130}
131
132impl FileType {
133    fn extend_midi(&self, v: &mut Vec<u8>) {
134        match self {
135            Self::MIDI => b"MIDI".iter().for_each(|c| v.push(*c)),
136            Self::MIEX => b"MIEX".iter().for_each(|c| v.push(*c)),
137            Self::ESEQ => b"ESEQ".iter().for_each(|c| v.push(*c)),
138            Self::TEXT => b"TEXT".iter().for_each(|c| v.push(*c)),
139            Self::BIN => b"BIN ".iter().for_each(|c| v.push(*c)),
140            Self::MAC => b"MAC ".iter().for_each(|c| v.push(*c)),
141            Self::Custom(chars) => chars[0..4].iter().for_each(|c| v.push(*c)),
142        }
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149    use crate::*;
150    use alloc::vec;
151
152    #[test]
153    fn encode_data() {
154        assert_eq!(
155            FileDumpMsg::encode_data(&[
156                0b11111111, 0b10101010, 0b00000000, 0b01010101, 0b11111111, 0b10101010, 0b00000000,
157                0b11010101
158            ]),
159            vec![
160                0b01100110, 0b01111111, 0b00101010, 0b00000000, 0b01010101, 0b01111111, 0b00101010,
161                0b00000000, 0b01000000, 0b01010101
162            ]
163        );
164    }
165
166    #[test]
167    fn serialize_file_dump_packet() {
168        let packet_msg = MidiMsg::SystemExclusive {
169            msg: SystemExclusiveMsg::UniversalNonRealTime {
170                device: DeviceID::AllCall,
171                msg: UniversalNonRealTimeMsg::FileDump(FileDumpMsg::packet(
172                    129,
173                    vec![
174                        0b11111111, 0b10101010, 0b00000000, 0b01010101, 0b11111111, 0b10101010,
175                        0b00000000, 0b11010101,
176                    ],
177                )),
178            },
179        }
180        .to_midi();
181
182        assert_eq!(packet_msg.len(), 19);
183        assert_eq!(&packet_msg[0..7], &[0xF0, 0x7E, 0x7F, 0x07, 0x02, 0x01, 9]);
184        assert_eq!(
185            &packet_msg[7..17],
186            &[
187                0b01100110, 0b01111111, 0b00101010, 0b00000000, 0b01010101, 0b01111111, 0b00101010,
188                0b00000000, 0b01000000, 0b01010101
189            ]
190        );
191        assert_eq!(
192            packet_msg[17], // Checksum
193            checksum(&[
194                0x7E, 0x7F, 0x07, 0x02, 0x01, 9, 0b01100110, 0b01111111, 0b00101010, 0b00000000,
195                0b01010101, 0b01111111, 0b00101010, 0b00000000, 0b01000000, 0b01010101
196            ])
197        );
198    }
199
200    #[test]
201    fn serialize_file_dump_header() {
202        assert_eq!(
203            MidiMsg::SystemExclusive {
204                msg: SystemExclusiveMsg::UniversalNonRealTime {
205                    device: DeviceID::AllCall,
206                    msg: UniversalNonRealTimeMsg::FileDump(FileDumpMsg::Header {
207                        sender_device: DeviceID::Device(9),
208                        file_type: FileType::MIDI,
209                        length: 66,
210                        name: BString::from("Hello"),
211                    }),
212                },
213            }
214            .to_midi(),
215            vec![
216                0xF0, 0x7E, 0x7F, // Receiver device
217                0x7, 0x1, 0x9, // Sender device
218                b"M"[0], b"I"[0], b"D"[0], b"I"[0], 66, // Size LSB
219                0x0, 0x0, 0x0, b"H"[0], b"e"[0], b"l"[0], b"l"[0], b"o"[0], 0xF7
220            ]
221        );
222    }
223}