diff mbox series

[29/36] ALSA: seq: Automatic conversion of UMP events

Message ID 20230519093114.28813-30-tiwai@suse.de (mailing list archive)
State Superseded
Headers show
Series ALSA: Add MIDI 2.0 support | expand

Commit Message

Takashi Iwai May 19, 2023, 9:31 a.m. UTC
This patch enables the automatic conversion of UMP events from/to the
legacy ALSA sequencer MIDI events.  Also, as UMP itself has two
different modes (MIDI 1.0 and MIDI 2.0), yet another converters
between them are needed, too.  Namely, we have conversions between the
legacy and UMP like:
  - seq legacy event -> seq UMP MIDI 1.0 event
  - seq legacy event -> seq UMP MIDI 2.0 event
  - seq UMP MIDI 1.0 event -> seq legacy event
  - seq UMP MIDI 2.0 event -> seq legacy event

and the conversions between UMP MIDI 1.0 and 2.0 clients like:
  - seq UMP MIDI 1.0 event -> seq UMP MIDI 2.0 event
  - seq UMP MIDI 2.0 event -> seq UMP MIDI 1.0 event

The translation is per best-effort; some MIDI 2.0 specific events are
ignored when translated to MIDI 1.0.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
---
 sound/core/seq/Kconfig           |    2 +
 sound/core/seq/Makefile          |    1 +
 sound/core/seq/seq_clientmgr.c   |   50 +-
 sound/core/seq/seq_clientmgr.h   |   15 +
 sound/core/seq/seq_ports.h       |   15 +
 sound/core/seq/seq_ump_convert.c | 1190 ++++++++++++++++++++++++++++++
 sound/core/seq/seq_ump_convert.h |   22 +
 7 files changed, 1280 insertions(+), 15 deletions(-)
 create mode 100644 sound/core/seq/seq_ump_convert.c
 create mode 100644 sound/core/seq/seq_ump_convert.h

Comments

Jaroslav Kysela May 22, 2023, 7:48 a.m. UTC | #1
On 19. 05. 23 11:31, Takashi Iwai wrote:
> This patch enables the automatic conversion of UMP events from/to the
> legacy ALSA sequencer MIDI events.  Also, as UMP itself has two
> different modes (MIDI 1.0 and MIDI 2.0), yet another converters
> between them are needed, too.  Namely, we have conversions between the
> legacy and UMP like:
>    - seq legacy event -> seq UMP MIDI 1.0 event
>    - seq legacy event -> seq UMP MIDI 2.0 event
>    - seq UMP MIDI 1.0 event -> seq legacy event
>    - seq UMP MIDI 2.0 event -> seq legacy event
> 
> and the conversions between UMP MIDI 1.0 and 2.0 clients like:
>    - seq UMP MIDI 1.0 event -> seq UMP MIDI 2.0 event
>    - seq UMP MIDI 2.0 event -> seq UMP MIDI 1.0 event
> 
> The translation is per best-effort; some MIDI 2.0 specific events are
> ignored when translated to MIDI 1.0.
> 
> Signed-off-by: Takashi Iwai <tiwai@suse.de>

Reviewed-by: Jaroslav Kysela <perex@perex.cz>
diff mbox series

Patch

diff --git a/sound/core/seq/Kconfig b/sound/core/seq/Kconfig
index c69d8beb09fa..f8336134153e 100644
--- a/sound/core/seq/Kconfig
+++ b/sound/core/seq/Kconfig
@@ -66,5 +66,7 @@  config SND_SEQ_UMP
 	  Say Y here to enable the support for handling UMP (Universal MIDI
 	  Packet) events via ALSA sequencer infrastructure, which is an
 	  essential feature for enabling MIDI 2.0 support.
+	  It includes the automatic conversion of ALSA sequencer events
+	  among legacy and UMP clients.
 
 endif # SND_SEQUENCER
diff --git a/sound/core/seq/Makefile b/sound/core/seq/Makefile
index 3a2177a7e50c..ba264a695643 100644
--- a/sound/core/seq/Makefile
+++ b/sound/core/seq/Makefile
@@ -8,6 +8,7 @@  snd-seq-objs := seq.o seq_lock.o seq_clientmgr.o seq_memory.o seq_queue.o \
                 seq_fifo.o seq_prioq.o seq_timer.o \
                 seq_system.o seq_ports.o
 snd-seq-$(CONFIG_SND_PROC_FS) += seq_info.o
+snd-seq-$(CONFIG_SND_SEQ_UMP) += seq_ump_convert.o
 snd-seq-midi-objs := seq_midi.o
 snd-seq-midi-emul-objs := seq_midi_emul.o
 snd-seq-midi-event-objs := seq_midi_event.o
diff --git a/sound/core/seq/seq_clientmgr.c b/sound/core/seq/seq_clientmgr.c
index 33aa6c5c5c9e..07b090f76b5f 100644
--- a/sound/core/seq/seq_clientmgr.c
+++ b/sound/core/seq/seq_clientmgr.c
@@ -20,6 +20,7 @@ 
 #include "seq_timer.h"
 #include "seq_info.h"
 #include "seq_system.h"
+#include "seq_ump_convert.h"
 #include <sound/seq_device.h>
 #ifdef CONFIG_COMPAT
 #include <linux/compat.h>
@@ -612,6 +613,27 @@  static int update_timestamp_of_queue(struct snd_seq_event *event,
 	return 1;
 }
 
+/* deliver a single event; called from below and UMP converter */
+int __snd_seq_deliver_single_event(struct snd_seq_client *dest,
+				   struct snd_seq_client_port *dest_port,
+				   struct snd_seq_event *event,
+				   int atomic, int hop)
+{
+	switch (dest->type) {
+	case USER_CLIENT:
+		if (!dest->data.user.fifo)
+			return 0;
+		return snd_seq_fifo_event_in(dest->data.user.fifo, event);
+	case KERNEL_CLIENT:
+		if (!dest_port->event_input)
+			return 0;
+		return dest_port->event_input(event,
+					      snd_seq_ev_is_direct(event),
+					      dest_port->private_data,
+					      atomic, hop);
+	}
+	return 0;
+}
 
 /*
  * deliver an event to the specified destination.
@@ -648,22 +670,20 @@  static int snd_seq_deliver_single_event(struct snd_seq_client *client,
 		update_timestamp_of_queue(event, dest_port->time_queue,
 					  dest_port->time_real);
 
-	switch (dest->type) {
-	case USER_CLIENT:
-		if (dest->data.user.fifo)
-			result = snd_seq_fifo_event_in(dest->data.user.fifo, event);
-		break;
-
-	case KERNEL_CLIENT:
-		if (dest_port->event_input == NULL)
-			break;
-		result = dest_port->event_input(event, direct,
-						dest_port->private_data,
-						atomic, hop);
-		break;
-	default:
-		break;
+#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
+	if (snd_seq_ev_is_ump(event)) {
+		result = snd_seq_deliver_from_ump(client, dest, dest_port,
+						  event, atomic, hop);
+		goto __skip;
+	} else if (snd_seq_client_is_ump(dest)) {
+		result = snd_seq_deliver_to_ump(client, dest, dest_port,
+						event, atomic, hop);
+		goto __skip;
 	}
+#endif /* CONFIG_SND_SEQ_UMP */
+
+	result = __snd_seq_deliver_single_event(dest, dest_port, event,
+						atomic, hop);
 
   __skip:
 	if (dest_port)
diff --git a/sound/core/seq/seq_clientmgr.h b/sound/core/seq/seq_clientmgr.h
index bb973d36ce78..97762892ffab 100644
--- a/sound/core/seq/seq_clientmgr.h
+++ b/sound/core/seq/seq_clientmgr.h
@@ -85,6 +85,11 @@  int snd_seq_kernel_client_write_poll(int clientid, struct file *file, poll_table
 int snd_seq_client_notify_subscription(int client, int port,
 				       struct snd_seq_port_subscribe *info, int evtype);
 
+int __snd_seq_deliver_single_event(struct snd_seq_client *dest,
+				   struct snd_seq_client_port *dest_port,
+				   struct snd_seq_event *event,
+				   int atomic, int hop);
+
 /* only for OSS sequencer */
 bool snd_seq_client_ioctl_lock(int clientid);
 void snd_seq_client_ioctl_unlock(int clientid);
@@ -95,4 +100,14 @@  extern int seq_client_load[15];
 struct snd_seq_client *snd_seq_kernel_client_get(int client);
 void snd_seq_kernel_client_put(struct snd_seq_client *cptr);
 
+static inline bool snd_seq_client_is_ump(struct snd_seq_client *c)
+{
+	return c->midi_version != SNDRV_SEQ_CLIENT_LEGACY_MIDI;
+}
+
+static inline bool snd_seq_client_is_midi2(struct snd_seq_client *c)
+{
+	return c->midi_version == SNDRV_SEQ_CLIENT_UMP_MIDI_2_0;
+}
+
 #endif
diff --git a/sound/core/seq/seq_ports.h b/sound/core/seq/seq_ports.h
index c6c138edceab..b111382f697a 100644
--- a/sound/core/seq/seq_ports.h
+++ b/sound/core/seq/seq_ports.h
@@ -42,6 +42,17 @@  struct snd_seq_port_subs_info {
 	int (*close)(void *private_data, struct snd_seq_port_subscribe *info);
 };
 
+/* context for converting from legacy control event to UMP packet */
+struct snd_seq_ump_midi2_bank {
+	bool rpn_set;
+	bool nrpn_set;
+	bool bank_set;
+	unsigned char cc_rpn_msb, cc_rpn_lsb;
+	unsigned char cc_nrpn_msb, cc_nrpn_lsb;
+	unsigned char cc_data_msb, cc_data_lsb;
+	unsigned char cc_bank_msb, cc_bank_lsb;
+};
+
 struct snd_seq_client_port {
 
 	struct snd_seq_addr addr;	/* client/port number */
@@ -75,6 +86,10 @@  struct snd_seq_client_port {
 	/* UMP direction and group */
 	unsigned char direction;
 	unsigned char ump_group;
+
+#if IS_ENABLED(CONFIG_SND_SEQ_UMP)
+	struct snd_seq_ump_midi2_bank midi2_bank[16]; /* per channel */
+#endif
 };
 
 struct snd_seq_client;
diff --git a/sound/core/seq/seq_ump_convert.c b/sound/core/seq/seq_ump_convert.c
new file mode 100644
index 000000000000..433fe842947e
--- /dev/null
+++ b/sound/core/seq/seq_ump_convert.c
@@ -0,0 +1,1190 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA sequencer event conversion between UMP and legacy clients
+ */
+
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <sound/core.h>
+#include <sound/ump.h>
+#include <sound/ump_msg.h>
+#include "seq_ump_convert.h"
+
+/*
+ * Upgrade / downgrade value bits
+ */
+static u8 downscale_32_to_7bit(u32 src)
+{
+	return src >> 25;
+}
+
+static u16 downscale_32_to_14bit(u32 src)
+{
+	return src >> 18;
+}
+
+static u8 downscale_16_to_7bit(u16 src)
+{
+	return src >> 9;
+}
+
+static u16 upscale_7_to_16bit(u8 src)
+{
+	u16 val, repeat;
+
+	val = (u16)src << 9;
+	if (src <= 0x40)
+		return val;
+	repeat = src & 0x3f;
+	return val | (repeat << 3) | (repeat >> 3);
+}
+
+static u32 upscale_7_to_32bit(u8 src)
+{
+	u32 val, repeat;
+
+	val = src << 25;
+	if (src <= 0x40)
+		return val;
+	repeat = src & 0x3f;
+	return val | (repeat << 19) | (repeat << 13) |
+		(repeat << 7) | (repeat << 1) | (repeat >> 5);
+}
+
+static u32 upscale_14_to_32bit(u16 src)
+{
+	u32 val, repeat;
+
+	val = src << 18;
+	if (src <= 0x2000)
+		return val;
+	repeat = src & 0x1fff;
+	return val | (repeat << 5) | (repeat >> 8);
+}
+
+static unsigned char get_ump_group(struct snd_seq_client_port *port)
+{
+	return port->ump_group ? (port->ump_group - 1) : 0;
+}
+
+/* create a UMP header */
+#define make_raw_ump(port, type) \
+	ump_compose(type, get_ump_group(port), 0, 0)
+
+/*
+ * UMP -> MIDI1 sequencer event
+ */
+
+/* MIDI 1.0 CVM */
+
+/* encode note event */
+static void ump_midi1_to_note_ev(const union snd_ump_midi1_msg *val,
+				 struct snd_seq_event *ev)
+{
+	ev->data.note.channel = val->note.channel;
+	ev->data.note.note = val->note.note;
+	ev->data.note.velocity = val->note.velocity;
+}
+
+/* encode one parameter controls */
+static void ump_midi1_to_ctrl_ev(const union snd_ump_midi1_msg *val,
+				 struct snd_seq_event *ev)
+{
+	ev->data.control.channel = val->caf.channel;
+	ev->data.control.value = val->caf.data;
+}
+
+/* encode pitch wheel change */
+static void ump_midi1_to_pitchbend_ev(const union snd_ump_midi1_msg *val,
+				      struct snd_seq_event *ev)
+{
+	ev->data.control.channel = val->pb.channel;
+	ev->data.control.value = (val->pb.data_msb << 7) | val->pb.data_lsb;
+	ev->data.control.value -= 8192;
+}
+
+/* encode midi control change */
+static void ump_midi1_to_cc_ev(const union snd_ump_midi1_msg *val,
+			       struct snd_seq_event *ev)
+{
+	ev->data.control.channel = val->cc.channel;
+	ev->data.control.param = val->cc.index;
+	ev->data.control.value = val->cc.data;
+}
+
+/* Encoding MIDI 1.0 UMP packet */
+struct seq_ump_midi1_to_ev {
+	int seq_type;
+	void (*encode)(const union snd_ump_midi1_msg *val, struct snd_seq_event *ev);
+};
+
+/* Encoders for MIDI1 status 0x80-0xe0 */
+static struct seq_ump_midi1_to_ev midi1_msg_encoders[] = {
+	{SNDRV_SEQ_EVENT_NOTEOFF,	ump_midi1_to_note_ev},	/* 0x80 */
+	{SNDRV_SEQ_EVENT_NOTEON,	ump_midi1_to_note_ev},	/* 0x90 */
+	{SNDRV_SEQ_EVENT_KEYPRESS,	ump_midi1_to_note_ev},	/* 0xa0 */
+	{SNDRV_SEQ_EVENT_CONTROLLER,	ump_midi1_to_cc_ev},	/* 0xb0 */
+	{SNDRV_SEQ_EVENT_PGMCHANGE,	ump_midi1_to_ctrl_ev},	/* 0xc0 */
+	{SNDRV_SEQ_EVENT_CHANPRESS,	ump_midi1_to_ctrl_ev},	/* 0xd0 */
+	{SNDRV_SEQ_EVENT_PITCHBEND,	ump_midi1_to_pitchbend_ev}, /* 0xe0 */
+};
+
+static int cvt_ump_midi1_to_event(const union snd_ump_midi1_msg *val,
+				  struct snd_seq_event *ev)
+{
+	unsigned char status = val->note.status;
+
+	if (status < 0x8 || status > 0xe)
+		return 0; /* invalid - skip */
+	status -= 8;
+	ev->type = midi1_msg_encoders[status].seq_type;
+	ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED;
+	midi1_msg_encoders[status].encode(val, ev);
+	return 1;
+}
+
+/* MIDI System message */
+
+/* encode one parameter value*/
+static void ump_system_to_one_param_ev(const union snd_ump_midi1_msg *val,
+				       struct snd_seq_event *ev)
+{
+	ev->data.control.value = val->system.parm1;
+}
+
+/* encode song position */
+static void ump_system_to_songpos_ev(const union snd_ump_midi1_msg *val,
+				     struct snd_seq_event *ev)
+{
+	ev->data.control.value = (val->system.parm1 << 7) | val->system.parm2;
+}
+
+/* Encoders for 0xf0 - 0xff */
+static struct seq_ump_midi1_to_ev system_msg_encoders[] = {
+	{SNDRV_SEQ_EVENT_NONE,		NULL},	 /* 0xf0 */
+	{SNDRV_SEQ_EVENT_QFRAME,	ump_system_to_one_param_ev}, /* 0xf1 */
+	{SNDRV_SEQ_EVENT_SONGPOS,	ump_system_to_songpos_ev}, /* 0xf2 */
+	{SNDRV_SEQ_EVENT_SONGSEL,	ump_system_to_one_param_ev}, /* 0xf3 */
+	{SNDRV_SEQ_EVENT_NONE,		NULL}, /* 0xf4 */
+	{SNDRV_SEQ_EVENT_NONE,		NULL}, /* 0xf5 */
+	{SNDRV_SEQ_EVENT_TUNE_REQUEST,	NULL}, /* 0xf6 */
+	{SNDRV_SEQ_EVENT_NONE,		NULL}, /* 0xf7 */
+	{SNDRV_SEQ_EVENT_CLOCK,		NULL}, /* 0xf8 */
+	{SNDRV_SEQ_EVENT_NONE,		NULL}, /* 0xf9 */
+	{SNDRV_SEQ_EVENT_START,		NULL}, /* 0xfa */
+	{SNDRV_SEQ_EVENT_CONTINUE,	NULL}, /* 0xfb */
+	{SNDRV_SEQ_EVENT_STOP,		NULL}, /* 0xfc */
+	{SNDRV_SEQ_EVENT_NONE,		NULL}, /* 0xfd */
+	{SNDRV_SEQ_EVENT_SENSING,	NULL}, /* 0xfe */
+	{SNDRV_SEQ_EVENT_RESET,		NULL}, /* 0xff */
+};
+
+static int cvt_ump_system_to_event(const union snd_ump_midi1_msg *val,
+				   struct snd_seq_event *ev)
+{
+	unsigned char status = val->system.status;
+
+	if ((status & 0xf0) != UMP_MIDI1_MSG_REALTIME)
+		return 0; /* invalid status - skip */
+	status &= 0x0f;
+	ev->type = system_msg_encoders[status].seq_type;
+	ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED;
+	if (ev->type == SNDRV_SEQ_EVENT_NONE)
+		return 0;
+	if (system_msg_encoders[status].encode)
+		system_msg_encoders[status].encode(val, ev);
+	return 1;
+}
+
+/* MIDI 2.0 CVM */
+
+/* encode note event */
+static int ump_midi2_to_note_ev(const union snd_ump_midi2_msg *val,
+				struct snd_seq_event *ev)
+{
+	ev->data.note.channel = val->note.channel;
+	ev->data.note.note = val->note.note;
+	ev->data.note.velocity = downscale_16_to_7bit(val->note.velocity);
+	/* correct note-on velocity 0 to 1;
+	 * it's no longer equivalent as not-off for MIDI 2.0
+	 */
+	if (ev->type == SNDRV_SEQ_EVENT_NOTEON &&
+	    !ev->data.note.velocity)
+		ev->data.note.velocity = 1;
+	return 1;
+}
+
+/* encode pitch wheel change */
+static int ump_midi2_to_pitchbend_ev(const union snd_ump_midi2_msg *val,
+				     struct snd_seq_event *ev)
+{
+	ev->data.control.channel = val->pb.channel;
+	ev->data.control.value = downscale_32_to_14bit(val->pb.data);
+	ev->data.control.value -= 8192;
+	return 1;
+}
+
+/* encode midi control change */
+static int ump_midi2_to_cc_ev(const union snd_ump_midi2_msg *val,
+			      struct snd_seq_event *ev)
+{
+	ev->data.control.channel = val->cc.channel;
+	ev->data.control.param = val->cc.index;
+	ev->data.control.value = downscale_32_to_7bit(val->cc.data);
+	return 1;
+}
+
+/* encode midi program change */
+static int ump_midi2_to_pgm_ev(const union snd_ump_midi2_msg *val,
+			       struct snd_seq_event *ev)
+{
+	int size = 1;
+
+	ev->data.control.channel = val->pg.channel;
+	if (val->pg.bank_valid) {
+		ev->type = SNDRV_SEQ_EVENT_CONTROL14;
+		ev->data.control.param = UMP_CC_BANK_SELECT;
+		ev->data.control.value = (val->pg.bank_msb << 7) | val->pg.bank_lsb;
+		ev[1] = ev[0];
+		ev++;
+		ev->type = SNDRV_SEQ_EVENT_PGMCHANGE;
+		size = 2;
+	}
+	ev->data.control.value = val->pg.program;
+	return size;
+}
+
+/* encode one parameter controls */
+static int ump_midi2_to_ctrl_ev(const union snd_ump_midi2_msg *val,
+				struct snd_seq_event *ev)
+{
+	ev->data.control.channel = val->caf.channel;
+	ev->data.control.value = downscale_32_to_7bit(val->caf.data);
+	return 1;
+}
+
+/* encode RPN/NRPN */
+static int ump_midi2_to_rpn_ev(const union snd_ump_midi2_msg *val,
+			       struct snd_seq_event *ev)
+{
+	ev->data.control.channel = val->rpn.channel;
+	ev->data.control.param = (val->rpn.bank << 7) | val->rpn.index;
+	ev->data.control.value = downscale_32_to_14bit(val->rpn.data);
+	return 1;
+}
+
+/* Encoding MIDI 2.0 UMP Packet */
+struct seq_ump_midi2_to_ev {
+	int seq_type;
+	int (*encode)(const union snd_ump_midi2_msg *val, struct snd_seq_event *ev);
+};
+
+/* Encoders for MIDI2 status 0x00-0xf0 */
+static struct seq_ump_midi2_to_ev midi2_msg_encoders[] = {
+	{SNDRV_SEQ_EVENT_NONE,		NULL},			/* 0x00 */
+	{SNDRV_SEQ_EVENT_NONE,		NULL},			/* 0x10 */
+	{SNDRV_SEQ_EVENT_REGPARAM,	ump_midi2_to_rpn_ev},	/* 0x20 */
+	{SNDRV_SEQ_EVENT_NONREGPARAM,	ump_midi2_to_rpn_ev},	/* 0x30 */
+	{SNDRV_SEQ_EVENT_NONE,		NULL},			/* 0x40 */
+	{SNDRV_SEQ_EVENT_NONE,		NULL},			/* 0x50 */
+	{SNDRV_SEQ_EVENT_NONE,		NULL},			/* 0x60 */
+	{SNDRV_SEQ_EVENT_NONE,		NULL},			/* 0x70 */
+	{SNDRV_SEQ_EVENT_NOTEOFF,	ump_midi2_to_note_ev},	/* 0x80 */
+	{SNDRV_SEQ_EVENT_NOTEON,	ump_midi2_to_note_ev},	/* 0x90 */
+	{SNDRV_SEQ_EVENT_KEYPRESS,	ump_midi2_to_note_ev},	/* 0xa0 */
+	{SNDRV_SEQ_EVENT_CONTROLLER,	ump_midi2_to_cc_ev},	/* 0xb0 */
+	{SNDRV_SEQ_EVENT_PGMCHANGE,	ump_midi2_to_pgm_ev},	/* 0xc0 */
+	{SNDRV_SEQ_EVENT_CHANPRESS,	ump_midi2_to_ctrl_ev},	/* 0xd0 */
+	{SNDRV_SEQ_EVENT_PITCHBEND,	ump_midi2_to_pitchbend_ev}, /* 0xe0 */
+	{SNDRV_SEQ_EVENT_NONE,		NULL},			/* 0xf0 */
+};
+
+static int cvt_ump_midi2_to_event(const union snd_ump_midi2_msg *val,
+				  struct snd_seq_event *ev)
+{
+	unsigned char status = val->note.status;
+
+	ev->type = midi2_msg_encoders[status].seq_type;
+	if (ev->type == SNDRV_SEQ_EVENT_NONE)
+		return 0; /* skip */
+	ev->flags = SNDRV_SEQ_EVENT_LENGTH_FIXED;
+	return midi2_msg_encoders[status].encode(val, ev);
+}
+
+/* parse and compose for a sysex var-length event */
+static int cvt_ump_sysex7_to_event(const u32 *data, unsigned char *buf,
+				   struct snd_seq_event *ev)
+{
+	unsigned char status;
+	unsigned char bytes;
+	u32 val;
+	int size = 0;
+
+	val = data[0];
+	status = ump_sysex_message_status(val);
+	bytes = ump_sysex_message_length(val);
+	if (bytes > 6)
+		return 0; // skip
+
+	if (status == UMP_SYSEX_STATUS_SINGLE ||
+	    status == UMP_SYSEX_STATUS_START) {
+		buf[0] = UMP_MIDI1_MSG_SYSEX_START;
+		size = 1;
+	}
+
+	if (bytes > 0)
+		buf[size++] = (val >> 8) & 0x7f;
+	if (bytes > 1)
+		buf[size++] = val & 0x7f;
+	val = data[1];
+	if (bytes > 2)
+		buf[size++] = (val >> 24) & 0x7f;
+	if (bytes > 3)
+		buf[size++] = (val >> 16) & 0x7f;
+	if (bytes > 4)
+		buf[size++] = (val >> 8) & 0x7f;
+	if (bytes > 5)
+		buf[size++] = val & 0x7f;
+
+	if (status == UMP_SYSEX_STATUS_SINGLE ||
+	    status == UMP_SYSEX_STATUS_END)
+		buf[size++] = UMP_MIDI1_MSG_SYSEX_END;
+
+	ev->type = SNDRV_SEQ_EVENT_SYSEX;
+	ev->flags = SNDRV_SEQ_EVENT_LENGTH_VARIABLE;
+	ev->data.ext.len = size;
+	ev->data.ext.ptr = buf;
+	return 1;
+}
+
+/* convert UMP packet from MIDI 1.0 to MIDI 2.0 and deliver it */
+static int cvt_ump_midi1_to_midi2(struct snd_seq_client *dest,
+				  struct snd_seq_client_port *dest_port,
+				  struct snd_seq_event *__event,
+				  int atomic, int hop)
+{
+	struct snd_seq_ump_event *event = (struct snd_seq_ump_event *)__event;
+	struct snd_seq_ump_event ev_cvt;
+	const union snd_ump_midi1_msg *midi1 = (const union snd_ump_midi1_msg *)event->ump;
+	union snd_ump_midi2_msg *midi2 = (union snd_ump_midi2_msg *)ev_cvt.ump;
+
+	ev_cvt = *event;
+	memset(&ev_cvt.ump, 0, sizeof(ev_cvt.ump));
+
+	midi2->note.type = UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE;
+	midi2->note.group = midi1->note.group;
+	midi2->note.status = midi1->note.status;
+	midi2->note.channel = midi1->note.channel;
+	switch (midi1->note.status) {
+	case UMP_MSG_STATUS_NOTE_ON:
+	case UMP_MSG_STATUS_NOTE_OFF:
+		midi2->note.note = midi1->note.note;
+		midi2->note.velocity = upscale_7_to_16bit(midi1->note.velocity);
+		break;
+	case UMP_MSG_STATUS_POLY_PRESSURE:
+		midi2->paf.note = midi1->paf.note;
+		midi2->paf.data = upscale_7_to_32bit(midi1->paf.data);
+		break;
+	case UMP_MSG_STATUS_CC:
+		midi2->cc.index = midi1->cc.index;
+		midi2->cc.data = upscale_7_to_32bit(midi1->cc.data);
+		break;
+	case UMP_MSG_STATUS_PROGRAM:
+		midi2->pg.program = midi1->pg.program;
+		break;
+	case UMP_MSG_STATUS_CHANNEL_PRESSURE:
+		midi2->caf.data = upscale_7_to_32bit(midi1->caf.data);
+		break;
+	case UMP_MSG_STATUS_PITCH_BEND:
+		midi2->pb.data = upscale_14_to_32bit((midi1->pb.data_msb << 7) |
+						     midi1->pb.data_lsb);
+		break;
+	default:
+		return 0;
+	}
+
+	return __snd_seq_deliver_single_event(dest, dest_port,
+					      (struct snd_seq_event *)&ev_cvt,
+					      atomic, hop);
+}
+
+/* convert UMP packet from MIDI 2.0 to MIDI 1.0 and deliver it */
+static int cvt_ump_midi2_to_midi1(struct snd_seq_client *dest,
+				  struct snd_seq_client_port *dest_port,
+				  struct snd_seq_event *__event,
+				  int atomic, int hop)
+{
+	struct snd_seq_ump_event *event = (struct snd_seq_ump_event *)__event;
+	struct snd_seq_ump_event ev_cvt;
+	union snd_ump_midi1_msg *midi1 = (union snd_ump_midi1_msg *)ev_cvt.ump;
+	const union snd_ump_midi2_msg *midi2 = (const union snd_ump_midi2_msg *)event->ump;
+	u16 v;
+
+	ev_cvt = *event;
+	memset(&ev_cvt.ump, 0, sizeof(ev_cvt.ump));
+
+	midi1->note.type = UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE;
+	midi1->note.group = midi2->note.group;
+	midi1->note.status = midi2->note.status;
+	midi1->note.channel = midi2->note.channel;
+	switch (midi2->note.status << 4) {
+	case UMP_MSG_STATUS_NOTE_ON:
+	case UMP_MSG_STATUS_NOTE_OFF:
+		midi1->note.note = midi2->note.note;
+		midi1->note.velocity = downscale_16_to_7bit(midi2->note.velocity);
+		break;
+	case UMP_MSG_STATUS_POLY_PRESSURE:
+		midi1->paf.note = midi2->paf.note;
+		midi1->paf.data = downscale_32_to_7bit(midi2->paf.data);
+		break;
+	case UMP_MSG_STATUS_CC:
+		midi1->cc.index = midi2->cc.index;
+		midi1->cc.data = downscale_32_to_7bit(midi2->cc.data);
+		break;
+	case UMP_MSG_STATUS_PROGRAM:
+		midi1->pg.program = midi2->pg.program;
+		break;
+	case UMP_MSG_STATUS_CHANNEL_PRESSURE:
+		midi1->caf.data = downscale_32_to_7bit(midi2->caf.data);
+		break;
+	case UMP_MSG_STATUS_PITCH_BEND:
+		v = downscale_32_to_14bit(midi2->pb.data);
+		midi1->pb.data_msb = v >> 7;
+		midi1->pb.data_lsb = v & 0x7f;
+		break;
+	default:
+		return 0;
+	}
+
+	return __snd_seq_deliver_single_event(dest, dest_port,
+					      (struct snd_seq_event *)&ev_cvt,
+					      atomic, hop);
+}
+
+/* convert UMP to a legacy ALSA seq event and deliver it */
+static int cvt_ump_to_any(struct snd_seq_client *dest,
+			  struct snd_seq_client_port *dest_port,
+			  struct snd_seq_event *event,
+			  unsigned char type,
+			  int atomic, int hop)
+{
+	struct snd_seq_event ev_cvt[2]; /* up to two events */
+	struct snd_seq_ump_event *ump_ev = (struct snd_seq_ump_event *)event;
+	/* use the second event as a temp buffer for saving stack usage */
+	unsigned char *sysex_buf = (unsigned char *)(ev_cvt + 1);
+	unsigned char flags = event->flags & ~SNDRV_SEQ_EVENT_UMP;
+	int i, len, err;
+
+	ev_cvt[0] = ev_cvt[1] = *event;
+	ev_cvt[0].flags = flags;
+	ev_cvt[1].flags = flags;
+	switch (type) {
+	case UMP_MSG_TYPE_SYSTEM:
+		len = cvt_ump_system_to_event((union snd_ump_midi1_msg *)ump_ev->ump,
+					      ev_cvt);
+		break;
+	case UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE:
+		len = cvt_ump_midi1_to_event((union snd_ump_midi1_msg *)ump_ev->ump,
+					     ev_cvt);
+		break;
+	case UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE:
+		len = cvt_ump_midi2_to_event((union snd_ump_midi2_msg *)ump_ev->ump,
+					     ev_cvt);
+		break;
+	case UMP_MSG_TYPE_DATA:
+		len = cvt_ump_sysex7_to_event(ump_ev->ump, sysex_buf, ev_cvt);
+		break;
+	default:
+		return 0;
+	}
+
+	for (i = 0; i < len; i++) {
+		err = __snd_seq_deliver_single_event(dest, dest_port,
+						     &ev_cvt[i], atomic, hop);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+/* Replace UMP group field with the destination and deliver */
+static int deliver_with_group_convert(struct snd_seq_client *dest,
+				      struct snd_seq_client_port *dest_port,
+				      struct snd_seq_ump_event *ump_ev,
+				      int atomic, int hop)
+{
+	struct snd_seq_ump_event ev = *ump_ev;
+
+	/* rewrite the group to the destination port */
+	ev.ump[0] &= ~(0xfU << 24);
+	/* fill with the new group; the dest_port->ump_group field is 1-based */
+	ev.ump[0] |= ((dest_port->ump_group - 1) << 24);
+
+	return __snd_seq_deliver_single_event(dest, dest_port,
+					      (struct snd_seq_event *)&ev,
+					      atomic, hop);
+}
+
+/* Convert from UMP packet and deliver */
+int snd_seq_deliver_from_ump(struct snd_seq_client *source,
+			     struct snd_seq_client *dest,
+			     struct snd_seq_client_port *dest_port,
+			     struct snd_seq_event *event,
+			     int atomic, int hop)
+{
+	struct snd_seq_ump_event *ump_ev = (struct snd_seq_ump_event *)event;
+	unsigned char type;
+
+	if (snd_seq_ev_is_variable(event))
+		return 0; // skip, no variable event for UMP, so far
+	type = ump_message_type(ump_ev->ump[0]);
+
+	if (snd_seq_client_is_ump(dest)) {
+		if (snd_seq_client_is_midi2(dest) &&
+		    type == UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE)
+			return cvt_ump_midi1_to_midi2(dest, dest_port,
+						      event, atomic, hop);
+		else if (!snd_seq_client_is_midi2(dest) &&
+			 type == UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE)
+			return cvt_ump_midi2_to_midi1(dest, dest_port,
+						      event, atomic, hop);
+		/* non-EP port and different group is set? */
+		if (dest_port->ump_group &&
+		    ump_message_group(*ump_ev->ump) + 1 != dest_port->ump_group)
+			return deliver_with_group_convert(dest, dest_port,
+							  ump_ev, atomic, hop);
+		/* copy as-is */
+		return __snd_seq_deliver_single_event(dest, dest_port,
+						      event, atomic, hop);
+	}
+
+	return cvt_ump_to_any(dest, dest_port, event, type, atomic, hop);
+}
+
+/*
+ * MIDI1 sequencer event -> UMP conversion
+ */
+
+/* Conversion to UMP MIDI 1.0 */
+
+/* convert note on/off event to MIDI 1.0 UMP */
+static int note_ev_to_ump_midi1(const struct snd_seq_event *event,
+				struct snd_seq_client_port *dest_port,
+				union snd_ump_midi1_msg *data,
+				unsigned char status)
+{
+	if (!event->data.note.velocity)
+		status = UMP_MSG_STATUS_NOTE_OFF;
+	data->note.status = status;
+	data->note.channel = event->data.note.channel & 0x0f;
+	data->note.velocity = event->data.note.velocity & 0x7f;
+	data->note.note = event->data.note.note & 0x7f;
+	return 1;
+}
+
+/* convert CC event to MIDI 1.0 UMP */
+static int cc_ev_to_ump_midi1(const struct snd_seq_event *event,
+			      struct snd_seq_client_port *dest_port,
+			      union snd_ump_midi1_msg *data,
+			      unsigned char status)
+{
+	data->cc.status = status;
+	data->cc.channel = event->data.control.channel & 0x0f;
+	data->cc.index = event->data.control.param;
+	data->cc.data = event->data.control.value;
+	return 1;
+}
+
+/* convert one-parameter control event to MIDI 1.0 UMP */
+static int ctrl_ev_to_ump_midi1(const struct snd_seq_event *event,
+				struct snd_seq_client_port *dest_port,
+				union snd_ump_midi1_msg *data,
+				unsigned char status)
+{
+	data->caf.status = status;
+	data->caf.channel = event->data.control.channel & 0x0f;
+	data->caf.data = event->data.control.value & 0x7f;
+	return 1;
+}
+
+/* convert pitchbend event to MIDI 1.0 UMP */
+static int pitchbend_ev_to_ump_midi1(const struct snd_seq_event *event,
+				     struct snd_seq_client_port *dest_port,
+				     union snd_ump_midi1_msg *data,
+				     unsigned char status)
+{
+	int val = event->data.control.value + 8192;
+
+	val = clamp(val, 0, 0x3fff);
+	data->pb.status = status;
+	data->pb.channel = event->data.control.channel & 0x0f;
+	data->pb.data_msb = (val >> 7) & 0x7f;
+	data->pb.data_lsb = val & 0x7f;
+	return 1;
+}
+
+/* convert 14bit control event to MIDI 1.0 UMP; split to two events */
+static int ctrl14_ev_to_ump_midi1(const struct snd_seq_event *event,
+				  struct snd_seq_client_port *dest_port,
+				  union snd_ump_midi1_msg *data,
+				  unsigned char status)
+{
+	data->cc.status = UMP_MSG_STATUS_CC;
+	data->cc.channel = event->data.control.channel & 0x0f;
+	data->cc.index = event->data.control.param & 0x7f;
+	if (event->data.control.param < 0x20) {
+		data->cc.data = (event->data.control.value >> 7) & 0x7f;
+		data[1] = data[0];
+		data[1].cc.index = event->data.control.param | 0x20;
+		data[1].cc.data = event->data.control.value & 0x7f;
+		return 2;
+	}
+
+	data->cc.data = event->data.control.value & 0x7f;
+	return 1;
+}
+
+/* convert RPN/NRPN event to MIDI 1.0 UMP; split to four events */
+static int rpn_ev_to_ump_midi1(const struct snd_seq_event *event,
+			       struct snd_seq_client_port *dest_port,
+			       union snd_ump_midi1_msg *data,
+			       unsigned char status)
+{
+	bool is_rpn = (status == UMP_MSG_STATUS_RPN);
+
+	data->cc.status = UMP_MSG_STATUS_CC;
+	data->cc.channel = event->data.control.channel & 0x0f;
+	data[1] = data[2] = data[3] = data[0];
+
+	data[0].cc.index = is_rpn ? UMP_CC_RPN_MSB : UMP_CC_NRPN_MSB;
+	data[0].cc.data = (event->data.control.param >> 7) & 0x7f;
+	data[1].cc.index = is_rpn ? UMP_CC_RPN_LSB : UMP_CC_NRPN_LSB;
+	data[1].cc.data = event->data.control.param & 0x7f;
+	data[2].cc.index = UMP_CC_DATA;
+	data[2].cc.data = (event->data.control.value >> 7) & 0x7f;
+	data[3].cc.index = UMP_CC_DATA_LSB;
+	data[3].cc.data = event->data.control.value & 0x7f;
+	return 4;
+}
+
+/* convert system / RT message to UMP */
+static int system_ev_to_ump_midi1(const struct snd_seq_event *event,
+				  struct snd_seq_client_port *dest_port,
+				  union snd_ump_midi1_msg *data,
+				  unsigned char status)
+{
+	data->system.status = status;
+	return 1;
+}
+
+/* convert system / RT message with 1 parameter to UMP */
+static int system_1p_ev_to_ump_midi1(const struct snd_seq_event *event,
+				     struct snd_seq_client_port *dest_port,
+				     union snd_ump_midi1_msg *data,
+				     unsigned char status)
+{
+	data->system.status = status;
+	data->system.parm1 = event->data.control.value & 0x7f;
+	return 1;
+}
+
+/* convert system / RT message with two parameters to UMP */
+static int system_2p_ev_to_ump_midi1(const struct snd_seq_event *event,
+				     struct snd_seq_client_port *dest_port,
+				     union snd_ump_midi1_msg *data,
+				     unsigned char status)
+{
+	data->system.status = status;
+	data->system.parm1 = (event->data.control.value >> 7) & 0x7f;
+	data->system.parm1 = event->data.control.value & 0x7f;
+	return 1;
+}
+
+/* Conversion to UMP MIDI 2.0 */
+
+/* convert note on/off event to MIDI 2.0 UMP */
+static int note_ev_to_ump_midi2(const struct snd_seq_event *event,
+				struct snd_seq_client_port *dest_port,
+				union snd_ump_midi2_msg *data,
+				unsigned char status)
+{
+	if (!event->data.note.velocity)
+		status = UMP_MSG_STATUS_NOTE_OFF;
+	data->note.status = status;
+	data->note.channel = event->data.note.channel & 0x0f;
+	data->note.note = event->data.note.note & 0x7f;
+	data->note.velocity = upscale_7_to_16bit(event->data.note.velocity & 0x7f);
+	return 1;
+}
+
+/* convert PAF event to MIDI 2.0 UMP */
+static int paf_ev_to_ump_midi2(const struct snd_seq_event *event,
+			       struct snd_seq_client_port *dest_port,
+			       union snd_ump_midi2_msg *data,
+			       unsigned char status)
+{
+	data->paf.status = status;
+	data->paf.channel = event->data.note.channel & 0x0f;
+	data->paf.note = event->data.note.note & 0x7f;
+	data->paf.data = upscale_7_to_32bit(event->data.note.velocity & 0x7f);
+	return 1;
+}
+
+/* set up the MIDI2 RPN/NRPN packet data from the parsed info */
+static void fill_rpn(struct snd_seq_ump_midi2_bank *cc,
+		     union snd_ump_midi2_msg *data)
+{
+	if (cc->rpn_set) {
+		data->rpn.status = UMP_MSG_STATUS_RPN;
+		data->rpn.bank = cc->cc_rpn_msb;
+		data->rpn.index = cc->cc_rpn_lsb;
+		cc->rpn_set = 0;
+		cc->cc_rpn_msb = cc->cc_rpn_lsb = 0;
+	} else {
+		data->rpn.status = UMP_MSG_STATUS_NRPN;
+		data->rpn.bank = cc->cc_nrpn_msb;
+		data->rpn.index = cc->cc_nrpn_lsb;
+		cc->nrpn_set = 0;
+		cc->cc_nrpn_msb = cc->cc_nrpn_lsb = 0;
+	}
+	data->rpn.data = upscale_14_to_32bit((cc->cc_data_msb << 7) |
+					     cc->cc_data_lsb);
+	cc->cc_data_msb = cc->cc_data_lsb = 0;
+}
+
+/* convert CC event to MIDI 2.0 UMP */
+static int cc_ev_to_ump_midi2(const struct snd_seq_event *event,
+			      struct snd_seq_client_port *dest_port,
+			      union snd_ump_midi2_msg *data,
+			      unsigned char status)
+{
+	unsigned char channel = event->data.control.channel & 0x0f;
+	unsigned char index = event->data.control.param & 0x7f;
+	unsigned char val = event->data.control.value & 0x7f;
+	struct snd_seq_ump_midi2_bank *cc = &dest_port->midi2_bank[channel];
+
+	/* process special CC's (bank/rpn/nrpn) */
+	switch (index) {
+	case UMP_CC_RPN_MSB:
+		cc->rpn_set = 1;
+		cc->cc_rpn_msb = val;
+		return 0; // skip
+	case UMP_CC_RPN_LSB:
+		cc->rpn_set = 1;
+		cc->cc_rpn_lsb = val;
+		return 0; // skip
+	case UMP_CC_NRPN_MSB:
+		cc->nrpn_set = 1;
+		cc->cc_nrpn_msb = val;
+		return 0; // skip
+	case UMP_CC_NRPN_LSB:
+		cc->nrpn_set = 1;
+		cc->cc_nrpn_lsb = val;
+		return 0; // skip
+	case UMP_CC_DATA:
+		cc->cc_data_msb = val;
+		return 0; // skip
+	case UMP_CC_BANK_SELECT:
+		cc->bank_set = 1;
+		cc->cc_bank_msb = val;
+		return 0; // skip
+	case UMP_CC_BANK_SELECT_LSB:
+		cc->bank_set = 1;
+		cc->cc_bank_lsb = val;
+		return 0; // skip
+	case UMP_CC_DATA_LSB:
+		cc->cc_data_lsb = val;
+		if (!(cc->rpn_set || cc->nrpn_set))
+			return 0; // skip
+		fill_rpn(cc, data);
+		return 1;
+	}
+
+	data->cc.status = status;
+	data->cc.channel = channel;
+	data->cc.index = index;
+	data->cc.data = upscale_7_to_32bit(event->data.control.value & 0x7f);
+	return 1;
+}
+
+/* convert one-parameter control event to MIDI 2.0 UMP */
+static int ctrl_ev_to_ump_midi2(const struct snd_seq_event *event,
+				struct snd_seq_client_port *dest_port,
+				union snd_ump_midi2_msg *data,
+				unsigned char status)
+{
+	data->caf.status = status;
+	data->caf.channel = event->data.control.channel & 0x0f;
+	data->caf.data = upscale_7_to_32bit(event->data.control.value & 0x7f);
+	return 1;
+}
+
+/* convert program change event to MIDI 2.0 UMP */
+static int pgm_ev_to_ump_midi2(const struct snd_seq_event *event,
+			       struct snd_seq_client_port *dest_port,
+			       union snd_ump_midi2_msg *data,
+			       unsigned char status)
+{
+	unsigned char channel = event->data.control.channel & 0x0f;
+	struct snd_seq_ump_midi2_bank *cc = &dest_port->midi2_bank[channel];
+
+	data->pg.status = status;
+	data->pg.channel = channel;
+	data->pg.program = event->data.control.value & 0x7f;
+	if (cc->bank_set) {
+		data->pg.bank_valid = 1;
+		data->pg.bank_msb = cc->cc_bank_msb;
+		data->pg.bank_lsb = cc->cc_bank_lsb;
+		cc->bank_set = 0;
+		cc->cc_bank_msb = cc->cc_bank_lsb = 0;
+	}
+	return 1;
+}
+
+/* convert pitchbend event to MIDI 2.0 UMP */
+static int pitchbend_ev_to_ump_midi2(const struct snd_seq_event *event,
+				     struct snd_seq_client_port *dest_port,
+				     union snd_ump_midi2_msg *data,
+				     unsigned char status)
+{
+	int val = event->data.control.value + 8192;
+
+	val = clamp(val, 0, 0x3fff);
+	data->pb.status = status;
+	data->pb.channel = event->data.control.channel & 0x0f;
+	data->pb.data = upscale_14_to_32bit(val);
+	return 1;
+}
+
+/* convert 14bit control event to MIDI 2.0 UMP; split to two events */
+static int ctrl14_ev_to_ump_midi2(const struct snd_seq_event *event,
+				  struct snd_seq_client_port *dest_port,
+				  union snd_ump_midi2_msg *data,
+				  unsigned char status)
+{
+	unsigned char channel = event->data.control.channel & 0x0f;
+	unsigned char index = event->data.control.param & 0x7f;
+	struct snd_seq_ump_midi2_bank *cc = &dest_port->midi2_bank[channel];
+	unsigned char msb, lsb;
+
+	msb = (event->data.control.value >> 7) & 0x7f;
+	lsb = event->data.control.value & 0x7f;
+	/* process special CC's (bank/rpn/nrpn) */
+	switch (index) {
+	case UMP_CC_BANK_SELECT:
+		cc->cc_bank_msb = msb;
+		fallthrough;
+	case UMP_CC_BANK_SELECT_LSB:
+		cc->bank_set = 1;
+		cc->cc_bank_lsb = lsb;
+		return 0; // skip
+	case UMP_CC_RPN_MSB:
+		cc->cc_rpn_msb = msb;
+		fallthrough;
+	case UMP_CC_RPN_LSB:
+		cc->rpn_set = 1;
+		cc->cc_rpn_lsb = lsb;
+		return 0; // skip
+	case UMP_CC_NRPN_MSB:
+		cc->cc_nrpn_msb = msb;
+		fallthrough;
+	case UMP_CC_NRPN_LSB:
+		cc->nrpn_set = 1;
+		cc->cc_nrpn_lsb = lsb;
+		return 0; // skip
+	case UMP_CC_DATA:
+		cc->cc_data_msb = msb;
+		fallthrough;
+	case UMP_CC_DATA_LSB:
+		cc->cc_data_lsb = lsb;
+		if (!(cc->rpn_set || cc->nrpn_set))
+			return 0; // skip
+		fill_rpn(cc, data);
+		return 1;
+	}
+
+	data->cc.status = UMP_MSG_STATUS_CC;
+	data->cc.channel = channel;
+	data->cc.index = index;
+	if (event->data.control.param < 0x20) {
+		data->cc.data = upscale_7_to_32bit(msb);
+		data[1] = data[0];
+		data[1].cc.index = event->data.control.param | 0x20;
+		data[1].cc.data = upscale_7_to_32bit(lsb);
+		return 2;
+	}
+
+	data->cc.data = upscale_7_to_32bit(lsb);
+	return 1;
+}
+
+/* convert RPN/NRPN event to MIDI 2.0 UMP */
+static int rpn_ev_to_ump_midi2(const struct snd_seq_event *event,
+			       struct snd_seq_client_port *dest_port,
+			       union snd_ump_midi2_msg *data,
+			       unsigned char status)
+{
+	data->rpn.status = status;
+	data->rpn.channel = event->data.control.channel;
+	data->rpn.bank = (event->data.control.param >> 7) & 0x7f;
+	data->rpn.index = event->data.control.param & 0x7f;
+	data->rpn.data = upscale_14_to_32bit(event->data.control.value & 0x3fff);
+	return 1;
+}
+
+/* convert system / RT message to UMP */
+static int system_ev_to_ump_midi2(const struct snd_seq_event *event,
+				  struct snd_seq_client_port *dest_port,
+				  union snd_ump_midi2_msg *data,
+				  unsigned char status)
+{
+	return system_ev_to_ump_midi1(event, dest_port,
+				      (union snd_ump_midi1_msg *)data,
+				      status);
+}
+
+/* convert system / RT message with 1 parameter to UMP */
+static int system_1p_ev_to_ump_midi2(const struct snd_seq_event *event,
+				     struct snd_seq_client_port *dest_port,
+				     union snd_ump_midi2_msg *data,
+				     unsigned char status)
+{
+	return system_1p_ev_to_ump_midi1(event, dest_port,
+					 (union snd_ump_midi1_msg *)data,
+					 status);
+}
+
+/* convert system / RT message with two parameters to UMP */
+static int system_2p_ev_to_ump_midi2(const struct snd_seq_event *event,
+				     struct snd_seq_client_port *dest_port,
+				     union snd_ump_midi2_msg *data,
+				     unsigned char status)
+{
+	return system_1p_ev_to_ump_midi1(event, dest_port,
+					 (union snd_ump_midi1_msg *)data,
+					 status);
+}
+
+struct seq_ev_to_ump {
+	int seq_type;
+	unsigned char status;
+	int (*midi1_encode)(const struct snd_seq_event *event,
+			    struct snd_seq_client_port *dest_port,
+			    union snd_ump_midi1_msg *data,
+			    unsigned char status);
+	int (*midi2_encode)(const struct snd_seq_event *event,
+			    struct snd_seq_client_port *dest_port,
+			    union snd_ump_midi2_msg *data,
+			    unsigned char status);
+};
+
+static const struct seq_ev_to_ump seq_ev_ump_encoders[] = {
+	{ SNDRV_SEQ_EVENT_NOTEON, UMP_MSG_STATUS_NOTE_ON,
+	  note_ev_to_ump_midi1, note_ev_to_ump_midi2 },
+	{ SNDRV_SEQ_EVENT_NOTEOFF, UMP_MSG_STATUS_NOTE_OFF,
+	  note_ev_to_ump_midi1, note_ev_to_ump_midi2 },
+	{ SNDRV_SEQ_EVENT_KEYPRESS, UMP_MSG_STATUS_POLY_PRESSURE,
+	  note_ev_to_ump_midi1, paf_ev_to_ump_midi2 },
+	{ SNDRV_SEQ_EVENT_CONTROLLER, UMP_MSG_STATUS_CC,
+	  cc_ev_to_ump_midi1, cc_ev_to_ump_midi2 },
+	{ SNDRV_SEQ_EVENT_PGMCHANGE, UMP_MSG_STATUS_PROGRAM,
+	  ctrl_ev_to_ump_midi1, pgm_ev_to_ump_midi2 },
+	{ SNDRV_SEQ_EVENT_CHANPRESS, UMP_MSG_STATUS_CHANNEL_PRESSURE,
+	  ctrl_ev_to_ump_midi1, ctrl_ev_to_ump_midi2 },
+	{ SNDRV_SEQ_EVENT_PITCHBEND, UMP_MSG_STATUS_PITCH_BEND,
+	  pitchbend_ev_to_ump_midi1, pitchbend_ev_to_ump_midi2 },
+	{ SNDRV_SEQ_EVENT_CONTROL14, 0,
+	  ctrl14_ev_to_ump_midi1, ctrl14_ev_to_ump_midi2 },
+	{ SNDRV_SEQ_EVENT_NONREGPARAM, UMP_MSG_STATUS_NRPN,
+	  rpn_ev_to_ump_midi1, rpn_ev_to_ump_midi2 },
+	{ SNDRV_SEQ_EVENT_REGPARAM, UMP_MSG_STATUS_RPN,
+	  rpn_ev_to_ump_midi1, rpn_ev_to_ump_midi2 },
+	{ SNDRV_SEQ_EVENT_QFRAME, UMP_SYSTEM_STATUS_MIDI_TIME_CODE,
+	  system_1p_ev_to_ump_midi1, system_1p_ev_to_ump_midi2 },
+	{ SNDRV_SEQ_EVENT_SONGPOS, UMP_SYSTEM_STATUS_SONG_POSITION,
+	  system_2p_ev_to_ump_midi1, system_2p_ev_to_ump_midi2 },
+	{ SNDRV_SEQ_EVENT_SONGSEL, UMP_SYSTEM_STATUS_SONG_SELECT,
+	  system_1p_ev_to_ump_midi1, system_1p_ev_to_ump_midi2 },
+	{ SNDRV_SEQ_EVENT_TUNE_REQUEST, UMP_SYSTEM_STATUS_TUNE_REQUEST,
+	  system_ev_to_ump_midi1, system_ev_to_ump_midi2 },
+	{ SNDRV_SEQ_EVENT_CLOCK, UMP_SYSTEM_STATUS_TIMING_CLOCK,
+	  system_ev_to_ump_midi1, system_ev_to_ump_midi2 },
+	{ SNDRV_SEQ_EVENT_START, UMP_SYSTEM_STATUS_START,
+	  system_ev_to_ump_midi1, system_ev_to_ump_midi2 },
+	{ SNDRV_SEQ_EVENT_CONTINUE, UMP_SYSTEM_STATUS_CONTINUE,
+	  system_ev_to_ump_midi1, system_ev_to_ump_midi2 },
+	{ SNDRV_SEQ_EVENT_STOP, UMP_SYSTEM_STATUS_STOP,
+	  system_ev_to_ump_midi1, system_ev_to_ump_midi2 },
+	{ SNDRV_SEQ_EVENT_SENSING, UMP_SYSTEM_STATUS_ACTIVE_SENSING,
+	  system_ev_to_ump_midi1, system_ev_to_ump_midi2 },
+};
+
+static const struct seq_ev_to_ump *find_ump_encoder(int type)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(seq_ev_ump_encoders); i++)
+		if (seq_ev_ump_encoders[i].seq_type == type)
+			return &seq_ev_ump_encoders[i];
+
+	return NULL;
+}
+
+static void setup_ump_event(struct snd_seq_ump_event *dest,
+			    const struct snd_seq_event *src)
+{
+	memcpy(dest, src, sizeof(*src));
+	dest->type = 0;
+	dest->flags |= SNDRV_SEQ_EVENT_UMP;
+	dest->flags &= ~SNDRV_SEQ_EVENT_LENGTH_MASK;
+	memset(dest->ump, 0, sizeof(dest->ump));
+}
+
+/* Convert ALSA seq event to UMP MIDI 1.0 and deliver it */
+static int cvt_to_ump_midi1(struct snd_seq_client *dest,
+			    struct snd_seq_client_port *dest_port,
+			    struct snd_seq_event *event,
+			    int atomic, int hop)
+{
+	const struct seq_ev_to_ump *encoder;
+	struct snd_seq_ump_event ev_cvt;
+	union snd_ump_midi1_msg data[4];
+	int i, n, err;
+
+	encoder = find_ump_encoder(event->type);
+	if (!encoder)
+		return __snd_seq_deliver_single_event(dest, dest_port,
+						      event, atomic, hop);
+
+	data->raw = make_raw_ump(dest_port, UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE);
+	n = encoder->midi1_encode(event, dest_port, data, encoder->status);
+	if (!n)
+		return 0;
+
+	setup_ump_event(&ev_cvt, event);
+	for (i = 0; i < n; i++) {
+		ev_cvt.ump[0] = data[i].raw;
+		err = __snd_seq_deliver_single_event(dest, dest_port,
+						     (struct snd_seq_event *)&ev_cvt,
+						     atomic, hop);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+/* Convert ALSA seq event to UMP MIDI 2.0 and deliver it */
+static int cvt_to_ump_midi2(struct snd_seq_client *dest,
+			    struct snd_seq_client_port *dest_port,
+			    struct snd_seq_event *event,
+			    int atomic, int hop)
+{
+	const struct seq_ev_to_ump *encoder;
+	struct snd_seq_ump_event ev_cvt;
+	union snd_ump_midi2_msg data[2];
+	int i, n, err;
+
+	encoder = find_ump_encoder(event->type);
+	if (!encoder)
+		return __snd_seq_deliver_single_event(dest, dest_port,
+						      event, atomic, hop);
+
+	data->raw[0] = make_raw_ump(dest_port, UMP_MSG_TYPE_MIDI2_CHANNEL_VOICE);
+	data->raw[1] = 0;
+	n = encoder->midi2_encode(event, dest_port, data, encoder->status);
+	if (!n)
+		return 0;
+
+	setup_ump_event(&ev_cvt, event);
+	for (i = 0; i < n; i++) {
+		memcpy(ev_cvt.ump, &data[i], sizeof(data[i]));
+		err = __snd_seq_deliver_single_event(dest, dest_port,
+						     (struct snd_seq_event *)&ev_cvt,
+						     atomic, hop);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+/* Fill up a sysex7 UMP from the byte stream */
+static void fill_sysex7_ump(struct snd_seq_client_port *dest_port,
+			    u32 *val, u8 status, u8 *buf, int len)
+{
+	memset(val, 0, 8);
+	memcpy((u8 *)val + 2, buf, len);
+#ifdef __LITTLE_ENDIAN
+	swab32_array(val, 2);
+#endif
+	val[0] |= ump_compose(UMP_MSG_TYPE_DATA, get_ump_group(dest_port),
+			      status, len);
+}
+
+/* Convert sysex var event to UMP sysex7 packets and deliver them */
+static int cvt_sysex_to_ump(struct snd_seq_client *dest,
+			    struct snd_seq_client_port *dest_port,
+			    struct snd_seq_event *event,
+			    int atomic, int hop)
+{
+	struct snd_seq_ump_event ev_cvt;
+	unsigned char status;
+	u8 buf[6], *xbuf;
+	int offset = 0;
+	int len, err;
+
+	if (!snd_seq_ev_is_variable(event))
+		return 0;
+
+	setup_ump_event(&ev_cvt, event);
+	for (;;) {
+		len = snd_seq_expand_var_event_at(event, sizeof(buf), buf, offset);
+		if (len <= 0)
+			break;
+		if (WARN_ON(len > 6))
+			break;
+		offset += len;
+		xbuf = buf;
+		if (*xbuf == UMP_MIDI1_MSG_SYSEX_START) {
+			status = UMP_SYSEX_STATUS_START;
+			xbuf++;
+			len--;
+			if (len > 0 && xbuf[len - 1] == UMP_MIDI1_MSG_SYSEX_END) {
+				status = UMP_SYSEX_STATUS_SINGLE;
+				len--;
+			}
+		} else {
+			if (xbuf[len - 1] == UMP_MIDI1_MSG_SYSEX_END) {
+				status = UMP_SYSEX_STATUS_END;
+				len--;
+			} else {
+				status = UMP_SYSEX_STATUS_CONTINUE;
+			}
+		}
+		fill_sysex7_ump(dest_port, ev_cvt.ump, status, xbuf, len);
+		err = __snd_seq_deliver_single_event(dest, dest_port,
+						     (struct snd_seq_event *)&ev_cvt,
+						     atomic, hop);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+/* Convert to UMP packet and deliver */
+int snd_seq_deliver_to_ump(struct snd_seq_client *source,
+			   struct snd_seq_client *dest,
+			   struct snd_seq_client_port *dest_port,
+			   struct snd_seq_event *event,
+			   int atomic, int hop)
+{
+	if (event->type == SNDRV_SEQ_EVENT_SYSEX)
+		return cvt_sysex_to_ump(dest, dest_port, event, atomic, hop);
+	else if (snd_seq_client_is_midi2(dest))
+		return cvt_to_ump_midi2(dest, dest_port, event, atomic, hop);
+	else
+		return cvt_to_ump_midi1(dest, dest_port, event, atomic, hop);
+}
diff --git a/sound/core/seq/seq_ump_convert.h b/sound/core/seq/seq_ump_convert.h
new file mode 100644
index 000000000000..6c146d803280
--- /dev/null
+++ b/sound/core/seq/seq_ump_convert.h
@@ -0,0 +1,22 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ALSA sequencer event conversion between UMP and legacy clients
+ */
+#ifndef __SEQ_UMP_CONVERT_H
+#define __SEQ_UMP_CONVERT_H
+
+#include "seq_clientmgr.h"
+#include "seq_ports.h"
+
+int snd_seq_deliver_from_ump(struct snd_seq_client *source,
+			     struct snd_seq_client *dest,
+			     struct snd_seq_client_port *dest_port,
+			     struct snd_seq_event *event,
+			     int atomic, int hop);
+int snd_seq_deliver_to_ump(struct snd_seq_client *source,
+			   struct snd_seq_client *dest,
+			   struct snd_seq_client_port *dest_port,
+			   struct snd_seq_event *event,
+			   int atomic, int hop);
+
+#endif /* __SEQ_UMP_CONVERT_H */