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 */
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 conversion
67 */
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, 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 */
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 conversion
243 */
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	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 */
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 fill_rpn(struct ump_cvt_to_ump_bank *cc,
291		     union snd_ump_midi2_msg *midi2)
292{
293	if (cc->rpn_set) {
294		midi2->rpn.status = UMP_MSG_STATUS_RPN;
295		midi2->rpn.bank = cc->cc_rpn_msb;
296		midi2->rpn.index = cc->cc_rpn_lsb;
297		cc->rpn_set = 0;
298		cc->cc_rpn_msb = cc->cc_rpn_lsb = 0;
299	} else {
300		midi2->rpn.status = UMP_MSG_STATUS_NRPN;
301		midi2->rpn.bank = cc->cc_nrpn_msb;
302		midi2->rpn.index = cc->cc_nrpn_lsb;
303		cc->nrpn_set = 0;
304		cc->cc_nrpn_msb = cc->cc_nrpn_lsb = 0;
305	}
306	midi2->rpn.data = upscale_14_to_32bit((cc->cc_data_msb << 7) |
307					      cc->cc_data_lsb);
308	cc->cc_data_msb = cc->cc_data_lsb = 0;
309}
310
311/* convert to a MIDI 1.0 Channel Voice message */
312static int cvt_legacy_cmd_to_ump(struct ump_cvt_to_ump *cvt,
313				 unsigned char group,
314				 unsigned int protocol,
315				 u32 *data, unsigned char bytes)
316{
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;
321
322	BUILD_BUG_ON(sizeof(union snd_ump_midi1_msg) != 4);
323	BUILD_BUG_ON(sizeof(union snd_ump_midi2_msg) != 8);
324
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,
328				      group, 0, buf[0]);
329		data[0] |= buf[1] << 8;
330		if (bytes > 2)
331			data[0] |= buf[2];
332		return 4;
333	}
334
335	status = *buf >> 4;
336	channel = *buf & 0x0f;
337	cc = &cvt->bank[channel];
338
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;
342
343	/* initialize the packet */
344	data[0] = ump_compose(UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE,
345			      group, status, channel);
346	data[1] = 0;
347
348	switch (status) {
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]);
353		break;
354	case UMP_MSG_STATUS_POLY_PRESSURE:
355		midi2->paf.note = buf[1];
356		midi2->paf.data = upscale_7_to_32bit(buf[2]);
357		break;
358	case UMP_MSG_STATUS_CC:
359		switch (buf[1]) {
360		case UMP_CC_RPN_MSB:
361			cc->rpn_set = 1;
362			cc->cc_rpn_msb = buf[2];
363			return 0; // skip
364		case UMP_CC_RPN_LSB:
365			cc->rpn_set = 1;
366			cc->cc_rpn_lsb = buf[2];
367			return 0; // skip
368		case UMP_CC_NRPN_MSB:
369			cc->nrpn_set = 1;
370			cc->cc_nrpn_msb = buf[2];
371			return 0; // skip
372		case UMP_CC_NRPN_LSB:
373			cc->nrpn_set = 1;
374			cc->cc_nrpn_lsb = buf[2];
375			return 0; // skip
376		case UMP_CC_DATA:
377			cc->cc_data_msb = buf[2];
378			return 0; // skip
379		case UMP_CC_BANK_SELECT:
380			cc->bank_set = 1;
381			cc->cc_bank_msb = buf[2];
382			return 0; // skip
383		case UMP_CC_BANK_SELECT_LSB:
384			cc->bank_set = 1;
385			cc->cc_bank_lsb = buf[2];
386			return 0; // skip
387		case UMP_CC_DATA_LSB:
388			cc->cc_data_lsb = buf[2];
389			if (cc->rpn_set || cc->nrpn_set)
390				fill_rpn(cc, midi2);
391			else
392				return 0; // skip
393			break;
394		default:
395			midi2->cc.index = buf[1];
396			midi2->cc.data = upscale_7_to_32bit(buf[2]);
397			break;
398		}
399		break;
400	case UMP_MSG_STATUS_PROGRAM:
401		midi2->pg.program = buf[1];
402		if (cc->bank_set) {
403			midi2->pg.bank_valid = 1;
404			midi2->pg.bank_msb = cc->cc_bank_msb;
405			midi2->pg.bank_lsb = cc->cc_bank_lsb;
406			cc->bank_set = 0;
407			cc->cc_bank_msb = cc->cc_bank_lsb = 0;
408		}
409		break;
410	case UMP_MSG_STATUS_CHANNEL_PRESSURE:
411		midi2->caf.data = upscale_7_to_32bit(buf[1]);
412		break;
413	case UMP_MSG_STATUS_PITCH_BEND:
414		midi2->pb.data = upscale_14_to_32bit(buf[1] | (buf[2] << 7));
415		break;
416	default:
417		return 0;
418	}
419
420	return 8;
421}
422
423static int do_convert_to_ump(struct ump_cvt_to_ump *cvt, unsigned char group,
424			     unsigned int protocol, unsigned char c, u32 *data)
425{
426	/* bytes for 0x80-0xf0 */
427	static unsigned char cmd_bytes[8] = {
428		3, 3, 3, 3, 2, 2, 3, 0
429	};
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
433	};
434	unsigned char bytes;
435
436	if (c == UMP_MIDI1_MSG_SYSEX_START) {
437		cvt->in_sysex = 1;
438		cvt->len = 0;
439		return 0;
440	}
441	if (c == UMP_MIDI1_MSG_SYSEX_END) {
442		if (!cvt->in_sysex)
443			return 0; /* skip */
444		return cvt_legacy_sysex_to_ump(cvt, group, data, true);
445	}
446
447	if ((c & 0xf0) == UMP_MIDI1_MSG_REALTIME) {
448		bytes = system_bytes[c & 0x0f];
449		if (!bytes)
450			return 0; /* skip */
451		if (bytes == 1) {
452			data[0] = ump_compose(UMP_MSG_TYPE_SYSTEM, group, 0, c);
453			return 4;
454		}
455		cvt->buf[0] = c;
456		cvt->len = 1;
457		cvt->cmd_bytes = bytes;
458		cvt->in_sysex = 0; /* abort SysEx */
459		return 0;
460	}
461
462	if (c & 0x80) {
463		bytes = cmd_bytes[(c >> 4) & 7];
464		cvt->buf[0] = c;
465		cvt->len = 1;
466		cvt->cmd_bytes = bytes;
467		cvt->in_sysex = 0; /* abort SysEx */
468		return 0;
469	}
470
471	if (cvt->in_sysex) {
472		cvt->buf[cvt->len++] = c;
473		if (cvt->len == 6)
474			return cvt_legacy_sysex_to_ump(cvt, group, data, false);
475		return 0;
476	}
477
478	if (!cvt->len)
479		return 0;
480
481	cvt->buf[cvt->len++] = c;
482	if (cvt->len < cvt->cmd_bytes)
483		return 0;
484	cvt->len = 1;
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);
488}
489
490/**
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
496 *
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.
499 */
500void snd_ump_convert_to_ump(struct ump_cvt_to_ump *cvt, unsigned char group,
501			    unsigned int protocol, unsigned char c)
502{
503	cvt->ump_bytes = do_convert_to_ump(cvt, group, protocol, c, cvt->ump);
504}
505EXPORT_SYMBOL_GPL(snd_ump_convert_to_ump);
506