seq: Add UMP support

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>
This commit is contained in:
Takashi Iwai 2022-11-17 15:49:44 +01:00
parent c40dc19a57
commit 2aefb5c41c
9 changed files with 604 additions and 51 deletions

View file

@ -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
*/