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}