midi_msg/system_exclusive/
file_reference.rs

1use crate::parse_error::*;
2use crate::util::*;
3use alloc::vec::Vec;
4use bstr::BString;
5
6/// The set of messages used for accessing files on a shared file system or network
7/// so they can be used to play sounds without transferring the file contents.
8/// Used by [`UniversalNonRealTimeMsg::FileReference`](crate::UniversalNonRealTimeMsg::FileReference).
9///
10/// As defined in CA-018.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum FileReferenceMsg {
13    /// Describe where a file is located for opening, but must be followed by a `SelectContents`
14    /// message if any sounds are to play.
15    Open {
16        /// A number 0-16383 used to distinguish between multiple file operations on the same device
17        ctx: u16,
18        file_type: FileReferenceType,
19        /// Max 260 character url.
20        url: BString,
21    },
22    /// Given the pointer to a file, prepare it so its sounds can be loaded.
23    SelectContents {
24        /// A number 0-16383 used to distinguish between multiple file operations on the same device
25        ctx: u16,
26        /// How to map the file's sounds onto MIDI banks/programs.
27        map: SelectMap,
28    },
29    /// The equivalent of an `Open` and `SelectContents` messages in succession.
30    OpenSelectContents {
31        /// A number 0-16383 used to distinguish between multiple file operations on the same device
32        ctx: u16,
33        file_type: FileReferenceType,
34        /// Max 260 character url.
35        url: BString,
36        /// How to map the file's sounds onto MIDI banks/programs.
37        map: SelectMap,
38    },
39    /// Close the file and deallocate the data related to it, such that its sounds should
40    /// no longer play.
41    Close {
42        /// A number 0-16383 used to distinguish between multiple file operations on the same device
43        ctx: u16,
44    },
45}
46
47impl FileReferenceMsg {
48    pub(crate) fn extend_midi(&self, v: &mut Vec<u8>) {
49        match self {
50            Self::Open {
51                ctx,
52                file_type,
53                url,
54            } => {
55                push_u14(*ctx, v);
56                let len = 4 + url.len().min(260) + 1;
57                push_u14(len as u16, v);
58                file_type.extend_midi(v);
59                v.extend_from_slice(&url[0..url.len().min(260)]);
60                v.push(0); // Null terminate URL
61            }
62            Self::SelectContents { ctx, map } => {
63                push_u14(*ctx, v);
64                push_u14(map.len() as u16, v);
65                map.extend_midi(v);
66            }
67            Self::OpenSelectContents {
68                ctx,
69                file_type,
70                url,
71                map,
72            } => {
73                push_u14(*ctx, v);
74                let len = 4 + url.len().min(260) + 1 + map.len();
75                push_u14(len as u16, v);
76                file_type.extend_midi(v);
77                v.extend_from_slice(&url[0..url.len().min(260)]);
78                v.push(0); // Null terminate URL
79                map.extend_midi(v);
80            }
81            Self::Close { ctx } => {
82                push_u14(*ctx, v);
83                v.push(0); // Len is zero
84                v.push(0); // And here's another byte for some reason ¯\_(ツ)_/¯
85            }
86        }
87    }
88
89    #[allow(dead_code)]
90    pub(crate) fn from_midi(_m: &[u8]) -> Result<(Self, usize), ParseError> {
91        Err(ParseError::NotImplemented("FileReferenceMsg"))
92    }
93}
94
95/// The file type of a given file, as used by [`FileReferenceMsg`].
96#[derive(Debug, Copy, Clone, PartialEq, Eq)]
97pub enum FileReferenceType {
98    DLS,
99    SF2,
100    WAV,
101}
102
103impl FileReferenceType {
104    fn extend_midi(&self, v: &mut Vec<u8>) {
105        match self {
106            Self::DLS => b"DLS ".iter().for_each(|c| v.push(*c)),
107            Self::SF2 => b"SF2 ".iter().for_each(|c| v.push(*c)),
108            Self::WAV => b"WAV ".iter().for_each(|c| v.push(*c)),
109        }
110    }
111}
112
113/// How to map a `DLS` or `SF2` file for MIDI reference. Used by [`SelectMap`].
114#[derive(Debug, Copy, Clone, PartialEq, Eq)]
115pub struct SoundFileMap {
116    /// MIDI bank number required to select sound for playing. 0-16383
117    pub dst_bank: u16,
118    /// MIDI program number required to select sound for playing. 0-127
119    pub dst_prog: u8,
120    /// MIDI bank number referenced in file's instrument header. 0-16383
121    pub src_bank: u16,
122    /// MIDI program number referenced in file's instrument header. 0-127
123    pub src_prog: u8,
124    /// The selected instrument is a drum instrument
125    pub src_drum: bool,
126    /// The selected instrument should be loaded as a drum instrument
127    pub dst_drum: bool,
128    /// Initial volume 0-127
129    pub volume: u8,
130}
131
132impl Default for SoundFileMap {
133    fn default() -> Self {
134        Self {
135            dst_bank: 0,
136            dst_prog: 0,
137            src_bank: 0,
138            src_prog: 0,
139            src_drum: false,
140            dst_drum: false,
141            volume: 0x7F,
142        }
143    }
144}
145
146impl SoundFileMap {
147    fn extend_midi(&self, v: &mut Vec<u8>) {
148        push_u14(self.dst_bank, v);
149        push_u7(self.dst_prog, v);
150        push_u14(self.src_bank, v);
151        push_u7(self.src_prog, v);
152        let mut flags: u8 = 0;
153        if self.src_drum {
154            flags += 1 << 0;
155        }
156        if self.dst_drum {
157            flags += 1 << 1;
158        }
159        v.push(flags);
160        push_u7(self.volume, v);
161    }
162}
163
164/// How to map a `WAV` file for MIDI reference. Used by [`SelectMap`].
165#[derive(Debug, Copy, Clone, PartialEq, Eq)]
166pub struct WAVMap {
167    /// MIDI bank number required to select sound for playing. 0-16383
168    pub dst_bank: u16,
169    /// MIDI program number required to select sound for playing. 0-127
170    pub dst_prog: u8,
171    /// MIDI note where sound plays at original pitch
172    pub base: u8,
173    /// Lowest MIDI note that plays
174    pub lokey: u8,
175    /// Highest MIDI note that plays
176    pub hikey: u8,
177    /// Fine tuning offset -8192-8191, representing the fractional cents to shift
178    /// in 1/8192ths of a cent
179    pub fine: i16,
180    /// Initial volume 0-127
181    pub volume: u8,
182}
183
184impl WAVMap {
185    fn extend_midi(&self, v: &mut Vec<u8>) {
186        push_u14(self.dst_bank, v);
187        push_u7(self.dst_prog, v);
188        push_u7(self.base, v);
189        push_u7(self.lokey, v);
190        push_u7(self.hikey, v);
191        let [msb, lsb] = i_to_u14(self.fine);
192        v.push(lsb);
193        v.push(msb);
194        push_u7(self.volume, v);
195    }
196}
197
198impl Default for WAVMap {
199    fn default() -> Self {
200        Self {
201            dst_bank: 0,
202            dst_prog: 0,
203            base: 60,
204            lokey: 0,
205            hikey: 0x7F,
206            fine: 0,
207            volume: 0x7F,
208        }
209    }
210}
211
212/// How to map a file for MIDI reference. Used by [`FileReferenceMsg::SelectContents`].
213#[derive(Debug, Clone, PartialEq, Eq)]
214pub enum SelectMap {
215    /// Used for DLS or SF2 files. No more than 127 `SoundFileMap`s.
216    ///
217    /// 0 `SoundFileMap`s indicates "use the map provided in the file".
218    SoundFile(Vec<SoundFileMap>),
219    /// Used for WAV files.
220    WAV(WAVMap),
221    /// Used for DLS or SF2 files. Use the mapping provided by the file,
222    /// but offset the given MIDI bank by `bank_offset`.
223    ///
224    /// Defined in CA-028
225    SoundFileBankOffset {
226        bank_offset: u16,
227        /// The selected instrument is a drum instrument
228        src_drum: bool,
229    },
230    /// Used for WAV files. Offset the dest MIDI bank by `bank_offset`.
231    ///
232    /// Defined in CA-028.
233    WAVBankOffset {
234        map: WAVMap,
235        bank_offset: u16,
236        /// The selected instrument is a drum instrument
237        src_drum: bool,
238    },
239}
240
241impl SelectMap {
242    fn extend_midi(&self, v: &mut Vec<u8>) {
243        match self {
244            Self::WAV(m) => m.extend_midi(v),
245            Self::WAVBankOffset {
246                map,
247                bank_offset,
248                src_drum,
249            } => {
250                map.extend_midi(v);
251                v.push(0); // count
252                v.push(0); // Extension ID 1
253                v.push(1); // Extension ID 2
254                v.push(3); // len
255                push_u14(*bank_offset, v);
256                let mut flags: u8 = 0;
257                if *src_drum {
258                    flags += 1 << 0;
259                }
260                push_u7(flags, v);
261            }
262            Self::SoundFileBankOffset {
263                bank_offset,
264                src_drum,
265            } => {
266                v.push(0); // count
267                v.push(0); // Extension ID 1
268                v.push(1); // Extension ID 2
269                v.push(3); // len
270                push_u14(*bank_offset, v);
271                let mut flags: u8 = 0;
272                if *src_drum {
273                    flags += 1 << 0;
274                }
275                push_u7(flags, v);
276            }
277            Self::SoundFile(maps) => {
278                let count = maps.len().min(127);
279                push_u7(count as u8, v);
280                for m in maps[0..count].iter() {
281                    m.extend_midi(v);
282                }
283            }
284        }
285    }
286
287    fn len(&self) -> usize {
288        match self {
289            Self::WAV(_) => 9,
290            Self::WAVBankOffset { .. } => 9 + 6,
291            Self::SoundFileBankOffset { .. } => 7,
292            Self::SoundFile(maps) => {
293                let count = maps.len().min(127);
294                1 + count * 8
295            }
296        }
297    }
298}
299
300#[cfg(test)]
301mod tests {
302    use super::*;
303    use crate::*;
304    use alloc::vec;
305
306    #[test]
307    fn serialize_sample_dump_msg() {
308        assert_eq!(
309            MidiMsg::SystemExclusive {
310                msg: SystemExclusiveMsg::UniversalNonRealTime {
311                    device: DeviceID::AllCall,
312                    msg: UniversalNonRealTimeMsg::FileReference(
313                        FileReferenceMsg::OpenSelectContents {
314                            ctx: 44,
315                            file_type: FileReferenceType::DLS,
316                            url: BString::from("file://foo.dls"),
317                            map: SelectMap::SoundFile(vec![SoundFileMap {
318                                dst_bank: 1 << 10,
319                                src_prog: 1,
320                                ..Default::default()
321                            }]),
322                        }
323                    ),
324                },
325            }
326            .to_midi(),
327            vec![
328                0xF0, 0x7E, 0x7F, // All call
329                0x0B, 0x03, // ExtendedSampleDump header
330                44, 00, // ctx
331                28, 0, // len,
332                b"D"[0], b"L"[0], b"S"[0], b" "[0], // Start URL
333                b"f"[0], b"i"[0], b"l"[0], b"e"[0], b":"[0], b"/"[0], b"/"[0], b"f"[0], b"o"[0],
334                b"o"[0], b"."[0], b"d"[0], b"l"[0], b"s"[0], 0, // End of url
335                1, // count
336                0, 8, // dst_bank
337                0, //dst_prog
338                0, 0,    // src_bank
339                1,    // src_prog
340                0,    // flags
341                0x7f, // vol
342                0xF7
343            ]
344        );
345    }
346}