1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Helpers for UMP <-> MIDI 1.0 byte stream conversion
4 */
5
6 #include <linux/module.h>
7 #include <linux/export.h>
8 #include <sound/core.h>
9 #include <sound/asound.h>
10 #include <sound/ump.h>
11 #include <sound/ump_convert.h>
12
13 /*
14 * Upgrade / downgrade value bits
15 */
downscale_32_to_7bit(u32 src)16 static u8 downscale_32_to_7bit(u32 src)
17 {
18 return src >> 25;
19 }
20
downscale_32_to_14bit(u32 src)21 static u16 downscale_32_to_14bit(u32 src)
22 {
23 return src >> 18;
24 }
25
downscale_16_to_7bit(u16 src)26 static u8 downscale_16_to_7bit(u16 src)
27 {
28 return src >> 9;
29 }
30
upscale_7_to_16bit(u8 src)31 static u16 upscale_7_to_16bit(u8 src)
32 {
33 u16 val, repeat;
34
35 val = (u16)src << 9;
36 if (src <= 0x40)
37 return val;
38 repeat = src & 0x3f;
39 return val | (repeat << 3) | (repeat >> 3);
40 }
41
upscale_7_to_32bit(u8 src)42 static u32 upscale_7_to_32bit(u8 src)
43 {
44 u32 val, repeat;
45
46 val = src << 25;
47 if (src <= 0x40)
48 return val;
49 repeat = src & 0x3f;
50 return val | (repeat << 19) | (repeat << 13) |
51 (repeat << 7) | (repeat << 1) | (repeat >> 5);
52 }
53
upscale_14_to_32bit(u16 src)54 static u32 upscale_14_to_32bit(u16 src)
55 {
56 u32 val, repeat;
57
58 val = src << 18;
59 if (src <= 0x2000)
60 return val;
61 repeat = src & 0x1fff;
62 return val | (repeat << 5) | (repeat >> 8);
63 }
64
65 /*
66 * UMP -> MIDI 1 byte stream conversion
67 */
68 /* convert a UMP System message to MIDI 1.0 byte stream */
cvt_ump_system_to_legacy(u32 data,unsigned char * buf)69 static int cvt_ump_system_to_legacy(u32 data, unsigned char *buf)
70 {
71 buf[0] = ump_message_status_channel(data);
72 switch (ump_message_status_code(data)) {
73 case UMP_SYSTEM_STATUS_MIDI_TIME_CODE:
74 case UMP_SYSTEM_STATUS_SONG_SELECT:
75 buf[1] = (data >> 8) & 0x7f;
76 return 2;
77 case UMP_SYSTEM_STATUS_SONG_POSITION:
78 buf[1] = (data >> 8) & 0x7f;
79 buf[2] = data & 0x7f;
80 return 3;
81 default:
82 return 1;
83 }
84 }
85
86 /* convert a UMP MIDI 1.0 Channel Voice message to MIDI 1.0 byte stream */
cvt_ump_midi1_to_legacy(u32 data,unsigned char * buf)87 static int cvt_ump_midi1_to_legacy(u32 data, unsigned char *buf)
88 {
89 buf[0] = ump_message_status_channel(data);
90 buf[1] = (data >> 8) & 0xff;
91 switch (ump_message_status_code(data)) {
92 case UMP_MSG_STATUS_PROGRAM:
93 case UMP_MSG_STATUS_CHANNEL_PRESSURE:
94 return 2;
95 default:
96 buf[2] = data & 0xff;
97 return 3;
98 }
99 }
100
101 /* convert a UMP MIDI 2.0 Channel Voice message to MIDI 1.0 byte stream */
cvt_ump_midi2_to_legacy(const union snd_ump_midi2_msg * midi2,unsigned char * buf)102 static int cvt_ump_midi2_to_legacy(const union snd_ump_midi2_msg *midi2,
103 unsigned char *buf)
104 {
105 unsigned char status = midi2->note.status;
106 unsigned char channel = midi2->note.channel;
107 u16 v;
108
109 buf[0] = (status << 4) | channel;
110 switch (status) {
111 case UMP_MSG_STATUS_NOTE_OFF:
112 case UMP_MSG_STATUS_NOTE_ON:
113 buf[1] = midi2->note.note;
114 buf[2] = downscale_16_to_7bit(midi2->note.velocity);
115 if (status == UMP_MSG_STATUS_NOTE_ON && !buf[2])
116 buf[2] = 1;
117 return 3;
118 case UMP_MSG_STATUS_POLY_PRESSURE:
119 buf[1] = midi2->paf.note;
120 buf[2] = downscale_32_to_7bit(midi2->paf.data);
121 return 3;
122 case UMP_MSG_STATUS_CC:
123 buf[1] = midi2->cc.index;
124 buf[2] = downscale_32_to_7bit(midi2->cc.data);
125 return 3;
126 case UMP_MSG_STATUS_CHANNEL_PRESSURE:
127 buf[1] = downscale_32_to_7bit(midi2->caf.data);
128 return 2;
129 case UMP_MSG_STATUS_PROGRAM:
130 if (midi2->pg.bank_valid) {
131 buf[0] = channel | (UMP_MSG_STATUS_CC << 4);
132 buf[1] = UMP_CC_BANK_SELECT;
133 buf[2] = midi2->pg.bank_msb;
134 buf[3] = channel | (UMP_MSG_STATUS_CC << 4);
135 buf[4] = UMP_CC_BANK_SELECT_LSB;
136 buf[5] = midi2->pg.bank_lsb;
137 buf[6] = channel | (UMP_MSG_STATUS_PROGRAM << 4);
138 buf[7] = midi2->pg.program;
139 return 8;
140 }
141 buf[1] = midi2->pg.program;
142 return 2;
143 case UMP_MSG_STATUS_PITCH_BEND:
144 v = downscale_32_to_14bit(midi2->pb.data);
145 buf[1] = v & 0x7f;
146 buf[2] = v >> 7;
147 return 3;
148 case UMP_MSG_STATUS_RPN:
149 case UMP_MSG_STATUS_NRPN:
150 buf[0] = channel | (UMP_MSG_STATUS_CC << 4);
151 buf[1] = status == UMP_MSG_STATUS_RPN ? UMP_CC_RPN_MSB : UMP_CC_NRPN_MSB;
152 buf[2] = midi2->rpn.bank;
153 buf[3] = buf[0];
154 buf[4] = status == UMP_MSG_STATUS_RPN ? UMP_CC_RPN_LSB : UMP_CC_NRPN_LSB;
155 buf[5] = midi2->rpn.index;
156 buf[6] = buf[0];
157 buf[7] = UMP_CC_DATA;
158 v = downscale_32_to_14bit(midi2->rpn.data);
159 buf[8] = v >> 7;
160 buf[9] = buf[0];
161 buf[10] = UMP_CC_DATA_LSB;
162 buf[11] = v & 0x7f;
163 return 12;
164 default:
165 return 0;
166 }
167 }
168
169 /* convert a UMP 7-bit SysEx message to MIDI 1.0 byte stream */
cvt_ump_sysex7_to_legacy(const u32 * data,unsigned char * buf)170 static int cvt_ump_sysex7_to_legacy(const u32 *data, unsigned char *buf)
171 {
172 unsigned char status;
173 unsigned char bytes;
174 int size, offset;
175
176 status = ump_sysex_message_status(*data);
177 if (status > UMP_SYSEX_STATUS_END)
178 return 0; // unsupported, skip
179 bytes = ump_sysex_message_length(*data);
180 if (bytes > 6)
181 return 0; // skip
182
183 size = 0;
184 if (status == UMP_SYSEX_STATUS_SINGLE ||
185 status == UMP_SYSEX_STATUS_START) {
186 buf[0] = UMP_MIDI1_MSG_SYSEX_START;
187 size = 1;
188 }
189
190 offset = 8;
191 for (; bytes; bytes--, size++) {
192 buf[size] = (*data >> offset) & 0x7f;
193 if (!offset) {
194 offset = 24;
195 data++;
196 } else {
197 offset -= 8;
198 }
199 }
200
201 if (status == UMP_SYSEX_STATUS_SINGLE ||
202 status == UMP_SYSEX_STATUS_END)
203 buf[size++] = UMP_MIDI1_MSG_SYSEX_END;
204
205 return size;
206 }
207
208 /**
209 * snd_ump_convert_from_ump - convert from UMP to legacy MIDI
210 * @data: UMP packet
211 * @buf: buffer to store legacy MIDI data
212 * @group_ret: pointer to store the target group
213 *
214 * Convert from a UMP packet @data to MIDI 1.0 bytes at @buf.
215 * The target group is stored at @group_ret.
216 *
217 * The function returns the number of bytes of MIDI 1.0 stream.
218 */
snd_ump_convert_from_ump(const u32 * data,unsigned char * buf,unsigned char * group_ret)219 int snd_ump_convert_from_ump(const u32 *data,
220 unsigned char *buf,
221 unsigned char *group_ret)
222 {
223 *group_ret = ump_message_group(*data);
224
225 switch (ump_message_type(*data)) {
226 case UMP_MSG_TYPE_SYSTEM:
227 return cvt_ump_system_to_legacy(*data, buf);
228 case UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE:
229 return cvt_ump_midi1_to_legacy(*data, buf);
230 case UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE:
231 return cvt_ump_midi2_to_legacy((const union snd_ump_midi2_msg *)data,
232 buf);
233 case UMP_MSG_TYPE_DATA:
234 return cvt_ump_sysex7_to_legacy(data, buf);
235 }
236
237 return 0;
238 }
239 EXPORT_SYMBOL_GPL(snd_ump_convert_from_ump);
240
241 /*
242 * MIDI 1 byte stream -> UMP conversion
243 */
244 /* convert MIDI 1.0 SysEx to a UMP packet */
cvt_legacy_sysex_to_ump(struct ump_cvt_to_ump * cvt,unsigned char group,u32 * data,bool finish)245 static int cvt_legacy_sysex_to_ump(struct ump_cvt_to_ump *cvt,
246 unsigned char group, u32 *data, bool finish)
247 {
248 unsigned char status;
249 bool start = cvt->in_sysex == 1;
250 int i, offset;
251
252 if (start && finish)
253 status = UMP_SYSEX_STATUS_SINGLE;
254 else if (start)
255 status = UMP_SYSEX_STATUS_START;
256 else if (finish)
257 status = UMP_SYSEX_STATUS_END;
258 else
259 status = UMP_SYSEX_STATUS_CONTINUE;
260 *data = ump_compose(UMP_MSG_TYPE_DATA, group, status, cvt->len);
261 offset = 8;
262 for (i = 0; i < cvt->len; i++) {
263 *data |= cvt->buf[i] << offset;
264 if (!offset) {
265 offset = 24;
266 data++;
267 } else
268 offset -= 8;
269 }
270 cvt->len = 0;
271 if (finish)
272 cvt->in_sysex = 0;
273 else
274 cvt->in_sysex++;
275 return 8;
276 }
277
278 /* convert to a UMP System message */
cvt_legacy_system_to_ump(struct ump_cvt_to_ump * cvt,unsigned char group,u32 * data)279 static int cvt_legacy_system_to_ump(struct ump_cvt_to_ump *cvt,
280 unsigned char group, u32 *data)
281 {
282 data[0] = ump_compose(UMP_MSG_TYPE_SYSTEM, group, 0, cvt->buf[0]);
283 if (cvt->cmd_bytes > 1)
284 data[0] |= cvt->buf[1] << 8;
285 if (cvt->cmd_bytes > 2)
286 data[0] |= cvt->buf[2];
287 return 4;
288 }
289
reset_rpn(struct ump_cvt_to_ump_bank * cc)290 static void reset_rpn(struct ump_cvt_to_ump_bank *cc)
291 {
292 cc->rpn_set = 0;
293 cc->nrpn_set = 0;
294 cc->cc_rpn_msb = cc->cc_rpn_lsb = 0;
295 cc->cc_data_msb = cc->cc_data_lsb = 0;
296 cc->cc_data_msb_set = cc->cc_data_lsb_set = 0;
297 }
298
fill_rpn(struct ump_cvt_to_ump_bank * cc,union snd_ump_midi2_msg * midi2,bool flush)299 static int fill_rpn(struct ump_cvt_to_ump_bank *cc,
300 union snd_ump_midi2_msg *midi2,
301 bool flush)
302 {
303 if (!(cc->cc_data_lsb_set || cc->cc_data_msb_set))
304 return 0; // skip
305 /* when not flushing, wait for complete data set */
306 if (!flush && (!cc->cc_data_lsb_set || !cc->cc_data_msb_set))
307 return 0; // skip
308
309 if (cc->rpn_set) {
310 midi2->rpn.status = UMP_MSG_STATUS_RPN;
311 midi2->rpn.bank = cc->cc_rpn_msb;
312 midi2->rpn.index = cc->cc_rpn_lsb;
313 } else if (cc->nrpn_set) {
314 midi2->rpn.status = UMP_MSG_STATUS_NRPN;
315 midi2->rpn.bank = cc->cc_nrpn_msb;
316 midi2->rpn.index = cc->cc_nrpn_lsb;
317 } else {
318 return 0; // skip
319 }
320
321 midi2->rpn.data = upscale_14_to_32bit((cc->cc_data_msb << 7) |
322 cc->cc_data_lsb);
323
324 reset_rpn(cc);
325 return 1;
326 }
327
328 /* convert to a MIDI 1.0 Channel Voice message */
cvt_legacy_cmd_to_ump(struct ump_cvt_to_ump * cvt,unsigned char group,unsigned int protocol,u32 * data,unsigned char bytes)329 static int cvt_legacy_cmd_to_ump(struct ump_cvt_to_ump *cvt,
330 unsigned char group,
331 unsigned int protocol,
332 u32 *data, unsigned char bytes)
333 {
334 const unsigned char *buf = cvt->buf;
335 struct ump_cvt_to_ump_bank *cc;
336 union snd_ump_midi2_msg *midi2 = (union snd_ump_midi2_msg *)data;
337 unsigned char status, channel;
338 int ret;
339
340 BUILD_BUG_ON(sizeof(union snd_ump_midi1_msg) != 4);
341 BUILD_BUG_ON(sizeof(union snd_ump_midi2_msg) != 8);
342
343 /* for MIDI 1.0 UMP, it's easy, just pack it into UMP */
344 if (protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI1) {
345 data[0] = ump_compose(UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE,
346 group, 0, buf[0]);
347 data[0] |= buf[1] << 8;
348 if (bytes > 2)
349 data[0] |= buf[2];
350 return 4;
351 }
352
353 status = *buf >> 4;
354 channel = *buf & 0x0f;
355 cc = &cvt->bank[channel];
356
357 /* special handling: treat note-on with 0 velocity as note-off */
358 if (status == UMP_MSG_STATUS_NOTE_ON && !buf[2])
359 status = UMP_MSG_STATUS_NOTE_OFF;
360
361 /* initialize the packet */
362 data[0] = ump_compose(UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE,
363 group, status, channel);
364 data[1] = 0;
365
366 switch (status) {
367 case UMP_MSG_STATUS_NOTE_ON:
368 case UMP_MSG_STATUS_NOTE_OFF:
369 midi2->note.note = buf[1];
370 midi2->note.velocity = upscale_7_to_16bit(buf[2]);
371 break;
372 case UMP_MSG_STATUS_POLY_PRESSURE:
373 midi2->paf.note = buf[1];
374 midi2->paf.data = upscale_7_to_32bit(buf[2]);
375 break;
376 case UMP_MSG_STATUS_CC:
377 switch (buf[1]) {
378 case UMP_CC_RPN_MSB:
379 ret = fill_rpn(cc, midi2, true);
380 cc->rpn_set = 1;
381 cc->cc_rpn_msb = buf[2];
382 if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f)
383 reset_rpn(cc);
384 return ret;
385 case UMP_CC_RPN_LSB:
386 ret = fill_rpn(cc, midi2, true);
387 cc->rpn_set = 1;
388 cc->cc_rpn_lsb = buf[2];
389 if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f)
390 reset_rpn(cc);
391 return ret;
392 case UMP_CC_NRPN_MSB:
393 ret = fill_rpn(cc, midi2, true);
394 cc->nrpn_set = 1;
395 cc->cc_nrpn_msb = buf[2];
396 return ret;
397 case UMP_CC_NRPN_LSB:
398 ret = fill_rpn(cc, midi2, true);
399 cc->nrpn_set = 1;
400 cc->cc_nrpn_lsb = buf[2];
401 return ret;
402 case UMP_CC_DATA:
403 cc->cc_data_msb_set = 1;
404 cc->cc_data_msb = buf[2];
405 return fill_rpn(cc, midi2, false);
406 case UMP_CC_BANK_SELECT:
407 cc->bank_set = 1;
408 cc->cc_bank_msb = buf[2];
409 return 0; // skip
410 case UMP_CC_BANK_SELECT_LSB:
411 cc->bank_set = 1;
412 cc->cc_bank_lsb = buf[2];
413 return 0; // skip
414 case UMP_CC_DATA_LSB:
415 cc->cc_data_lsb_set = 1;
416 cc->cc_data_lsb = buf[2];
417 return fill_rpn(cc, midi2, false);
418 default:
419 midi2->cc.index = buf[1];
420 midi2->cc.data = upscale_7_to_32bit(buf[2]);
421 break;
422 }
423 break;
424 case UMP_MSG_STATUS_PROGRAM:
425 midi2->pg.program = buf[1];
426 if (cc->bank_set) {
427 midi2->pg.bank_valid = 1;
428 midi2->pg.bank_msb = cc->cc_bank_msb;
429 midi2->pg.bank_lsb = cc->cc_bank_lsb;
430 cc->bank_set = 0;
431 }
432 break;
433 case UMP_MSG_STATUS_CHANNEL_PRESSURE:
434 midi2->caf.data = upscale_7_to_32bit(buf[1]);
435 break;
436 case UMP_MSG_STATUS_PITCH_BEND:
437 midi2->pb.data = upscale_14_to_32bit(buf[1] | (buf[2] << 7));
438 break;
439 default:
440 return 0;
441 }
442
443 return 8;
444 }
445
do_convert_to_ump(struct ump_cvt_to_ump * cvt,unsigned char group,unsigned int protocol,unsigned char c,u32 * data)446 static int do_convert_to_ump(struct ump_cvt_to_ump *cvt, unsigned char group,
447 unsigned int protocol, unsigned char c, u32 *data)
448 {
449 /* bytes for 0x80-0xf0 */
450 static unsigned char cmd_bytes[8] = {
451 3, 3, 3, 3, 2, 2, 3, 0
452 };
453 /* bytes for 0xf0-0xff */
454 static unsigned char system_bytes[16] = {
455 0, 2, 3, 2, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1
456 };
457 unsigned char bytes;
458
459 if (c == UMP_MIDI1_MSG_SYSEX_START) {
460 cvt->in_sysex = 1;
461 cvt->len = 0;
462 return 0;
463 }
464 if (c == UMP_MIDI1_MSG_SYSEX_END) {
465 if (!cvt->in_sysex)
466 return 0; /* skip */
467 return cvt_legacy_sysex_to_ump(cvt, group, data, true);
468 }
469
470 if ((c & 0xf0) == UMP_MIDI1_MSG_REALTIME) {
471 bytes = system_bytes[c & 0x0f];
472 if (!bytes)
473 return 0; /* skip */
474 if (bytes == 1) {
475 data[0] = ump_compose(UMP_MSG_TYPE_SYSTEM, group, 0, c);
476 return 4;
477 }
478 cvt->buf[0] = c;
479 cvt->len = 1;
480 cvt->cmd_bytes = bytes;
481 cvt->in_sysex = 0; /* abort SysEx */
482 return 0;
483 }
484
485 if (c & 0x80) {
486 bytes = cmd_bytes[(c >> 4) & 7];
487 cvt->buf[0] = c;
488 cvt->len = 1;
489 cvt->cmd_bytes = bytes;
490 cvt->in_sysex = 0; /* abort SysEx */
491 return 0;
492 }
493
494 if (cvt->in_sysex) {
495 cvt->buf[cvt->len++] = c;
496 if (cvt->len == 6)
497 return cvt_legacy_sysex_to_ump(cvt, group, data, false);
498 return 0;
499 }
500
501 if (!cvt->len)
502 return 0;
503
504 cvt->buf[cvt->len++] = c;
505 if (cvt->len < cvt->cmd_bytes)
506 return 0;
507 cvt->len = 1;
508 if ((cvt->buf[0] & 0xf0) == UMP_MIDI1_MSG_REALTIME)
509 return cvt_legacy_system_to_ump(cvt, group, data);
510 return cvt_legacy_cmd_to_ump(cvt, group, protocol, data, cvt->cmd_bytes);
511 }
512
513 /**
514 * snd_ump_convert_to_ump - convert legacy MIDI byte to UMP packet
515 * @cvt: converter context
516 * @group: target UMP group
517 * @protocol: target UMP protocol
518 * @c: MIDI 1.0 byte data
519 *
520 * Feed a MIDI 1.0 byte @c and convert to a UMP packet if completed.
521 * The result is stored in the buffer in @cvt.
522 */
snd_ump_convert_to_ump(struct ump_cvt_to_ump * cvt,unsigned char group,unsigned int protocol,unsigned char c)523 void snd_ump_convert_to_ump(struct ump_cvt_to_ump *cvt, unsigned char group,
524 unsigned int protocol, unsigned char c)
525 {
526 cvt->ump_bytes = do_convert_to_ump(cvt, group, protocol, c, cvt->ump);
527 }
528 EXPORT_SYMBOL_GPL(snd_ump_convert_to_ump);
529