Skip to main content

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