Contributors: 6
Author Tokens Token Proportion Commits Commit Proportion
Takashi Iwai 7234 97.47% 19 76.00%
Jaroslav Kysela 169 2.28% 1 4.00%
Linus Torvalds (pre-git) 15 0.20% 2 8.00%
Paul Gortmaker 2 0.03% 1 4.00%
Minjie Du 1 0.01% 1 4.00%
Thomas Gleixner 1 0.01% 1 4.00%
Total 7422 25

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287
// 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.parm2 << 7) | val->system.parm1;
}

/* 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;
	struct ump_cvt_to_ump_bank *cc;

	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:
		cc = &dest_port->midi2_bank[midi1->note.channel];
		switch (midi1->cc.index) {
		case UMP_CC_BANK_SELECT:
			cc->bank_set = 1;
			cc->cc_bank_msb = midi1->cc.data;
			return 0; // skip
		case UMP_CC_BANK_SELECT_LSB:
			cc->bank_set = 1;
			cc->cc_bank_lsb = midi1->cc.data;
			return 0; // skip
		}
		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;
		cc = &dest_port->midi2_bank[midi1->note.channel];
		if (cc->bank_set) {
			midi2->pg.bank_valid = 1;
			midi2->pg.bank_msb = cc->cc_bank_msb;
			midi2->pg.bank_lsb = cc->cc_bank_lsb;
			cc->bank_set = 0;
		}
		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;
	int err;
	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) {
	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:
		if (midi2->pg.bank_valid) {
			midi1->cc.status = UMP_MSG_STATUS_CC;
			midi1->cc.index = UMP_CC_BANK_SELECT;
			midi1->cc.data = midi2->pg.bank_msb;
			err = __snd_seq_deliver_single_event(dest, dest_port,
							     (struct snd_seq_event *)&ev_cvt,
							     atomic, hop);
			if (err < 0)
				return err;
			midi1->cc.index = UMP_CC_BANK_SELECT_LSB;
			midi1->cc.data = midi2->pg.bank_lsb;
			err = __snd_seq_deliver_single_event(dest, dest_port,
							     (struct snd_seq_event *)&ev_cvt,
							     atomic, hop);
			if (err < 0)
				return err;
			midi1->note.status = midi2->note.status;
		}
		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);
}

/* apply the UMP event filter; return true to skip the event */
static bool ump_event_filtered(struct snd_seq_client *dest,
			       const struct snd_seq_ump_event *ev)
{
	unsigned char group;

	group = ump_message_group(ev->ump[0]);
	if (ump_is_groupless_msg(ump_message_type(ev->ump[0])))
		return dest->group_filter & (1U << 0);
	/* check the bitmap for 1-based group number */
	return dest->group_filter & (1U << (group + 1));
}

/* 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
	if (ump_event_filtered(dest, ump_ev))
		return 0; // skip if group filter is set and matching
	type = ump_message_type(ump_ev->ump[0]);

	if (snd_seq_client_is_ump(dest)) {
		bool is_midi2 = snd_seq_client_is_midi2(dest) &&
			!dest_port->is_midi1;

		if (is_midi2 && type == UMP_MSG_TYPE_MIDI1_CHANNEL_VOICE)
			return cvt_ump_midi1_to_midi2(dest, dest_port,
						      event, atomic, hop);
		else if (!is_midi2 && 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_is_groupless_msg(type) &&
		    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.type = UMP_MSG_TYPE_SYSTEM; // override
	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.type = UMP_MSG_TYPE_SYSTEM; // override
	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.type = UMP_MSG_TYPE_SYSTEM; // override
	data->system.status = status;
	data->system.parm1 = event->data.control.value & 0x7f;
	data->system.parm2 = (event->data.control.value >> 7) & 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;
}

static void reset_rpn(struct ump_cvt_to_ump_bank *cc)
{
	cc->rpn_set = 0;
	cc->nrpn_set = 0;
	cc->cc_rpn_msb = cc->cc_rpn_lsb = 0;
	cc->cc_data_msb = cc->cc_data_lsb = 0;
	cc->cc_data_msb_set = cc->cc_data_lsb_set = 0;
}

/* set up the MIDI2 RPN/NRPN packet data from the parsed info */
static int fill_rpn(struct ump_cvt_to_ump_bank *cc,
		    union snd_ump_midi2_msg *data,
		    unsigned char channel,
		    bool flush)
{
	if (!(cc->cc_data_lsb_set || cc->cc_data_msb_set))
		return 0; // skip
	/* when not flushing, wait for complete data set */
	if (!flush && (!cc->cc_data_lsb_set || !cc->cc_data_msb_set))
		return 0; // skip

	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;
	} else if (cc->nrpn_set) {
		data->rpn.status = UMP_MSG_STATUS_NRPN;
		data->rpn.bank = cc->cc_nrpn_msb;
		data->rpn.index = cc->cc_nrpn_lsb;
	} else {
		return 0; // skip
	}

	data->rpn.data = upscale_14_to_32bit((cc->cc_data_msb << 7) |
					     cc->cc_data_lsb);
	data->rpn.channel = channel;

	reset_rpn(cc);
	return 1;
}

/* 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 ump_cvt_to_ump_bank *cc = &dest_port->midi2_bank[channel];
	int ret;

	/* process special CC's (bank/rpn/nrpn) */
	switch (index) {
	case UMP_CC_RPN_MSB:
		ret = fill_rpn(cc, data, channel, true);
		cc->rpn_set = 1;
		cc->cc_rpn_msb = val;
		if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f)
			reset_rpn(cc);
		return ret;
	case UMP_CC_RPN_LSB:
		ret = fill_rpn(cc, data, channel, true);
		cc->rpn_set = 1;
		cc->cc_rpn_lsb = val;
		if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f)
			reset_rpn(cc);
		return ret;
	case UMP_CC_NRPN_MSB:
		ret = fill_rpn(cc, data, channel, true);
		cc->nrpn_set = 1;
		cc->cc_nrpn_msb = val;
		return ret;
	case UMP_CC_NRPN_LSB:
		ret = fill_rpn(cc, data, channel, true);
		cc->nrpn_set = 1;
		cc->cc_nrpn_lsb = val;
		return ret;
	case UMP_CC_DATA:
		cc->cc_data_msb_set = 1;
		cc->cc_data_msb = val;
		return fill_rpn(cc, data, channel, false);
	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_set = 1;
		cc->cc_data_lsb = val;
		return fill_rpn(cc, data, channel, false);
	}

	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 ump_cvt_to_ump_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;
	}
	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 ump_cvt_to_ump_bank *cc = &dest_port->midi2_bank[channel];
	unsigned char msb, lsb;
	int ret;

	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:
	case UMP_CC_RPN_LSB:
		ret = fill_rpn(cc, data, channel, true);
		cc->cc_rpn_msb = msb;
		cc->cc_rpn_lsb = lsb;
		cc->rpn_set = 1;
		if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f)
			reset_rpn(cc);
		return ret;
	case UMP_CC_NRPN_MSB:
	case UMP_CC_NRPN_LSB:
		ret = fill_rpn(cc, data, channel, true);
		cc->cc_nrpn_msb = msb;
		cc->nrpn_set = 1;
		cc->cc_nrpn_lsb = lsb;
		return ret;
	case UMP_CC_DATA:
	case UMP_CC_DATA_LSB:
		cc->cc_data_msb_set = cc->cc_data_lsb_set = 1;
		cc->cc_data_msb = msb;
		cc->cc_data_lsb = lsb;
		return fill_rpn(cc, data, channel, false);
	}

	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_2p_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 },
	{ SNDRV_SEQ_EVENT_RESET, UMP_SYSTEM_STATUS_RESET,
	  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[8], *xbuf;
	int offset = 0;
	int len, err;
	bool finished = false;

	if (!snd_seq_ev_is_variable(event))
		return 0;

	setup_ump_event(&ev_cvt, event);
	while (!finished) {
		len = snd_seq_expand_var_event_at(event, sizeof(buf), buf, offset);
		if (len <= 0)
			break;
		if (WARN_ON(len > sizeof(buf)))
			break;

		xbuf = buf;
		status = UMP_SYSEX_STATUS_CONTINUE;
		/* truncate the sysex start-marker */
		if (*xbuf == UMP_MIDI1_MSG_SYSEX_START) {
			status = UMP_SYSEX_STATUS_START;
			len--;
			offset++;
			xbuf++;
		}

		/* if the last of this packet or the 1st byte of the next packet
		 * is the end-marker, finish the transfer with this packet
		 */
		if (len > 0 && len < 8 &&
		    xbuf[len - 1] == UMP_MIDI1_MSG_SYSEX_END) {
			if (status == UMP_SYSEX_STATUS_START)
				status = UMP_SYSEX_STATUS_SINGLE;
			else
				status = UMP_SYSEX_STATUS_END;
			len--;
			finished = true;
		}

		len = min(len, 6);
		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;
		offset += len;
	}
	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 (dest->group_filter & (1U << dest_port->ump_group))
		return 0; /* group filtered - skip the event */
	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) && !dest_port->is_midi1)
		return cvt_to_ump_midi2(dest, dest_port, event, atomic, hop);
	else
		return cvt_to_ump_midi1(dest, dest_port, event, atomic, hop);
}