529 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Helpers for UMP <-> MIDI 1.0 byte stream conversion4 */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 bits15 */16static u8 downscale_32_to_7bit(u32 src)17{18 return src >> 25;19}20 21static u16 downscale_32_to_14bit(u32 src)22{23 return src >> 18;24}25 26static u8 downscale_16_to_7bit(u16 src)27{28 return src >> 9;29}30 31static 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 42static 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 54static 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 conversion67 */68/* convert a UMP System message to MIDI 1.0 byte stream */69static 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 */87static 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 */102static 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 */170static 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, skip179 bytes = ump_sysex_message_length(*data);180 if (bytes > 6)181 return 0; // skip182 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 MIDI210 * @data: UMP packet211 * @buf: buffer to store legacy MIDI data212 * @group_ret: pointer to store the target group213 *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 */219int 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}239EXPORT_SYMBOL_GPL(snd_ump_convert_from_ump);240 241/*242 * MIDI 1 byte stream -> UMP conversion243 */244/* convert MIDI 1.0 SysEx to a UMP packet */245static 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 else259 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 } else268 offset -= 8;269 }270 cvt->len = 0;271 if (finish)272 cvt->in_sysex = 0;273 else274 cvt->in_sysex++;275 return 8;276}277 278/* convert to a UMP System message */279static 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 290static 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 299static 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; // skip305 /* when not flushing, wait for complete data set */306 if (!flush && (!cc->cc_data_lsb_set || !cc->cc_data_msb_set))307 return 0; // skip308 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; // skip319 }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 */329static 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; // skip410 case UMP_CC_BANK_SELECT_LSB:411 cc->bank_set = 1;412 cc->cc_bank_lsb = buf[2];413 return 0; // skip414 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 446static 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, 0452 };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, 1456 };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 packet515 * @cvt: converter context516 * @group: target UMP group517 * @protocol: target UMP protocol518 * @c: MIDI 1.0 byte data519 *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 */523void 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}528EXPORT_SYMBOL_GPL(snd_ump_convert_to_ump);529