alsa: add UMP support

Use the new UMP alsa sequencer API to make it produce UMP packets.

Set the alsa sequencer to MIDI2.0, which will make it convert all
messages to MIDI-2.0 UMP automatically. We can copy this straight into
the control buffers.

This also solves some problems with large sysex messages that are now
nicely split into chunks with UMP.
This commit is contained in:
Wim Taymans 2024-07-26 15:15:52 +02:00
parent 7f38653589
commit 94bb5a2dd2
3 changed files with 42 additions and 55 deletions

View file

@ -17,6 +17,7 @@
#include <spa/monitor/device.h>
#include <spa/param/audio/format.h>
#include <spa/param/latency-utils.h>
#include <spa/control/control.h>
#include <spa/pod/filter.h>
#include "alsa-seq.h"
@ -290,7 +291,7 @@ static void emit_port_info(struct seq_state *this, struct seq_port *port, bool f
clean_name(alias);
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi");
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit raw UMP");
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_OBJECT_PATH, path);
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, name);
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_ALIAS, alias);
@ -544,7 +545,8 @@ impl_node_port_enum_params(void *object, int seq,
param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control));
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control),
SPA_FORMAT_CONTROL_types, SPA_POD_CHOICE_FLAGS_Int(1u<<SPA_CONTROL_UMP));
break;
case SPA_PARAM_Format:
@ -555,7 +557,8 @@ impl_node_port_enum_params(void *object, int seq,
param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Format, SPA_PARAM_Format,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control));
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control),
SPA_FORMAT_CONTROL_types, SPA_POD_Int(1u<<SPA_CONTROL_UMP));
break;
case SPA_PARAM_Buffers:
@ -648,6 +651,7 @@ static int port_set_format(void *object, struct seq_port *port,
port->have_format = false;
} else {
struct spa_audio_info info = { 0 };
uint32_t types;
if ((err = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0)
return err;
@ -656,6 +660,13 @@ static int port_set_format(void *object, struct seq_port *port,
info.media_subtype != SPA_MEDIA_SUBTYPE_control)
return -EINVAL;
if ((err = spa_pod_parse_object(format,
SPA_TYPE_OBJECT_Format, NULL,
SPA_FORMAT_CONTROL_types, SPA_POD_Int(&types))) < 0)
return err;
if (types != 1u << SPA_CONTROL_UMP)
return -EINVAL;
port->current_format = info;
port->have_format = true;
}

View file

@ -16,6 +16,7 @@
#include <spa/pod/filter.h>
#include <spa/support/system.h>
#include <spa/control/control.h>
#include <spa/control/ump-utils.h>
#include "alsa.h"
@ -36,6 +37,7 @@ static int seq_open(struct seq_state *state, struct seq_conn *conn, bool with_qu
0)) < 0) {
return res;
}
snd_seq_set_client_midi_version(conn->hndl, SND_SEQ_CLIENT_UMP_MIDI_2_0);
return 0;
}
@ -164,7 +166,7 @@ static void init_ports(struct seq_state *state)
}
}
static void debug_event(struct seq_state *state, snd_seq_event_t *ev)
static void debug_event(struct seq_state *state, snd_seq_ump_event_t *ev)
{
if (SPA_LIKELY(!spa_log_level_topic_enabled(state->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_TRACE)))
return;
@ -191,10 +193,10 @@ static void debug_event(struct seq_state *state, snd_seq_event_t *ev)
static void alsa_seq_on_sys(struct spa_source *source)
{
struct seq_state *state = source->data;
snd_seq_event_t *ev;
snd_seq_ump_event_t *ev;
int res;
while (snd_seq_event_input(state->sys.hndl, &ev) > 0) {
while (snd_seq_ump_event_input(state->sys.hndl, &ev) > 0) {
const snd_seq_addr_t *addr = &ev->data.addr;
if (addr->client == state->event.addr.client)
@ -240,7 +242,6 @@ static void alsa_seq_on_sys(struct spa_source *source)
break;
}
snd_seq_free_event(ev);
}
}
@ -522,15 +523,15 @@ static int process_recycle(struct seq_state *state)
static int process_read(struct seq_state *state)
{
snd_seq_event_t *ev;
snd_seq_ump_event_t *ev;
struct seq_stream *stream = &state->streams[SPA_DIRECTION_OUTPUT];
uint32_t i;
uint32_t *data;
long size;
uint8_t data[MAX_EVENT_SIZE];
int res;
/* copy all new midi events into their port buffers */
while ((res = snd_seq_event_input(state->event.hndl, &ev)) > 0) {
while ((res = snd_seq_ump_event_input(state->event.hndl, &ev)) > 0) {
const snd_seq_addr_t *addr = &ev->source;
struct seq_port *port;
uint64_t ev_time, diff;
@ -552,11 +553,8 @@ static int process_read(struct seq_state *state)
continue;
}
snd_midi_event_reset_decode(stream->codec);
if ((size = snd_midi_event_decode(stream->codec, data, MAX_EVENT_SIZE, ev)) < 0) {
spa_log_warn(state->log, "decode failed: %s", snd_strerror(size));
continue;
}
data = (uint32_t*)&ev->ump[0];
size = spa_ump_message_size(snd_ump_msg_hdr_type(ev->ump[0])) * 4;
/* queue_time is the estimated current time of the queue as calculated by
* the DLL. Calculate the age of the event. */
@ -576,11 +574,9 @@ static int process_read(struct seq_state *state)
spa_log_trace_fp(state->log, "event %d time:%"PRIu64" offset:%d size:%ld port:%d.%d",
ev->type, ev_time, offset, size, addr->client, addr->port);
spa_pod_builder_control(&port->builder, offset, SPA_CONTROL_Midi);
spa_pod_builder_control(&port->builder, offset, SPA_CONTROL_UMP);
spa_pod_builder_bytes(&port->builder, data, size);
snd_seq_free_event(ev);
/* make sure we can fit at least one control event of max size otherwise
* we keep the event in the queue and try to copy it in the next cycle */
if (port->builder.state.offset +
@ -659,10 +655,9 @@ static int process_write(struct seq_state *state)
struct spa_pod_sequence *pod;
struct spa_data *d;
struct spa_pod_control *c;
snd_seq_event_t ev;
snd_seq_ump_event_t ev;
uint64_t out_time;
snd_seq_real_time_t out_rt;
long size = 0;
if (!port->valid || io == NULL)
continue;
@ -686,53 +681,33 @@ static int process_write(struct seq_state *state)
}
SPA_POD_SEQUENCE_FOREACH(pod, c) {
long s, body_size;
size_t body_size;
uint8_t *body;
if (c->type != SPA_CONTROL_Midi)
if (c->type != SPA_CONTROL_UMP)
continue;
body = SPA_POD_BODY(&c->value);
body_size = SPA_POD_BODY_SIZE(&c->value);
spa_zero(ev);
while (body_size > 0) {
if (size == 0)
/* only reset when we start decoding a new message */
snd_seq_ev_clear(&ev);
memcpy(ev.ump, body, SPA_MIN(sizeof(ev.ump), (size_t)body_size));
if ((s = snd_midi_event_encode(stream->codec,
body, body_size, &ev)) < 0) {
spa_log_warn(state->log, "failed to encode event: %s",
snd_strerror(s));
snd_midi_event_reset_encode(stream->codec);
size = 0;
break;
}
body += s;
body_size -= s;
size += s;
if (ev.type == SND_SEQ_EVENT_NONE)
/* this can happen when the event is not complete yet, like
* a sysex message and we need to encode some more data. */
break;
snd_seq_ev_set_source(&ev, state->event.addr.port);
snd_seq_ev_set_dest(&ev, port->addr.client, port->addr.port);
snd_seq_ev_set_source(&ev, state->event.addr.port);
snd_seq_ev_set_dest(&ev, port->addr.client, port->addr.port);
out_time = state->queue_time + NSEC_FROM_CLOCK(&state->rate, c->offset);
out_time = state->queue_time + NSEC_FROM_CLOCK(&state->rate, c->offset);
out_rt.tv_nsec = out_time % SPA_NSEC_PER_SEC;
out_rt.tv_sec = out_time / SPA_NSEC_PER_SEC;
snd_seq_ev_schedule_real(&ev, state->event.queue_id, 0, &out_rt);
out_rt.tv_nsec = out_time % SPA_NSEC_PER_SEC;
out_rt.tv_sec = out_time / SPA_NSEC_PER_SEC;
snd_seq_ev_schedule_real(&ev, state->event.queue_id, 0, &out_rt);
spa_log_trace_fp(state->log, "event %d time:%"PRIu64" offset:%d size:%ld port:%d.%d",
ev.type, out_time, c->offset, size, port->addr.client, port->addr.port);
spa_log_trace_fp(state->log, "event %d time:%"PRIu64" offset:%d size:%ld port:%d.%d",
ev.type, out_time, c->offset, size, port->addr.client, port->addr.port);
if ((err = snd_seq_event_output(state->event.hndl, &ev)) < 0) {
spa_log_warn(state->log, "failed to output event: %s",
snd_strerror(err));
}
size = 0;
if ((err = snd_seq_ump_event_output(state->event.hndl, &ev)) < 0) {
spa_log_warn(state->log, "failed to output event: %s",
snd_strerror(err));
}
}
}

View file

@ -13,6 +13,7 @@ extern "C" {
#include <math.h>
#include <alsa/asoundlib.h>
#include <alsa/ump_msg.h>
#include <spa/support/plugin.h>
#include <spa/support/loop.h>