diff --git a/doc/dox/internals/midi.dox b/doc/dox/internals/midi.dox index e89b24578..819652bd6 100644 --- a/doc/dox/internals/midi.dox +++ b/doc/dox/internals/midi.dox @@ -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 diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index 17e6a9929..2a575979a 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -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; }