pw-cat: split SysEx over multiple packets

Use the same method as RTP Midi to split long SysEx messages into chunks
that we can then put into buffers and reassemble again later.
This commit is contained in:
Wim Taymans 2026-05-20 16:08:04 +02:00
parent 4e389940e8
commit 87a2ae2f15
2 changed files with 65 additions and 4 deletions

View file

@ -69,6 +69,33 @@ and consumers must be enabled explicitly. UMP in producers is supported
still and will be converted to Midi1 by all consumers that did not explicitly
enable UMP support.
## SysEx
SysEx messages in Midi1 can be of unlimited length. They start with an
F0 byte and end with a F7 byte. Because of the buffer data length limitations,
it might be necessary to split a MIDI1 SysEx message accross multiple
buffers.
The stategy to implement this is specified in RFC 6295 (RTP Midi) section
3.2. Long SysEx messages can be split up into parts by using the following
start/end bytes combinations:
|-----------------------------------------------------------|
| Sublist Position | Head Status Octet | Tail Status Octet |
|-----------------------------------------------------------|
| first | 0xF0 | 0xF0 |
|-----------------------------------------------------------|
| middle | 0xF7 | 0xF0 |
|-----------------------------------------------------------|
| last | 0xF7 | 0xF7 |
|-----------------------------------------------------------|
| cancel | 0xF7 | 0xF4 |
-----------------------------------------------------------
Nodes that require a complete SysEx message must be able to assemble the
complete message from the parts before processing the message.
## The PipeWire Daemon
Nothing special is implemented for MIDI. Negotiation of formats

View file

@ -185,6 +185,7 @@ struct data {
struct {
FILE *file;
bool close;
bool first;
} sysex;
struct {
FILE *file;
@ -1552,16 +1553,48 @@ static int sysex_play(struct data *d, void *dst, unsigned int n_frames, bool *nu
struct spa_pod_builder b;
struct spa_pod_frame f;
size_t size, to_read = n_frames - 64;
uint8_t bytes[to_read];
uint8_t data[to_read+2], *bytes;
bytes = &data[1];
size = fread(bytes, 1, to_read, d->sysex.file);
if (size != to_read) {
if (ferror(d->sysex.file))
return -EIO;
}
if (feof(d->sysex.file)) {
if (size == 0)
return 0;
if (bytes[size-1] != 0xf7) {
bytes[size] = 0xf7;
size += 1;
}
} else {
if (bytes[size-1] != 0xf0) {
bytes[size] = 0xf0;
size += 1;
}
}
if (d->sysex.first) {
if (bytes[0] != 0xf0) {
bytes = &data[0];
bytes[0] = 0xf0;
size += 1;
}
d->sysex.first = false;
} else {
if (bytes[0] != 0xf7) {
bytes = &data[0];
bytes[0] = 0xf7;
size += 1;
}
}
spa_zero(b);
spa_pod_builder_init(&b, dst, n_frames);
spa_pod_builder_push_sequence(&b, &f, 0);
spa_pod_builder_control(&b, 0, SPA_CONTROL_Midi);
size = fread(bytes, 1, to_read, d->sysex.file);
spa_pod_builder_bytes(&b, bytes, size);
spa_pod_builder_pop(&b, &f);
@ -1590,6 +1623,7 @@ static int setup_sysex(struct data *data)
data->fill = sysex_play;
data->stride = 1;
data->sysex.first = true;
return 0;
}