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 */ 16 static u8 downscale_32_to_7bit(u32 src) 17 { 18 return src >> 25; 19 } 20 21 static u16 downscale_32_to_14bit(u32 src) 22 { 23 return src >> 18; 24 } 25 26 static u8 downscale_16_to_7bit(u16 src) 27 { 28 return src >> 9; 29 } 30 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 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 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 */ 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 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 */ 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