1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Helpers for UMP <-> MIDI 1.0 byte stream conversion
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>
14 * Upgrade / downgrade value bits
16 static u8 downscale_32_to_7bit(u32 src)
21 static u16 downscale_32_to_14bit(u32 src)
26 static u8 downscale_16_to_7bit(u16 src)
31 static u16 upscale_7_to_16bit(u8 src)
39 return val | (repeat << 3) | (repeat >> 3);
42 static u32 upscale_7_to_32bit(u8 src)
50 return val | (repeat << 19) | (repeat << 13) |
51 (repeat << 7) | (repeat << 1) | (repeat >> 5);
54 static u32 upscale_14_to_32bit(u16 src)
61 repeat = src & 0x1fff;
62 return val | (repeat << 5) | (repeat >> 8);
66 * UMP -> MIDI 1 byte stream conversion
68 /* convert a UMP System message to MIDI 1.0 byte stream */
69 static int cvt_ump_system_to_legacy(u32 data, unsigned char *buf)
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;
77 case UMP_SYSTEM_STATUS_SONG_POSITION:
78 buf[1] = (data >> 8) & 0x7f;
86 /* convert a UMP MIDI 1.0 Channel Voice message to MIDI 1.0 byte stream */
87 static int cvt_ump_midi1_to_legacy(u32 data, unsigned char *buf)
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:
101 /* convert a UMP MIDI 2.0 Channel Voice message to MIDI 1.0 byte stream */
102 static int cvt_ump_midi2_to_legacy(const union snd_ump_midi2_msg *midi2,
105 unsigned char status = midi2->note.status;
106 unsigned char channel = midi2->note.channel;
109 buf[0] = (status << 4) | channel;
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])
118 case UMP_MSG_STATUS_POLY_PRESSURE:
119 buf[1] = midi2->paf.note;
120 buf[2] = downscale_32_to_7bit(midi2->paf.data);
122 case UMP_MSG_STATUS_CC:
123 buf[1] = midi2->cc.index;
124 buf[2] = downscale_32_to_7bit(midi2->cc.data);
126 case UMP_MSG_STATUS_CHANNEL_PRESSURE:
127 buf[1] = downscale_32_to_7bit(midi2->caf.data);
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;
141 buf[1] = midi2->pg.program;
143 case UMP_MSG_STATUS_PITCH_BEND:
144 v = downscale_32_to_14bit(midi2->pb.data);
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;
154 buf[4] = status == UMP_MSG_STATUS_RPN ? UMP_CC_RPN_LSB : UMP_CC_NRPN_LSB;
155 buf[5] = midi2->rpn.index;
157 buf[7] = UMP_CC_DATA;
158 v = downscale_32_to_14bit(midi2->rpn.data);
161 buf[10] = UMP_CC_DATA_LSB;
169 /* convert a UMP 7-bit SysEx message to MIDI 1.0 byte stream */
170 static int cvt_ump_sysex7_to_legacy(const u32 *data, unsigned char *buf)
172 unsigned char status;
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);
184 if (status == UMP_SYSEX_STATUS_SINGLE ||
185 status == UMP_SYSEX_STATUS_START) {
186 buf[0] = UMP_MIDI1_MSG_SYSEX_START;
191 for (; bytes; bytes--, size++) {
192 buf[size] = (*data >> offset) & 0x7f;
201 if (status == UMP_SYSEX_STATUS_SINGLE ||
202 status == UMP_SYSEX_STATUS_END)
203 buf[size++] = UMP_MIDI1_MSG_SYSEX_END;
209 * snd_ump_convert_from_ump - convert from UMP to legacy MIDI
211 * @buf: buffer to store legacy MIDI data
212 * @group_ret: pointer to store the target group
214 * Convert from a UMP packet @data to MIDI 1.0 bytes at @buf.
215 * The target group is stored at @group_ret.
217 * The function returns the number of bytes of MIDI 1.0 stream.
219 int snd_ump_convert_from_ump(const u32 *data,
221 unsigned char *group_ret)
223 *group_ret = ump_message_group(*data);
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,
233 case UMP_MSG_TYPE_DATA:
234 return cvt_ump_sysex7_to_legacy(data, buf);
239 EXPORT_SYMBOL_GPL(snd_ump_convert_from_ump);
242 * MIDI 1 byte stream -> UMP conversion
244 /* convert MIDI 1.0 SysEx to a UMP packet */
245 static int cvt_legacy_sysex_to_ump(struct ump_cvt_to_ump *cvt,
246 unsigned char group, u32 *data, bool finish)
248 unsigned char status;
249 bool start = cvt->in_sysex == 1;
253 status = UMP_SYSEX_STATUS_SINGLE;
255 status = UMP_SYSEX_STATUS_START;
257 status = UMP_SYSEX_STATUS_END;
259 status = UMP_SYSEX_STATUS_CONTINUE;
260 *data = ump_compose(UMP_MSG_TYPE_DATA, group, status, cvt->len);
262 for (i = 0; i < cvt->len; i++) {
263 *data |= cvt->buf[i] << offset;
278 /* convert to a UMP System message */
279 static int cvt_legacy_system_to_ump(struct ump_cvt_to_ump *cvt,
280 unsigned char group, u32 *data)
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];
290 static void fill_rpn(struct ump_cvt_to_ump_bank *cc,
291 union snd_ump_midi2_msg *midi2)
294 midi2->rpn.status = UMP_MSG_STATUS_RPN;
295 midi2->rpn.bank = cc->cc_rpn_msb;
296 midi2->rpn.index = cc->cc_rpn_lsb;
298 cc->cc_rpn_msb = cc->cc_rpn_lsb = 0;
300 midi2->rpn.status = UMP_MSG_STATUS_NRPN;
301 midi2->rpn.bank = cc->cc_nrpn_msb;
302 midi2->rpn.index = cc->cc_nrpn_lsb;
304 cc->cc_nrpn_msb = cc->cc_nrpn_lsb = 0;
306 midi2->rpn.data = upscale_14_to_32bit((cc->cc_data_msb << 7) |
308 cc->cc_data_msb = cc->cc_data_lsb = 0;
311 /* convert to a MIDI 1.0 Channel Voice message */
312 static int cvt_legacy_cmd_to_ump(struct ump_cvt_to_ump *cvt,
314 unsigned int protocol,
315 u32 *data, unsigned char bytes)
317 const unsigned char *buf = cvt->buf;
318 struct ump_cvt_to_ump_bank *cc;
319 union snd_ump_midi2_msg *midi2 = (union snd_ump_midi2_msg *)data;
320 unsigned char status, channel;
322 BUILD_BUG_ON(sizeof(union snd_ump_midi1_msg) != 4);
323 BUILD_BUG_ON(sizeof(union snd_ump_midi2_msg) != 8);
325 /* for MIDI 1.0 UMP, it's easy, just pack it into UMP */
326 if (protocol & SNDRV_UMP_EP_INFO_PROTO_MIDI1) {
327 data[0] = ump_compose(UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE,
329 data[0] |= buf[1] << 8;
336 channel = *buf & 0x0f;
337 cc = &cvt->bank[channel];
339 /* special handling: treat note-on with 0 velocity as note-off */
340 if (status == UMP_MSG_STATUS_NOTE_ON && !buf[2])
341 status = UMP_MSG_STATUS_NOTE_OFF;
343 /* initialize the packet */
344 data[0] = ump_compose(UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE,
345 group, status, channel);
349 case UMP_MSG_STATUS_NOTE_ON:
350 case UMP_MSG_STATUS_NOTE_OFF:
351 midi2->note.note = buf[1];
352 midi2->note.velocity = upscale_7_to_16bit(buf[2]);
354 case UMP_MSG_STATUS_POLY_PRESSURE:
355 midi2->paf.note = buf[1];
356 midi2->paf.data = upscale_7_to_32bit(buf[2]);
358 case UMP_MSG_STATUS_CC:
362 cc->cc_rpn_msb = buf[2];
366 cc->cc_rpn_lsb = buf[2];
368 case UMP_CC_NRPN_MSB:
370 cc->cc_nrpn_msb = buf[2];
372 case UMP_CC_NRPN_LSB:
374 cc->cc_nrpn_lsb = buf[2];
377 cc->cc_data_msb = buf[2];
379 case UMP_CC_BANK_SELECT:
381 cc->cc_bank_msb = buf[2];
383 case UMP_CC_BANK_SELECT_LSB:
385 cc->cc_bank_lsb = buf[2];
387 case UMP_CC_DATA_LSB:
388 cc->cc_data_lsb = buf[2];
389 if (cc->rpn_set || cc->nrpn_set)
395 midi2->cc.index = buf[1];
396 midi2->cc.data = upscale_7_to_32bit(buf[2]);
400 case UMP_MSG_STATUS_PROGRAM:
401 midi2->pg.program = buf[1];
403 midi2->pg.bank_valid = 1;
404 midi2->pg.bank_msb = cc->cc_bank_msb;
405 midi2->pg.bank_lsb = cc->cc_bank_lsb;
407 cc->cc_bank_msb = cc->cc_bank_lsb = 0;
410 case UMP_MSG_STATUS_CHANNEL_PRESSURE:
411 midi2->caf.data = upscale_7_to_32bit(buf[1]);
413 case UMP_MSG_STATUS_PITCH_BEND:
414 midi2->pb.data = upscale_14_to_32bit(buf[1] | (buf[2] << 7));
423 static int do_convert_to_ump(struct ump_cvt_to_ump *cvt, unsigned char group,
424 unsigned int protocol, unsigned char c, u32 *data)
426 /* bytes for 0x80-0xf0 */
427 static unsigned char cmd_bytes[8] = {
428 3, 3, 3, 3, 2, 2, 3, 0
430 /* bytes for 0xf0-0xff */
431 static unsigned char system_bytes[16] = {
432 0, 2, 3, 2, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1
436 if (c == UMP_MIDI1_MSG_SYSEX_START) {
441 if (c == UMP_MIDI1_MSG_SYSEX_END) {
444 return cvt_legacy_sysex_to_ump(cvt, group, data, true);
447 if ((c & 0xf0) == UMP_MIDI1_MSG_REALTIME) {
448 bytes = system_bytes[c & 0x0f];
452 data[0] = ump_compose(UMP_MSG_TYPE_SYSTEM, group, 0, c);
457 cvt->cmd_bytes = bytes;
458 cvt->in_sysex = 0; /* abort SysEx */
463 bytes = cmd_bytes[(c >> 4) & 7];
466 cvt->cmd_bytes = bytes;
467 cvt->in_sysex = 0; /* abort SysEx */
472 cvt->buf[cvt->len++] = c;
474 return cvt_legacy_sysex_to_ump(cvt, group, data, false);
481 cvt->buf[cvt->len++] = c;
482 if (cvt->len < cvt->cmd_bytes)
485 if ((cvt->buf[0] & 0xf0) == UMP_MIDI1_MSG_REALTIME)
486 return cvt_legacy_system_to_ump(cvt, group, data);
487 return cvt_legacy_cmd_to_ump(cvt, group, protocol, data, cvt->cmd_bytes);
491 * snd_ump_convert_to_ump - convert legacy MIDI byte to UMP packet
492 * @cvt: converter context
493 * @group: target UMP group
494 * @protocol: target UMP protocol
495 * @c: MIDI 1.0 byte data
497 * Feed a MIDI 1.0 byte @c and convert to a UMP packet if completed.
498 * The result is stored in the buffer in @cvt.
500 void snd_ump_convert_to_ump(struct ump_cvt_to_ump *cvt, unsigned char group,
501 unsigned int protocol, unsigned char c)
503 cvt->ump_bytes = do_convert_to_ump(cvt, group, protocol, c, cvt->ump);
505 EXPORT_SYMBOL_GPL(snd_ump_convert_to_ump);