alsa-seq: handle SysEx without snd_midi_event_t

When writing sysex, bypass the encoder and simply place the sysex
message in the seq queue. Make sure strip off the 0xf7 and 0xf0
continuation bytes.

When reading sysex, bypass the decoder and append/prepend the
continuation bytes.

This way, we can make the decoder buffer size back to something small
(large enough for one simple midi event) and we can handle arbitrary
sysex message lengths.
This commit is contained in:
Wim Taymans 2026-05-21 12:45:43 +02:00
parent 87a2ae2f15
commit 26a2467e7b
2 changed files with 56 additions and 33 deletions

View file

@ -629,6 +629,7 @@ static int process_read(struct seq_state *state)
/* copy all new midi events into their port buffers */
while (1) {
const snd_seq_addr_t *addr;
uint8_t head = 0, tail = 0, extra = 0, *dst;
uint64_t ev_time, diff;
uint32_t offset;
void *event;
@ -706,19 +707,45 @@ static int process_read(struct seq_state *state)
} else {
snd_seq_event_t *ev = event;
snd_midi_event_reset_decode(stream->codec);
if ((size = snd_midi_event_decode(stream->codec, midi1_data, sizeof(midi1_data), ev)) < 0) {
spa_log_warn(state->log, "decode failed: %s", snd_strerror(size));
continue;
if (ev->type == SND_SEQ_EVENT_SYSEX) {
switch (ev->flags & SND_SEQ_EVENT_LENGTH_MASK) {
case SND_SEQ_EVENT_LENGTH_FIXED:
continue;
}
size = ev->data.ext.len;
data = dst = ev->data.ext.ptr;
if (dst[size-1] != 0xf7) {
tail = 0xf0;
extra++;
}
if (dst[0] != 0xf0) {
head = 0xf7;
extra++;
}
} else {
snd_midi_event_reset_decode(stream->codec);
if ((size = snd_midi_event_decode(stream->codec,
midi1_data, sizeof(midi1_data), ev)) < 0) {
spa_log_warn(state->log, "decode failed: %s", snd_strerror(size));
continue;
}
data = midi1_data;
}
data = midi1_data;
}
spa_log_trace_fp(state->log, "event %d time:%"PRIu64" offset:%d size:%ld port:%d.%d",
type, ev_time, offset, size, addr->client, addr->port);
spa_pod_builder_control(&port->builder, offset, ump ? SPA_CONTROL_UMP : SPA_CONTROL_Midi );
spa_pod_builder_bytes(&port->builder, data, size);
dst = spa_pod_builder_reserve_bytes(&port->builder, size + extra);
if (tail)
dst[size+extra-1] = tail;
if (head) {
dst[0] = head;
dst++;
}
memcpy(dst, data, size);
/* 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 */
@ -860,42 +887,38 @@ static int process_write(struct seq_state *state)
#endif
} else {
snd_seq_event_t ev;
int size = 0;
long s;
if (c.type != SPA_CONTROL_Midi)
continue;
while (body_size > 0) {
if (size == 0)
snd_seq_ev_clear(&ev);
if ((s = snd_midi_event_encode(stream->codec, body, body_size, &ev)) < 0) {
if (body[0] == 0xf0 || body[0] == 0xf7) {
if (body[body_size-1] == 0xf0)
body_size -= 1;
if (body[0] == 0xf7) {
body += 1;
body_size -= 1;
}
snd_seq_ev_set_sysex(&ev, body_size, body);
} else {
long s;
if ((s = snd_midi_event_encode(stream->codec, body,
body_size, &ev)) < 0 ||
ev.type == SND_SEQ_EVENT_NONE) {
spa_log_warn(state->log, "failed to encode event: %s",
snd_strerror(size));
snd_strerror(s));
snd_midi_event_reset_encode(stream->codec);
size = 0;
continue;
}
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. */
continue;
}
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_schedule_real(&ev, state->event.queue_id, 0, &out_rt);
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_schedule_real(&ev, state->event.queue_id, 0, &out_rt);
debug_event(state, "send", &ev);
debug_event(state, "send", &ev);
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_event_output(state->event.hndl, &ev)) < 0) {
spa_log_warn(state->log, "failed to output event: %s",
snd_strerror(err));
}
}
}

View file

@ -39,7 +39,7 @@ struct props {
bool disable_longname;
};
#define MAX_EVENT_SIZE 1024
#define MAX_EVENT_SIZE 64
#define MAX_PORTS 256
#define MAX_BUFFERS 32