diff mbox series

[alsa-lib,v2,10/10] seq: Add UMP support

Message ID 20230523122247.11744-11-tiwai@suse.de
State New
Headers show
Series Add MIDI 2.0 support | expand

Commit Message

Takashi Iwai May 23, 2023, 12:22 p.m. UTC
This patch adds the basic support of UMP on ALSA sequencer API.
An extended event type, snd_seq_ump_event_t, is defined.  It's
compatible with the existing type, snd_seq_event_t, but it has a
larger payload of 16 bytes instead of 12 bytes, for holding the full
128bit UMP packet.

The new snd_seq_ump_event_t must have the bit SND_SEQ_EVENT_UMP in the
event flags.

A few new API functions have been added such as
snd_seq_ump_event_output() and snd_seq_ump_event_input() for
reading/writing this new event object.

The support of UMP in the sequencer client is switched by the function
snd_seq_client_set_midi_version().  It can switch from the default
legacy MIDI to UMP MIDI 1.0 or 2.0 on the fly.

The automatic event conversion among UMP and legacy clients can be
suppressed via snd_seq_client_set_ump_conversion().

The inquiry of the associated UMP Endpoints and UMP Blocks can be done
via snd_seq_get_ump_endpoint_info() and snd_seq_get_ump_block_info().

Signed-off-by: Takashi Iwai <tiwai@suse.de>
---
 include/local.h     |   2 +
 include/seq.h       |  44 +++++
 include/seq_event.h |  42 +++--
 include/seqmid.h    |  24 +++
 src/Versions.in     |  17 ++
 src/seq/seq.c       | 410 ++++++++++++++++++++++++++++++++++++++++----
 src/seq/seq_hw.c    |  72 +++++++-
 src/seq/seq_local.h |   6 +-
 src/seq/seqmid.c    |  38 ++++
 9 files changed, 604 insertions(+), 51 deletions(-)
diff mbox series

Patch

diff --git a/include/local.h b/include/local.h
index c6be21ee20ca..512e44555361 100644
--- a/include/local.h
+++ b/include/local.h
@@ -196,7 +196,9 @@ 
 #define snd_seq_real_time	sndrv_seq_real_time
 #define snd_seq_timestamp	sndrv_seq_timestamp
 #define snd_seq_event_type_t	sndrv_seq_event_type_t
+#define snd_seq_event_data	sndrv_seq_event_data
 #define snd_seq_event		sndrv_seq_event
+#define snd_seq_ump_event	sndrv_seq_ump_event
 #define snd_seq_connect		sndrv_seq_connect
 #define snd_seq_ev_note		sndrv_seq_ev_note
 #define snd_seq_ev_ctrl		sndrv_seq_ev_ctrl
diff --git a/include/seq.h b/include/seq.h
index 123a1057f568..7faf4367df3d 100644
--- a/include/seq.h
+++ b/include/seq.h
@@ -130,6 +130,13 @@  typedef enum snd_seq_client_type {
 	SND_SEQ_KERNEL_CLIENT   = 2	/**< kernel client */
 } snd_seq_client_type_t;
                         
+/** client MIDI version */
+enum {
+	SND_SEQ_CLIENT_LEGACY_MIDI = 0,		/**< Legacy client */
+	SND_SEQ_CLIENT_UMP_MIDI_1_0 = 1,	/**< UMP MIDI 1.0 */
+	SND_SEQ_CLIENT_UMP_MIDI_2_0 = 2		/**< UMP MIDI 2.0 */
+};
+
 size_t snd_seq_client_info_sizeof(void);
 /** allocate a #snd_seq_client_info_t container on stack */
 #define snd_seq_client_info_alloca(ptr) \
@@ -149,11 +156,19 @@  const unsigned char *snd_seq_client_info_get_event_filter(const snd_seq_client_i
 int snd_seq_client_info_get_num_ports(const snd_seq_client_info_t *info);
 int snd_seq_client_info_get_event_lost(const snd_seq_client_info_t *info);
 
+int snd_seq_client_info_get_midi_version(const snd_seq_client_info_t *info);
+int snd_seq_client_info_get_ump_group_enabled(const snd_seq_client_info_t *info,
+					      int group);
+int snd_seq_client_info_get_ump_conversion(const snd_seq_client_info_t *info);
 void snd_seq_client_info_set_client(snd_seq_client_info_t *info, int client);
 void snd_seq_client_info_set_name(snd_seq_client_info_t *info, const char *name);
 void snd_seq_client_info_set_broadcast_filter(snd_seq_client_info_t *info, int val);
 void snd_seq_client_info_set_error_bounce(snd_seq_client_info_t *info, int val);
 void snd_seq_client_info_set_event_filter(snd_seq_client_info_t *info, unsigned char *filter);
+void snd_seq_client_info_set_midi_version(snd_seq_client_info_t *info, int midi_version);
+void snd_seq_client_info_set_ump_group_enabled(snd_seq_client_info_t *info,
+					       int group, int enable);
+void snd_seq_client_info_set_ump_conversion(snd_seq_client_info_t *info, int enable);
 
 void snd_seq_client_info_event_filter_clear(snd_seq_client_info_t *info);
 void snd_seq_client_info_event_filter_add(snd_seq_client_info_t *info, int event_type);
@@ -165,6 +180,11 @@  int snd_seq_get_any_client_info(snd_seq_t *handle, int client, snd_seq_client_in
 int snd_seq_set_client_info(snd_seq_t *handle, snd_seq_client_info_t *info);
 int snd_seq_query_next_client(snd_seq_t *handle, snd_seq_client_info_t *info);
 
+int snd_seq_get_ump_endpoint_info(snd_seq_t *seq, int client, void *info);
+int snd_seq_get_ump_block_info(snd_seq_t *seq, int client, int blk, void *info);
+int snd_seq_set_ump_endpoint_info(snd_seq_t *seq, const void *info);
+int snd_seq_set_ump_block_info(snd_seq_t *seq, int blk, const void *info);
+
 /*
  */
 
@@ -222,6 +242,14 @@  typedef struct _snd_seq_port_info snd_seq_port_info_t;
 #define SND_SEQ_PORT_CAP_SUBS_READ	(1<<5)	/**< allow read subscription */
 #define SND_SEQ_PORT_CAP_SUBS_WRITE	(1<<6)	/**< allow write subscription */
 #define SND_SEQ_PORT_CAP_NO_EXPORT	(1<<7)	/**< routing not allowed */
+#define SND_SEQ_PORT_CAP_INACTIVE	(1<<8)	/**< inactive port */
+#define SND_SEQ_PORT_CAP_UMP_ENDPOINT	(1<<9)	/**< UMP Endpoint port */
+
+/** port direction */
+#define SND_SEQ_PORT_DIR_UNKNOWN	0	/**< Unknown */
+#define SND_SEQ_PORT_DIR_INPUT		1	/**< Input only */
+#define SND_SEQ_PORT_DIR_OUTPUT		2	/**< Output only */
+#define SND_SEQ_PORT_DIR_BIDIRECTION	3	/**< Input/output bidirectional */
 
 /* port type */
 /** Messages sent from/to this port have device-specific semantics. */
@@ -238,6 +266,8 @@  typedef struct _snd_seq_port_info snd_seq_port_info_t;
 #define SND_SEQ_PORT_TYPE_MIDI_MT32	(1<<5)
 /** This port is compatible with the General MIDI 2 specification. */
 #define SND_SEQ_PORT_TYPE_MIDI_GM2	(1<<6)
+/** This port is a UMP port. */
+#define SND_SEQ_PORT_TYPE_MIDI_UMP	(1<<7)
 /** This port understands SND_SEQ_EVENT_SAMPLE_xxx messages
     (these are not MIDI messages). */
 #define SND_SEQ_PORT_TYPE_SYNTH		(1<<10)
@@ -283,6 +313,8 @@  int snd_seq_port_info_get_port_specified(const snd_seq_port_info_t *info);
 int snd_seq_port_info_get_timestamping(const snd_seq_port_info_t *info);
 int snd_seq_port_info_get_timestamp_real(const snd_seq_port_info_t *info);
 int snd_seq_port_info_get_timestamp_queue(const snd_seq_port_info_t *info);
+int snd_seq_port_info_get_direction(const snd_seq_port_info_t *info);
+int snd_seq_port_info_get_ump_group(const snd_seq_port_info_t *info);
 
 void snd_seq_port_info_set_client(snd_seq_port_info_t *info, int client);
 void snd_seq_port_info_set_port(snd_seq_port_info_t *info, int port);
@@ -297,6 +329,8 @@  void snd_seq_port_info_set_port_specified(snd_seq_port_info_t *info, int val);
 void snd_seq_port_info_set_timestamping(snd_seq_port_info_t *info, int enable);
 void snd_seq_port_info_set_timestamp_real(snd_seq_port_info_t *info, int realtime);
 void snd_seq_port_info_set_timestamp_queue(snd_seq_port_info_t *info, int queue);
+void snd_seq_port_info_set_direction(snd_seq_port_info_t *info, int direction);
+void snd_seq_port_info_set_ump_gruop(snd_seq_port_info_t *info, int ump_group);
 
 int snd_seq_create_port(snd_seq_t *handle, snd_seq_port_info_t *info);
 int snd_seq_delete_port(snd_seq_t *handle, int port);
@@ -572,6 +606,12 @@  void snd_seq_remove_events_set_tag(snd_seq_remove_events_t *info, int tag);
 
 int snd_seq_remove_events(snd_seq_t *handle, snd_seq_remove_events_t *info);
 
+int snd_seq_ump_event_output(snd_seq_t *seq, snd_seq_ump_event_t *ev);
+int snd_seq_ump_event_output_buffer(snd_seq_t *seq, snd_seq_ump_event_t *ev);
+int snd_seq_ump_extract_output(snd_seq_t *seq, snd_seq_ump_event_t **ev_res);
+int snd_seq_ump_event_output_direct(snd_seq_t *seq, snd_seq_ump_event_t *ev);
+int snd_seq_ump_event_input(snd_seq_t *seq, snd_seq_ump_event_t **ev);
+
 /** \} */
 
 /**
@@ -729,6 +769,10 @@  extern const unsigned int snd_seq_event_types[];
 #define snd_seq_ev_is_direct(ev) \
 	((ev)->queue == SND_SEQ_QUEUE_DIRECT)
 
+/** UMP events */
+#define snd_seq_ev_is_ump(ev) \
+	((ev)->flags & SND_SEQ_EVENT_UMP)
+
 /** \} */
 
 #ifdef __cplusplus
diff --git a/include/seq_event.h b/include/seq_event.h
index 60727f52a358..9ca384ee4a0a 100644
--- a/include/seq_event.h
+++ b/include/seq_event.h
@@ -225,6 +225,7 @@  typedef union snd_seq_timestamp {
 #define SND_SEQ_PRIORITY_HIGH		(1<<4)	/**< event should be processed before others */
 #define SND_SEQ_PRIORITY_MASK		(1<<4)	/**< mask for priority bits */
 
+#define SND_SEQ_EVENT_UMP		(1<<5)	/**< UMP packet event */
 
 /** Note event */
 typedef struct snd_seq_ev_note {
@@ -291,6 +292,19 @@  typedef struct snd_seq_ev_queue_control {
 	} param;				/**< data value union */
 } snd_seq_ev_queue_control_t;
 
+/** Sequencer event data */
+typedef union snd_seq_event_data {
+	snd_seq_ev_note_t note;		/**< note information */
+	snd_seq_ev_ctrl_t control;	/**< MIDI control information */
+	snd_seq_ev_raw8_t raw8;		/**< raw8 data */
+	snd_seq_ev_raw32_t raw32;	/**< raw32 data */
+	snd_seq_ev_ext_t ext;		/**< external data */
+	snd_seq_ev_queue_control_t queue; /**< queue control */
+	snd_seq_timestamp_t time;	/**< timestamp */
+	snd_seq_addr_t addr;		/**< address */
+	snd_seq_connect_t connect;	/**< connect information */
+	snd_seq_result_t result;	/**< operation result code */
+} snd_seq_event_data_t;
 
 /** Sequencer event */
 typedef struct snd_seq_event {
@@ -304,20 +318,24 @@  typedef struct snd_seq_event {
 	snd_seq_addr_t source;		/**< source address */
 	snd_seq_addr_t dest;		/**< destination address */
 
-	union {
-		snd_seq_ev_note_t note;		/**< note information */
-		snd_seq_ev_ctrl_t control;	/**< MIDI control information */
-		snd_seq_ev_raw8_t raw8;		/**< raw8 data */
-		snd_seq_ev_raw32_t raw32;	/**< raw32 data */
-		snd_seq_ev_ext_t ext;		/**< external data */
-		snd_seq_ev_queue_control_t queue; /**< queue control */
-		snd_seq_timestamp_t time;	/**< timestamp */
-		snd_seq_addr_t addr;		/**< address */
-		snd_seq_connect_t connect;	/**< connect information */
-		snd_seq_result_t result;	/**< operation result code */
-	} data;				/**< event data... */
+	snd_seq_event_data_t data;	/**< event data... */
 } snd_seq_event_t;
 
+/** UMP sequencer event; compatible with legacy sequencer event */
+typedef struct snd_seq_ump_event {
+	snd_seq_event_type_t type;	/**< event type */
+	unsigned char flags;		/**< event flags */
+	unsigned char tag;		/**< tag */
+	unsigned char queue;		/**< schedule queue */
+	snd_seq_timestamp_t time;	/**< schedule time */
+	snd_seq_addr_t source;		/**< source address */
+	snd_seq_addr_t dest;		/**< destination address */
+
+	union {
+		snd_seq_event_data_t data;	/**< (shared) legacy data */
+		unsigned int ump[4];		/**< UMP data bytes */
+	};
+} snd_seq_ump_event_t;
 
 /** \} */
 
diff --git a/include/seqmid.h b/include/seqmid.h
index 3986628a06f3..4089ac207861 100644
--- a/include/seqmid.h
+++ b/include/seqmid.h
@@ -284,6 +284,28 @@  extern "C" {
 	 (ev)->data.queue.queue = (q),\
 	 (ev)->data.queue.param.time.tick = (ttime))
 
+/**
+ * \brief set the event UMP flag
+ * \param ev event record
+ */
+static inline void snd_seq_ev_set_ump(snd_seq_ump_event_t *ev)
+{
+	ev->flags |= SND_SEQ_EVENT_UMP;
+	ev->type = 0; /* unused for UMP */
+}
+
+/**
+ * \brief set the event UMP flag and fill UMP raw bytes
+ * \param ev event record
+ * \param data UMP packet data
+ * \param bytes UMP packet size in bytes
+ */
+static inline void snd_seq_ev_set_ump_data(snd_seq_ump_event_t *ev, void *data, size_t bytes)
+{
+	snd_seq_ev_set_ump(ev);
+	memcpy(ev->ump, data, bytes);
+}
+
 /* set and send a queue control event */
 int snd_seq_control_queue(snd_seq_t *seq, int q, int type, int value, snd_seq_event_t *ev);
 
@@ -343,6 +365,8 @@  int snd_seq_disconnect_to(snd_seq_t *seq, int my_port, int dest_client, int dest
  */
 int snd_seq_set_client_name(snd_seq_t *seq, const char *name);
 int snd_seq_set_client_event_filter(snd_seq_t *seq, int event_type);
+int snd_seq_set_client_midi_version(snd_seq_t *seq, int midi_version);
+int snd_seq_set_client_ump_conversion(snd_seq_t *seq, int enable);
 int snd_seq_set_client_pool_output(snd_seq_t *seq, size_t size);
 int snd_seq_set_client_pool_output_room(snd_seq_t *seq, size_t size);
 int snd_seq_set_client_pool_input(snd_seq_t *seq, size_t size);
diff --git a/src/Versions.in b/src/Versions.in
index 2acf3d1889df..0c2837305039 100644
--- a/src/Versions.in
+++ b/src/Versions.in
@@ -156,4 +156,21 @@  ALSA_1.2.10 {
     @SYMBOL_PREFIX@snd_ctl_ump_next_device;
     @SYMBOL_PREFIX@snd_ctl_ump_endpoint_info;
     @SYMBOL_PREFIX@snd_ctl_ump_block_info;
+    @SYMBOL_PREFIX@snd_seq_ump_*;
+    @SYMBOL_PREFIX@snd_seq_client_info_get_midi_version;
+    @SYMBOL_PREFIX@snd_seq_seq_client_info_get_ump_group_enabled;
+    @SYMBOL_PREFIX@snd_seq_seq_client_get_ump_conversion;
+    @SYMBOL_PREFIX@snd_seq_client_info_set_midi_version;
+    @SYMBOL_PREFIX@snd_seq_seq_client_info_set_ump_group_enabled;
+    @SYMBOL_PREFIX@snd_seq_seq_client_set_ump_conversion;
+    @SYMBOL_PREFIX@snd_seq_get_ump_endpoint_info;
+    @SYMBOL_PREFIX@snd_seq_get_ump_block_info;
+    @SYMBOL_PREFIX@snd_seq_set_ump_endpoint_info;
+    @SYMBOL_PREFIX@snd_seq_set_ump_block_info;
+    @SYMBOL_PREFIX@snd_seq_port_info_get_direction;
+    @SYMBOL_PREFIX@snd_seq_port_info_get_ump_group;
+    @SYMBOL_PREFIX@snd_seq_port_info_set_direction;
+    @SYMBOL_PREFIX@snd_seq_port_info_set_ump_group;
+    @SYMBOL_PREFIX@snd_seq_set_client_midi_version;
+    @SYMBOL_PREFIX@snd_seq_set_client_ump_conversion;
 } ALSA_1.2.9;
diff --git a/src/seq/seq.c b/src/seq/seq.c
index f051426f9648..65ccaaed5896 100644
--- a/src/seq/seq.c
+++ b/src/seq/seq.c
@@ -1204,6 +1204,11 @@  size_t snd_seq_get_output_buffer_size(snd_seq_t *seq)
 	return seq->obufsize;
 }
 
+static inline size_t get_packet_size(snd_seq_t *seq)
+{
+	return seq->packet_size ? seq->packet_size : sizeof(snd_seq_event_t);
+}
+
 /**
  * \brief Return the size of input buffer
  * \param seq sequencer handle
@@ -1219,7 +1224,7 @@  size_t snd_seq_get_input_buffer_size(snd_seq_t *seq)
 	assert(seq);
 	if (!seq->ibuf)
 		return 0;
-	return seq->ibufsize * sizeof(snd_seq_event_t);
+	return seq->ibufsize * get_packet_size(seq);
 }
 
 /**
@@ -1261,13 +1266,17 @@  int snd_seq_set_output_buffer_size(snd_seq_t *seq, size_t size)
  */
 int snd_seq_set_input_buffer_size(snd_seq_t *seq, size_t size)
 {
+	size_t packet_size;
+
 	assert(seq && seq->ibuf);
-	assert(size >= sizeof(snd_seq_event_t));
+	assert(size >= packet_size);
 	snd_seq_drop_input(seq);
-	size = (size + sizeof(snd_seq_event_t) - 1) / sizeof(snd_seq_event_t);
+	packet_size = get_packet_size(seq);
+	size = (size + packet_size - 1) / packet_size;
 	if (size != seq->ibufsize) {
-		snd_seq_event_t *newbuf;
-		newbuf = calloc(sizeof(snd_seq_event_t), size);
+		char *newbuf;
+		/* use ump event size for avoiding reallocation at switching */
+		newbuf = calloc(sizeof(snd_seq_ump_event_t), size);
 		if (newbuf == NULL)
 			return -ENOMEM;
 		free(seq->ibuf);
@@ -1726,6 +1735,47 @@  int snd_seq_client_info_get_event_lost(const snd_seq_client_info_t *info)
 	return info->event_lost;
 }
 
+/**
+ * \brief Get the MIDI protocol version number of a client_info container
+ * \param info client_info container
+ * \return MIDI protocol version
+ *
+ * \sa snd_seq_get_client_info()
+ */
+int snd_seq_client_info_get_midi_version(const snd_seq_client_info_t *info)
+{
+	assert(info);
+	return info->midi_version;
+}
+
+/**
+ * \brief Get the UMP group filter status
+ * \param info client_info container
+ * \param group 0-based group index
+ * \return 0 if the group is filtered / disabled, 1 if it's processed
+ *
+ * \sa snd_seq_get_client_info()
+ */
+int snd_seq_client_info_get_ump_group_enabled(const snd_seq_client_info_t *info,
+					      int group)
+{
+	assert(info);
+	return !(info->group_filter & (1U << group));
+}
+
+/**
+ * \brief Get the automatic conversion mode for UMP
+ * \param info client_info container
+ * \return 1 if the conversion is enabled, 0 if not
+ *
+ * \sa snd_seq_get_client_info()
+ */
+int snd_seq_client_info_get_ump_conversion(const snd_seq_client_info_t *info)
+{
+	assert(info);
+	return info->midi_version;
+}
+
 /**
  * \brief Set the client id of a client_info container
  * \param info client_info container
@@ -1769,6 +1819,54 @@  void snd_seq_client_info_set_broadcast_filter(snd_seq_client_info_t *info, int v
 		info->filter &= ~SNDRV_SEQ_FILTER_BROADCAST;
 }
 
+/**
+ * \brief Set the MIDI protocol version of a client_info container
+ * \param info client_info container
+ * \param midi_version MIDI protocol version to set
+ *
+ * \sa snd_seq_get_client_info(), snd_seq_client_info_get_midi_version()
+ */
+void snd_seq_client_info_set_midi_version(snd_seq_client_info_t *info, int midi_version)
+{
+	assert(info);
+	info->midi_version = midi_version;
+}
+
+/**
+ * \brief Set the UMP group filter status
+ * \param info client_info container
+ * \param group 0-based group index
+ * \param enable 0 to filter/disable the group, non-zero to enable
+ *
+ * \sa snd_seq_set_client_info(), snd_seq_client_info_get_ump_group_enabled()
+ */
+void snd_seq_client_info_set_ump_group_enabled(snd_seq_client_info_t *info,
+					       int group, int enable)
+{
+	assert(info);
+	if (enable)
+		info->group_filter &= ~(1U << group);
+	else
+		info->group_filter |= (1U << group);
+}
+
+/**
+ * \brief Set the automatic conversion mode for UMP
+ * \param info client_info container
+ * \param enable 0 or 1 for disabling/enabling the conversion
+ *
+ * \sa snd_seq_set_client_info(), snd_seq_client_info_get_ump_conversion()
+ */
+void snd_seq_client_info_set_ump_conversion(snd_seq_client_info_t *info,
+					    int enable)
+{
+	assert(info);
+	if (enable)
+		info->filter &= ~SNDRV_SEQ_FILTER_NO_CONVERT;
+	else
+		info->filter |= SNDRV_SEQ_FILTER_NO_CONVERT;
+}
+
 /**
  * \brief Set the error-bounce usage of a client_info container
  * \param info client_info container
@@ -1887,6 +1985,65 @@  int snd_seq_query_next_client(snd_seq_t *seq, snd_seq_client_info_t *info)
 	return seq->ops->query_next_client(seq, info);
 }
 
+/**
+ * \brief Get UMP Endpoint information
+ * \param seq sequencer handle
+ * \param client client number to query
+ * \param info the pointer to store snd_ump_endpoint_info_t data
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_seq_get_ump_endpoint_info(snd_seq_t *seq, int client, void *info)
+{
+	assert(seq && info);
+	return seq->ops->get_ump_info(seq, client,
+				      SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT,
+				      info);
+}
+
+/**
+ * \brief Get UMP Block information
+ * \param seq sequencer handle
+ * \param client sequencer client number to query
+ * \param blk UMP block number (0-based) to query
+ * \param info the pointer to store snd_ump_block_info_t data
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_seq_get_ump_block_info(snd_seq_t *seq, int client, int blk, void *info)
+{
+	assert(seq && info);
+	return seq->ops->get_ump_info(seq, client,
+				      SNDRV_SEQ_CLIENT_UMP_INFO_BLOCK + blk,
+				      info);
+}
+
+/**
+ * \brief Set UMP Endpoint information to the current client
+ * \param seq sequencer handle
+ * \param info the pointer to send snd_ump_endpoint_info_t data
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_seq_set_ump_endpoint_info(snd_seq_t *seq, const void *info)
+{
+	assert(seq && info);
+	return seq->ops->set_ump_info(seq,
+				      SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT,
+				      info);
+}
+
+/**
+ * \brief Set UMP Block information to the current client
+ * \param seq sequencer handle
+ * \param blk UMP block number (0-based) to send
+ * \param info the pointer to send snd_ump_block_info_t data
+ * \return 0 on success otherwise a negative error code
+ */
+int snd_seq_set_ump_block_info(snd_seq_t *seq, int blk, const void *info)
+{
+	assert(seq && info);
+	return seq->ops->set_ump_info(seq,
+				      SNDRV_SEQ_CLIENT_UMP_INFO_BLOCK + blk,
+				      info);
+}
 
 /*----------------------------------------------------------------*/
 
@@ -2134,6 +2291,32 @@  int snd_seq_port_info_get_timestamp_queue(const snd_seq_port_info_t *info)
 	return info->time_queue;
 }
 
+/**
+ * \brief Get the direction of the port
+ * \param info port_info container
+ * \return the direction of the port
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_set_direction()
+ */
+int snd_seq_port_info_get_direction(const snd_seq_port_info_t *info)
+{
+	assert(info);
+	return info->direction;
+}
+
+/**
+ * \brief Get the UMP Group assigned to the port
+ * \param info port_info container
+ * \return 0 for no conversion, or the (1-based) UMP Group number assigned to the port
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_set_ump_group()
+ */
+int snd_seq_port_info_get_ump_group(const snd_seq_port_info_t *info)
+{
+	assert(info);
+	return info->ump_group;
+}
+
 /**
  * \brief Set the client id of a port_info container
  * \param info port_info container
@@ -2312,6 +2495,31 @@  void snd_seq_port_info_set_timestamp_queue(snd_seq_port_info_t *info, int queue)
 	info->time_queue = queue;
 }
 
+/**
+ * \brief Set the direction of the port
+ * \param info port_info container
+ * \param direction the port direction
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_get_direction()
+ */
+void snd_seq_port_info_set_direction(snd_seq_port_info_t *info, int direction)
+{
+	assert(info);
+	info->direction = direction;
+}
+
+/**
+ * \brief Set the UMP Group assigned to the port
+ * \param info port_info container
+ * \param ump_group 0 for no conversion, or the (1-based) UMP Group number
+ *
+ * \sa snd_seq_get_port_info(), snd_seq_port_info_get_ump_gruop()
+ */
+void snd_seq_port_info_set_ump_group(snd_seq_port_info_t *info, int ump_group)
+{
+	assert(info);
+	info->ump_group = ump_group;
+}
 
 /**
  * \brief create a sequencer port on the current client
@@ -3874,7 +4082,9 @@  ssize_t snd_seq_event_length(snd_seq_event_t *ev)
 {
 	ssize_t len = sizeof(snd_seq_event_t);
 	assert(ev);
-	if (snd_seq_ev_is_variable(ev))
+	if (snd_seq_ev_is_ump(ev))
+		len = sizeof(snd_seq_ump_event_t);
+	else if (snd_seq_ev_is_variable(ev))
 		len += ev->data.ext.len;
 	return len;
 }
@@ -3925,7 +4135,10 @@  int snd_seq_event_output(snd_seq_t *seq, snd_seq_event_t *ev)
  *
  * This function doesn't drain buffer unlike snd_seq_event_output().
  *
- * \sa snd_seq_event_output()
+ * \note
+ * For a UMP event, use snd_seq_ump_event_output_buffer() instead.
+ *
+ * \sa snd_seq_event_output(), snd_seq_ump_event_output_buffer()
  */
 int snd_seq_event_output_buffer(snd_seq_t *seq, snd_seq_event_t *ev)
 {
@@ -3938,12 +4151,15 @@  int snd_seq_event_output_buffer(snd_seq_t *seq, snd_seq_event_t *ev)
 		return -EINVAL;
 	if ((seq->obufsize - seq->obufused) < (size_t) len)
 		return -EAGAIN;
-	memcpy(seq->obuf + seq->obufused, ev, sizeof(snd_seq_event_t));
-	seq->obufused += sizeof(snd_seq_event_t);
-	if (snd_seq_ev_is_variable(ev)) {
-		memcpy(seq->obuf + seq->obufused, ev->data.ext.ptr, ev->data.ext.len);
-		seq->obufused += ev->data.ext.len;
+	if (snd_seq_ev_is_ump(ev)) {
+		memcpy(seq->obuf + seq->obufused, ev, sizeof(snd_seq_ump_event_t));
+	} else {
+		memcpy(seq->obuf + seq->obufused, ev, sizeof(snd_seq_event_t));
+		if (snd_seq_ev_is_variable(ev))
+			memcpy(seq->obuf + seq->obufused + sizeof(snd_seq_event_t),
+			       ev->data.ext.ptr, ev->data.ext.len);
 	}
+	seq->obufused += len;
 	return seq->obufused;
 }
 
@@ -3991,7 +4207,7 @@  int snd_seq_event_output_direct(snd_seq_t *seq, snd_seq_event_t *ev)
 	len = snd_seq_event_length(ev);
 	if (len < 0)
 		return len;
-	else if (len == sizeof(*ev)) {
+	if (snd_seq_ev_is_ump(ev) || !snd_seq_ev_is_variable(ev)) {
 		buf = ev;
 	} else {
 		if (alloc_tmpbuf(seq, (size_t)len) < 0)
@@ -4049,6 +4265,36 @@  int snd_seq_drain_output(snd_seq_t *seq)
 	return 0;
 }
 
+static int extract_output(snd_seq_t *seq, snd_seq_event_t **ev_res, int ump_allowed)
+{
+	size_t len, olen;
+	assert(seq);
+	if (ev_res)
+		*ev_res = NULL;
+ repeat:
+	if ((olen = seq->obufused) < sizeof(snd_seq_event_t))
+		return -ENOENT;
+	len = snd_seq_event_length((snd_seq_event_t *)seq->obuf);
+	if (olen < len)
+		return -ENOENT;
+	/* skip invalid UMP events */
+	if (snd_seq_ev_is_ump((snd_seq_event_t *)seq->obuf) && !ump_allowed) {
+		seq->obufused -= len;
+		memmove(seq->obuf, seq->obuf + len, seq->obufused);
+		goto repeat;
+	}
+	if (ev_res) {
+		/* extract the event */
+		if (alloc_tmpbuf(seq, len) < 0)
+			return -ENOMEM;
+		memcpy(seq->tmpbuf, seq->obuf, len);
+		*ev_res = (snd_seq_event_t *)seq->tmpbuf;
+	}
+	seq->obufused = olen - len;
+	memmove(seq->obuf, seq->obuf + len, seq->obufused);
+	return 0;
+}
+
 /**
  * \brief extract the first event in output buffer
  * \param seq sequencer handle
@@ -4062,25 +4308,7 @@  int snd_seq_drain_output(snd_seq_t *seq)
  */
 int snd_seq_extract_output(snd_seq_t *seq, snd_seq_event_t **ev_res)
 {
-	size_t len, olen;
-	snd_seq_event_t ev;
-	assert(seq);
-	if (ev_res)
-		*ev_res = NULL;
-	if ((olen = seq->obufused) < sizeof(snd_seq_event_t))
-		return -ENOENT;
-	memcpy(&ev, seq->obuf, sizeof(snd_seq_event_t));
-	len = snd_seq_event_length(&ev);
-	if (ev_res) {
-		/* extract the event */
-		if (alloc_tmpbuf(seq, len) < 0)
-			return -ENOMEM;
-		memcpy(seq->tmpbuf, seq->obuf, len);
-		*ev_res = seq->tmpbuf;
-	}
-	seq->obufused = olen - len;
-	memmove(seq->obuf, seq->obuf + len, seq->obufused);
-	return 0;
+	return extract_output(seq, ev_res, 0);
 }
 
 /*----------------------------------------------------------------*/
@@ -4094,32 +4322,35 @@  int snd_seq_extract_output(snd_seq_t *seq, snd_seq_event_t **ev_res)
  */
 static ssize_t snd_seq_event_read_buffer(snd_seq_t *seq)
 {
+	size_t packet_size = get_packet_size(seq);
 	ssize_t len;
-	len = (seq->ops->read)(seq, seq->ibuf, seq->ibufsize * sizeof(snd_seq_event_t));
+
+	len = (seq->ops->read)(seq, seq->ibuf, seq->ibufsize * packet_size);
 	if (len < 0)
 		return len;
-	seq->ibuflen = len / sizeof(snd_seq_event_t);
+	seq->ibuflen = len / packet_size;
 	seq->ibufptr = 0;
 	return seq->ibuflen;
 }
 
 static int snd_seq_event_retrieve_buffer(snd_seq_t *seq, snd_seq_event_t **retp)
 {
+	size_t packet_size = get_packet_size(seq);
 	size_t ncells;
 	snd_seq_event_t *ev;
 
-	*retp = ev = &seq->ibuf[seq->ibufptr];
+	*retp = ev = (snd_seq_event_t *)(seq->ibuf + seq->ibufptr * packet_size);
 	seq->ibufptr++;
 	seq->ibuflen--;
 	if (! snd_seq_ev_is_variable(ev))
 		return 1;
-	ncells = (ev->data.ext.len + sizeof(snd_seq_event_t) - 1) / sizeof(snd_seq_event_t);
+	ncells = (ev->data.ext.len + packet_size - 1) / packet_size;
 	if (seq->ibuflen < ncells) {
 		seq->ibuflen = 0; /* clear buffer */
 		*retp = NULL;
 		return -EINVAL;
 	}
-	ev->data.ext.ptr = ev + 1;
+	ev->data.ext.ptr = (char *)ev + packet_size;
 	seq->ibuflen -= ncells;
 	seq->ibufptr += ncells;
 	return 1;
@@ -4212,6 +4443,111 @@  int snd_seq_event_input_pending(snd_seq_t *seq, int fetch_sequencer)
 
 /*----------------------------------------------------------------*/
 
+/*
+ * I/O for UMP packets
+ */
+
+/**
+ * \brief output a UMP event
+ * \param seq sequencer handle
+ * \param ev UMP event to be output
+ * \return the number of remaining events or a negative error code
+ *
+ * Just like snd_seq_event_output(), it puts an event onto the buffer,
+ * draining the buffer automatically when needed, but the event is
+ * snd_seq_ump_event_t type instead snd_seq_event_t.
+ *
+ * Calling this function is allowed only when the client is set to
+ * \c SND_SEQ_CLIENT_UMP_MIDI_1_0 or \c SND_SEQ_CLIENT_UMP_MIDI_2_0.
+ *
+ * The flushing and clearing of the buffer is done via the same functions,
+ * snd_seq_event_drain_output() and snd_seq_drop_output().
+ *
+ * \sa snd_seq_event_output()
+ */
+int snd_seq_ump_event_output(snd_seq_t *seq, snd_seq_ump_event_t *ev)
+{
+	if (!seq->midi_version)
+		return -EBADFD;
+	return snd_seq_event_output(seq, (snd_seq_event_t *)ev);
+}
+
+/**
+ * \brief output an event onto the lib buffer without draining buffer
+ * \param seq sequencer handle
+ * \param ev UMP event to be output
+ * \return the byte size of remaining events. \c -EAGAIN if the buffer becomes full.
+ *
+ * This is a UMP event version of snd_seq_event_output_buffer().
+ *
+ * \sa snd_seq_event_output_buffer(), snd_seq_ump_event_output()
+ */
+int snd_seq_ump_event_output_buffer(snd_seq_t *seq, snd_seq_ump_event_t *ev)
+{
+	if (!seq->midi_version)
+		return -EBADFD;
+	return snd_seq_event_output_buffer(seq, (snd_seq_event_t *)ev);
+}
+
+/**
+ * \brief extract the first UMP event in output buffer
+ * \param seq sequencer handle
+ * \param ev_res UMP event pointer to be extracted
+ * \return 0 on success otherwise a negative error code
+ *
+ * This is a UMP event version of snd_seq_extract_output().
+ *
+ * \sa snd_seq_extract_output(), snd_seq_ump_event_output()
+ */
+int snd_seq_ump_extract_output(snd_seq_t *seq, snd_seq_ump_event_t **ev_res)
+{
+	if (!seq->midi_version)
+		return -EBADFD;
+	return extract_output(seq, (snd_seq_event_t **)ev_res, 1);
+}
+
+/**
+ * \brief output a UMP event directly to the sequencer NOT through output buffer
+ * \param seq sequencer handle
+ * \param ev UMP event to be output
+ * \return the byte size sent to sequencer or a negative error code
+ *
+ * This is a UMP event version of snd_seq_event_output_direct().
+ *
+ * \sa snd_seq_event_output_direct()
+ */
+int snd_seq_ump_event_output_direct(snd_seq_t *seq, snd_seq_ump_event_t *ev)
+{
+	if (!seq->midi_version)
+		return -EBADFD;
+	return snd_seq_event_output_direct(seq, (snd_seq_event_t *)ev);
+}
+
+/**
+ * \brief retrieve a UMP event from sequencer
+ * \param seq sequencer handle
+ * \param ev UMP event pointer to be stored
+ *
+ * Like snd_seq_event_input(), this reads out the input event, but in
+ * snd_seq_ump_event_t type instead of snd_seq_event_t type.
+ *
+ * Calling this function is allowed only when the client is set to
+ * \c SND_SEQ_CLIENT_UMP_MIDI_1_0 or \c SND_SEQ_CLIENT_UMP_MIDI_2_0.
+ *
+ * For other input operations, the same function like
+ * snd_seq_event_input_pending() or snd_seq_drop_input() can be still used.
+ *
+ * \sa snd_seq_event_input()
+ */
+int snd_seq_ump_event_input(snd_seq_t *seq, snd_seq_ump_event_t **ev)
+{
+	if (!seq->midi_version)
+		return -EBADFD;
+	return snd_seq_event_input(seq, (snd_seq_event_t **)ev);
+}
+
+/*----------------------------------------------------------------*/
+
 /*
  * clear event buffers
  */
diff --git a/src/seq/seq_hw.c b/src/seq/seq_hw.c
index e4b4d2a02a0d..196de970f6f3 100644
--- a/src/seq/seq_hw.c
+++ b/src/seq/seq_hw.c
@@ -94,6 +94,20 @@  static int snd_seq_hw_system_info(snd_seq_t *seq, snd_seq_system_info_t * info)
 	return 0;
 }
 
+static void update_midi_version(snd_seq_t *seq, snd_seq_client_info_t *info)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+
+	if (SNDRV_PROTOCOL_VERSION(1, 0, 3) <= hw->version &&
+	    seq->midi_version != (int)info->midi_version) {
+		seq->midi_version = info->midi_version;
+		if (info->midi_version > 0)
+			seq->packet_size = sizeof(snd_seq_ump_event_t);
+		else
+			seq->packet_size = sizeof(snd_seq_event_t);
+	}
+}
+
 static int snd_seq_hw_get_client_info(snd_seq_t *seq, snd_seq_client_info_t * info)
 {
 	snd_seq_hw_t *hw = seq->private_data;
@@ -105,16 +119,64 @@  static int snd_seq_hw_get_client_info(snd_seq_t *seq, snd_seq_client_info_t * in
 		info->card = -1;
 		info->pid = -1;
 	}
+	update_midi_version(seq, info);
 	return 0;
 }
 
 static int snd_seq_hw_set_client_info(snd_seq_t *seq, snd_seq_client_info_t * info)
 {
 	snd_seq_hw_t *hw = seq->private_data;
+
 	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, info) < 0) {
 		/*SYSERR("SNDRV_SEQ_IOCTL_SET_CLIENT_INFO failed");*/
 		return -errno;
 	}
+	update_midi_version(seq, info);
+	return 0;
+}
+
+static int snd_seq_hw_get_ump_info(snd_seq_t *seq, int client, int type, void *info)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	struct snd_seq_client_ump_info buf;
+	size_t size;
+
+	if (type < 0 || type >= SNDRV_SEQ_CLIENT_UMP_INFO_BLOCK + 32)
+		return -EINVAL;
+	if (hw->version < SNDRV_PROTOCOL_VERSION(1, 0, 3))
+		return -ENOTTY;
+	if (type == SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT)
+		size = sizeof(struct snd_ump_endpoint_info);
+	else
+		size = sizeof(struct snd_ump_block_info);
+	buf.client = client;
+	buf.type = type;
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_GET_CLIENT_UMP_INFO, &buf) < 0)
+		return -errno;
+	memcpy(info, buf.info, size);
+	return 0;
+}
+
+static int snd_seq_hw_set_ump_info(snd_seq_t *seq, int type, const void *info)
+{
+	snd_seq_hw_t *hw = seq->private_data;
+	struct snd_seq_client_ump_info buf;
+	size_t size;
+
+	if (type < 0 || type >= SNDRV_SEQ_CLIENT_UMP_INFO_BLOCK + 32)
+		return -EINVAL;
+	if (hw->version < SNDRV_PROTOCOL_VERSION(1, 0, 3))
+		return -ENOTTY;
+	if (type == SNDRV_SEQ_CLIENT_UMP_INFO_ENDPOINT)
+		size = sizeof(struct snd_ump_endpoint_info);
+	else
+		size = sizeof(struct snd_ump_block_info);
+	buf.client = seq->client;
+	buf.type = type;
+	memcpy(buf.info, info, size);
+	*(int *)buf.info = -1; /* invalidate the card number */
+	if (ioctl(hw->fd, SNDRV_SEQ_IOCTL_SET_CLIENT_UMP_INFO, &buf) < 0)
+		return -errno;
 	return 0;
 }
 
@@ -396,6 +458,8 @@  static const snd_seq_ops_t snd_seq_hw_ops = {
 	.system_info = snd_seq_hw_system_info,
 	.get_client_info = snd_seq_hw_get_client_info,
 	.set_client_info = snd_seq_hw_set_client_info,
+	.get_ump_info = snd_seq_hw_get_ump_info,
+	.set_ump_info = snd_seq_hw_set_ump_info,
 	.create_port = snd_seq_hw_create_port,
 	.delete_port = snd_seq_hw_delete_port,
 	.get_port_info = snd_seq_hw_get_port_info,
@@ -476,6 +540,11 @@  int snd_seq_hw_open(snd_seq_t **handle, const char *name, int streams, int mode)
 		close(fd);
 		return -SND_ERROR_INCOMPATIBLE_VERSION;
 	}
+	if (SNDRV_PROTOCOL_VERSION(1, 0, 3) <= ver) {
+		/* inform the protocol version we're supporting */
+		unsigned int user_ver = SNDRV_SEQ_VERSION;
+		ioctl(fd, SNDRV_SEQ_IOCTL_USER_PVERSION, &user_ver);
+	}
 	hw = calloc(1, sizeof(snd_seq_hw_t));
 	if (hw == NULL) {
 		close(fd);
@@ -500,7 +569,7 @@  int snd_seq_hw_open(snd_seq_t **handle, const char *name, int streams, int mode)
 		}
 	}
 	if (streams & SND_SEQ_OPEN_INPUT) {
-		seq->ibuf = (snd_seq_event_t *) calloc(sizeof(snd_seq_event_t), seq->ibufsize = SND_SEQ_IBUF_SIZE);
+		seq->ibuf = (char *) calloc(sizeof(snd_seq_ump_event_t), seq->ibufsize = SND_SEQ_IBUF_SIZE);
 		if (!seq->ibuf) {
 			free(seq->obuf);
 			free(hw);
@@ -519,6 +588,7 @@  int snd_seq_hw_open(snd_seq_t **handle, const char *name, int streams, int mode)
 	seq->poll_fd = fd;
 	seq->ops = &snd_seq_hw_ops;
 	seq->private_data = hw;
+	seq->packet_size = sizeof(snd_seq_event_t);
 	client = snd_seq_hw_client_id(seq);
 	if (client < 0) {
 		snd_seq_close(seq);
diff --git a/src/seq/seq_local.h b/src/seq/seq_local.h
index f97a5200397a..9b4a65459d3d 100644
--- a/src/seq/seq_local.h
+++ b/src/seq/seq_local.h
@@ -41,6 +41,8 @@  typedef struct {
 	int (*system_info)(snd_seq_t *seq, snd_seq_system_info_t * info);
 	int (*get_client_info)(snd_seq_t *seq, snd_seq_client_info_t * info);
 	int (*set_client_info)(snd_seq_t *seq, snd_seq_client_info_t * info);
+	int (*get_ump_info)(snd_seq_t *seq, int client, int type, void *info);
+	int (*set_ump_info)(snd_seq_t *seq, int type, const void *info);
 	int (*create_port)(snd_seq_t *seq, snd_seq_port_info_t * port);
 	int (*delete_port)(snd_seq_t *seq, snd_seq_port_info_t * port);
 	int (*get_port_info)(snd_seq_t *seq, snd_seq_port_info_t * info);
@@ -84,12 +86,14 @@  struct _snd_seq {
 	char *obuf;		/* output buffer */
 	size_t obufsize;		/* output buffer size */
 	size_t obufused;		/* output buffer used size */
-	snd_seq_event_t *ibuf;	/* input buffer */
+	char *ibuf;		/* input buffer */
 	size_t ibufptr;		/* current pointer of input buffer */
 	size_t ibuflen;		/* queued length */
 	size_t ibufsize;		/* input buffer size */
 	snd_seq_event_t *tmpbuf;	/* temporary event for extracted event */
 	size_t tmpbufsize;		/* size of errbuf */
+	size_t packet_size;		/* input packet alignment size */
+	int midi_version;	/* current protocol version */
 };
 
 int snd_seq_hw_open(snd_seq_t **handle, const char *name, int streams, int mode);
diff --git a/src/seq/seqmid.c b/src/seq/seqmid.c
index 75061a577ef3..55651f3896f3 100644
--- a/src/seq/seqmid.c
+++ b/src/seq/seqmid.c
@@ -255,6 +255,44 @@  int snd_seq_set_client_event_filter(snd_seq_t *seq, int event_type)
 	return snd_seq_set_client_info(seq, &info);
 }
 
+/**
+ * \brief set client MIDI protocol version
+ * \param seq sequencer handle
+ * \param midi_version MIDI protocol version to set
+ * \return 0 on success or negative error code
+ *
+ * \sa snd_seq_set_client_info()
+ */
+int snd_seq_set_client_midi_version(snd_seq_t *seq, int midi_version)
+{
+	snd_seq_client_info_t info;
+	int err;
+
+	if ((err = snd_seq_get_client_info(seq, &info)) < 0)
+		return err;
+	snd_seq_client_info_set_midi_version(&info, midi_version);
+	return snd_seq_set_client_info(seq, &info);
+}
+
+/**
+ * \brief enable/disable client's automatic conversion of UMP/legacy events
+ * \param seq sequencer handle
+ * \param enable 0 or 1 to disable/enable the conversion
+ * \return 0 on success or negative error code
+ *
+ * \sa snd_seq_set_client_info()
+ */
+int snd_seq_set_client_ump_conversion(snd_seq_t *seq, int enable)
+{
+	snd_seq_client_info_t info;
+	int err;
+
+	if ((err = snd_seq_get_client_info(seq, &info)) < 0)
+		return err;
+	snd_seq_client_info_set_ump_conversion(&info, enable);
+	return snd_seq_set_client_info(seq, &info);
+}
+
 /**
  * \brief change the output pool size of the given client
  * \param seq sequencer handle