Skip to main content

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