jack: Add UMP to MIDI conversions

Add 2 new port format extensions, one for OSC and another for UMP.

Make sure we convert the events from UMP/OSC/MIDI to jack events
depending on the port type.

Try to produce UMP by default.
This commit is contained in:
Wim Taymans 2024-07-26 15:07:41 +02:00
parent 7b03a41e3d
commit 648ba982d4
2 changed files with 180 additions and 46 deletions

View file

@ -25,6 +25,13 @@ int jack_get_video_image_size(jack_client_t *client, jack_image_size_t *size);
int jack_set_sample_rate (jack_client_t *client, jack_nframes_t nframes);
/* raw OSC message */
#define JACK_DEFAULT_OSC_TYPE "8 bit raw OSC"
/* MIDI 2.0 UMP type. This contains raw UMP data, which can have MIDI 1.0 or
* MIDI 2.0 packets. The data is an array of 32 bit ints. */
#define JACK_DEFAULT_UMP_TYPE "32 bit raw UMP"
#ifdef __cplusplus
}
#endif

View file

@ -29,6 +29,7 @@
#include <spa/utils/result.h>
#include <spa/utils/string.h>
#include <spa/utils/ringbuffer.h>
#include <spa/control/ump-utils.h>
#include <pipewire/pipewire.h>
#include <pipewire/private.h>
@ -39,8 +40,6 @@
#include "pipewire/extensions/metadata.h"
#include "pipewire-jack-extensions.h"
#define JACK_DEFAULT_VIDEO_TYPE "32 bit float RGBA video"
/* use 512KB stack per thread - the default is way too high to be feasible
* with mlockall() on many systems */
#define THREAD_STACK 524288
@ -65,9 +64,16 @@ PW_LOG_TOPIC_STATIC(jack_log_topic, "jack");
#define PW_LOG_TOPIC_DEFAULT jack_log_topic
#define TYPE_ID_AUDIO 0
#define TYPE_ID_MIDI 1
#define TYPE_ID_VIDEO 2
#define TYPE_ID_OTHER 3
#define TYPE_ID_VIDEO 1
#define TYPE_ID_MIDI 2
#define TYPE_ID_OSC 3
#define TYPE_ID_UMP 4
#define TYPE_ID_OTHER 5
#define TYPE_ID_IS_EVENT(t) ((t) >= TYPE_ID_MIDI && (t) <= TYPE_ID_UMP)
#define TYPE_ID_CAN_OSC(t) ((t) == TYPE_ID_MIDI || (t) == TYPE_ID_OSC)
#define TYPE_ID_IS_HIDDEN(t) ((t) >= TYPE_ID_OTHER)
#define TYPE_ID_IS_COMPATIBLE(a,b)(((a) == (b)) || (TYPE_ID_IS_EVENT(a) && TYPE_ID_IS_EVENT(b)))
#define SELF_CONNECT_ALLOW 0
#define SELF_CONNECT_FAIL_EXT -1
@ -638,7 +644,7 @@ static struct mix *find_port_peer(struct port *port, uint32_t peer_id)
{
struct mix *mix;
spa_list_for_each(mix, &port->mix, port_link) {
pw_log_info("%p %d %d", port, mix->peer_id, peer_id);
pw_log_trace("%p %d %d", port, mix->peer_id, peer_id);
if (mix->peer_id == peer_id)
return mix;
}
@ -1381,12 +1387,25 @@ static inline bool is_osc(jack_midi_event_t *ev)
return ev->size >= 1 && (ev->buffer[0] == '#' || ev->buffer[0] == '/');
}
static size_t convert_from_midi(void *midi, void *buffer, size_t size)
static size_t convert_from_event(void *midi, void *buffer, size_t size, uint32_t type)
{
struct spa_pod_builder b = { 0, };
uint32_t i, count;
struct spa_pod_frame f;
uint32_t event_type;
switch (type) {
case TYPE_ID_MIDI:
case TYPE_ID_OSC:
/* we handle MIDI as OSC, check below */
event_type = SPA_CONTROL_OSC;
break;
case TYPE_ID_UMP:
event_type = SPA_CONTROL_UMP;
break;
default:
return 0;
}
count = jack_midi_get_event_count(midi);
spa_pod_builder_init(&b, buffer, size);
@ -1395,14 +1414,43 @@ static size_t convert_from_midi(void *midi, void *buffer, size_t size)
for (i = 0; i < count; i++) {
jack_midi_event_t ev;
jack_midi_event_get(&ev, midi, i);
spa_pod_builder_control(&b, ev.time,
is_osc(&ev) ? SPA_CONTROL_OSC : SPA_CONTROL_Midi);
spa_pod_builder_bytes(&b, ev.buffer, ev.size);
if (type != TYPE_ID_MIDI || is_osc(&ev)) {
/* no midi port or it's OSC */
spa_pod_builder_control(&b, ev.time, event_type);
spa_pod_builder_bytes(&b, ev.buffer, ev.size);
} else {
/* midi port and it's not OSC, convert to UMP */
uint8_t *data = ev.buffer;
size_t size = ev.size;
uint64_t state = 0;
while (size > 0) {
uint32_t ump[4];
int ump_size = spa_ump_from_midi(&data, &size,
ump, sizeof(ump), 0, &state);
if (ump_size <= 0)
break;
spa_pod_builder_control(&b, ev.time, SPA_CONTROL_UMP);
spa_pod_builder_bytes(&b, ump, ump_size);
}
}
}
spa_pod_builder_pop(&b, &f);
return b.state.offset;
}
static inline int event_compare(uint8_t s1, uint8_t s2)
{
/* 11 (controller) > 12 (program change) >
* 8 (note off) > 9 (note on) > 10 (aftertouch) >
* 13 (channel pressure) > 14 (pitch bend) */
static int priotab[] = { 5,4,3,7,6,2,1,0 };
if ((s1 & 0xf) != (s2 & 0xf))
return 0;
return priotab[(s2>>4) & 7] - priotab[(s1>>4) & 7];
}
static inline int event_sort(struct spa_pod_control *a, struct spa_pod_control *b)
{
if (a->offset < b->offset)
@ -1414,21 +1462,20 @@ static inline int event_sort(struct spa_pod_control *a, struct spa_pod_control *
switch(a->type) {
case SPA_CONTROL_Midi:
{
/* 11 (controller) > 12 (program change) >
* 8 (note off) > 9 (note on) > 10 (aftertouch) >
* 13 (channel pressure) > 14 (pitch bend) */
static int priotab[] = { 5,4,3,7,6,2,1,0 };
uint8_t *da, *db;
if (SPA_POD_BODY_SIZE(&a->value) < 1 ||
SPA_POD_BODY_SIZE(&b->value) < 1)
uint8_t *sa = SPA_POD_BODY(&a->value), *sb = SPA_POD_BODY(&b->value);
if (SPA_POD_BODY_SIZE(&a->value) < 1 || SPA_POD_BODY_SIZE(&b->value) < 1)
return 0;
da = SPA_POD_BODY(&a->value);
db = SPA_POD_BODY(&b->value);
if ((da[0] & 0xf) != (db[0] & 0xf))
return event_compare(sa[0], sb[0]);
}
case SPA_CONTROL_UMP:
{
uint32_t *sa = SPA_POD_BODY(&a->value), *sb = SPA_POD_BODY(&b->value);
if (SPA_POD_BODY_SIZE(&a->value) < 4 || SPA_POD_BODY_SIZE(&b->value) < 4)
return 0;
return priotab[(db[0]>>4) & 7] - priotab[(da[0]>>4) & 7];
if ((sa[0] >> 28) != 2 || (sa[0] >> 28) != 4 ||
(sb[0] >> 28) != 2 || (sb[0] >> 28) != 4)
return 0;
return event_compare(sa[0] >> 16, sb[0] >> 16);
}
default:
return 0;
@ -1487,11 +1534,12 @@ static inline int midi_event_write(void *port_buffer,
return 0;
}
static void convert_to_midi(struct spa_pod_sequence **seq, uint32_t n_seq, void *midi, bool fix)
static void convert_to_event(struct spa_pod_sequence **seq, uint32_t n_seq, void *midi, bool fix, uint32_t type)
{
struct spa_pod_control *c[n_seq];
uint64_t state = 0;
uint32_t i;
int res;
int res = 0;
for (i = 0; i < n_seq; i++)
c[i] = spa_pod_control_first(&seq[i]->body);
@ -1515,16 +1563,51 @@ static void convert_to_midi(struct spa_pod_sequence **seq, uint32_t n_seq, void
switch(next->type) {
case SPA_CONTROL_OSC:
if (!TYPE_ID_CAN_OSC(type))
break;
SPA_FALLTHROUGH;
case SPA_CONTROL_Midi:
{
uint8_t *data = SPA_POD_BODY(&next->value);
size_t size = SPA_POD_BODY_SIZE(&next->value);
if ((res = midi_event_write(midi, next->offset, data, size, fix)) < 0)
if (type == TYPE_ID_UMP) {
while (size > 0) {
uint32_t ump[4];
int ump_size = spa_ump_from_midi(&data, &size, ump, sizeof(ump), 0, &state);
if (ump_size <= 0)
break;
if ((res = midi_event_write(midi, next->offset,
(uint8_t*)ump, ump_size, false)) < 0)
break;
}
} else {
res = midi_event_write(midi, next->offset, data, size, fix);
}
if (res < 0)
pw_log_warn("midi %p: can't write event: %s", midi,
spa_strerror(res));
break;
}
case SPA_CONTROL_UMP:
{
void *data = SPA_POD_BODY(&next->value);
size_t size = SPA_POD_BODY_SIZE(&next->value);
uint8_t ev[32];
if (type == TYPE_ID_MIDI) {
int ev_size = spa_ump_to_midi(data, size, ev, sizeof(ev));
if (ev_size <= 0)
break;
size = ev_size;
data = ev;
} else if (type != TYPE_ID_UMP)
break;
if ((res = midi_event_write(midi, next->offset, data, size, fix)) < 0)
pw_log_warn("midi %p: can't write event: %s", midi,
spa_strerror(res));
}
}
c[next_index] = spa_pod_control_next(c[next_index]);
}
@ -1591,19 +1674,22 @@ static inline void process_empty(struct port *p, uint32_t frames)
struct client *c = p->client;
void *ptr, *src = p->emptyptr;
struct port *tied = p->tied;
uint32_t type = p->object->port.type_id;
if (SPA_UNLIKELY(tied != NULL)) {
if ((src = tied->get_buffer(tied, frames)) == NULL)
src = p->emptyptr;
}
switch (p->object->port.type_id) {
switch (type) {
case TYPE_ID_AUDIO:
ptr = get_buffer_output(p, frames, sizeof(float), NULL);
if (SPA_LIKELY(ptr != NULL))
memcpy(ptr, src, frames * sizeof(float));
break;
case TYPE_ID_MIDI:
case TYPE_ID_OSC:
case TYPE_ID_UMP:
{
struct buffer *b;
ptr = get_buffer_output(p, c->max_frames, 1, &b);
@ -1611,8 +1697,8 @@ static inline void process_empty(struct port *p, uint32_t frames)
/* first build the complete pod in scratch memory, then copy it
* to the target buffer. This makes it possible for multiple threads
* to do this concurrently */
b->datas[0].chunk->size = convert_from_midi(src,
midi_scratch, MIDI_SCRATCH_FRAMES * sizeof(float));
b->datas[0].chunk->size = convert_from_event(src, midi_scratch,
MIDI_SCRATCH_FRAMES * sizeof(float), type);
memcpy(ptr, midi_scratch, b->datas[0].chunk->size);
}
break;
@ -2393,6 +2479,8 @@ static int param_enum_format(struct client *c, struct port *p,
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp),
SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_DSP_F32));
break;
case TYPE_ID_UMP:
case TYPE_ID_OSC:
case TYPE_ID_MIDI:
*param = spa_pod_builder_add_object(b,
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
@ -2424,6 +2512,8 @@ static int param_format(struct client *c, struct port *p,
SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_DSP_F32));
break;
case TYPE_ID_MIDI:
case TYPE_ID_OSC:
case TYPE_ID_UMP:
*param = spa_pod_builder_add_object(b,
SPA_TYPE_OBJECT_Format, SPA_PARAM_Format,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application),
@ -2448,6 +2538,8 @@ static int param_buffers(struct client *c, struct port *p,
switch (p->object->port.type_id) {
case TYPE_ID_AUDIO:
case TYPE_ID_MIDI:
case TYPE_ID_OSC:
case TYPE_ID_UMP:
*param = spa_pod_builder_add_object(b,
SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, MAX_BUFFERS),
@ -2781,7 +2873,7 @@ static inline void *init_buffer(struct port *p, uint32_t nframes)
if (p->zeroed)
return data;
if (p->object->port.type_id == TYPE_ID_MIDI) {
if (TYPE_ID_IS_EVENT(p->object->port.type_id)) {
struct midi_buffer *mb = data;
midi_init_buffer(data, c->max_frames, nframes);
pw_log_debug("port %p: init midi buffer size:%d frames:%d", p,
@ -3266,10 +3358,14 @@ static jack_port_type_id_t string_to_type(const char *port_type)
{
if (spa_streq(JACK_DEFAULT_AUDIO_TYPE, port_type))
return TYPE_ID_AUDIO;
else if (spa_streq(JACK_DEFAULT_MIDI_TYPE, port_type))
return TYPE_ID_MIDI;
else if (spa_streq(JACK_DEFAULT_VIDEO_TYPE, port_type))
return TYPE_ID_VIDEO;
else if (spa_streq(JACK_DEFAULT_MIDI_TYPE, port_type))
return TYPE_ID_MIDI;
else if (spa_streq(JACK_DEFAULT_OSC_TYPE, port_type))
return TYPE_ID_OSC;
else if (spa_streq(JACK_DEFAULT_UMP_TYPE, port_type))
return TYPE_ID_UMP;
else if (spa_streq("other", port_type))
return TYPE_ID_OTHER;
else
@ -3281,22 +3377,46 @@ static const char* type_to_string(jack_port_type_id_t type_id)
switch(type_id) {
case TYPE_ID_AUDIO:
return JACK_DEFAULT_AUDIO_TYPE;
case TYPE_ID_MIDI:
return JACK_DEFAULT_MIDI_TYPE;
case TYPE_ID_VIDEO:
return JACK_DEFAULT_VIDEO_TYPE;
case TYPE_ID_MIDI:
case TYPE_ID_OSC:
case TYPE_ID_UMP:
/* all returned as MIDI */
return JACK_DEFAULT_MIDI_TYPE;
case TYPE_ID_OTHER:
return "other";
default:
return NULL;
}
}
static const char* type_to_format_dsp(jack_port_type_id_t type_id)
{
switch(type_id) {
case TYPE_ID_AUDIO:
return JACK_DEFAULT_AUDIO_TYPE;
case TYPE_ID_VIDEO:
return JACK_DEFAULT_VIDEO_TYPE;
case TYPE_ID_OSC:
return JACK_DEFAULT_OSC_TYPE;
case TYPE_ID_MIDI:
case TYPE_ID_UMP:
/* all exposed to PipeWire as UMP */
return JACK_DEFAULT_UMP_TYPE;
default:
return NULL;
}
}
static bool type_is_dsp(jack_port_type_id_t type_id)
{
switch(type_id) {
case TYPE_ID_AUDIO:
case TYPE_ID_MIDI:
case TYPE_ID_VIDEO:
case TYPE_ID_OSC:
case TYPE_ID_UMP:
return true;
default:
return false;
@ -3686,7 +3806,7 @@ static void registry_event_global(void *data, uint32_t id,
}
if (is_monitor && !c->show_monitor)
goto exit;
if (type_id == TYPE_ID_MIDI && !c->show_midi)
if (TYPE_ID_IS_EVENT(type_id) && !c->show_midi)
goto exit;
o = NULL;
@ -3747,6 +3867,8 @@ static void registry_event_global(void *data, uint32_t id,
(int)(sizeof(tmp)-11), tmp, serial);
else
snprintf(o->port.name, sizeof(o->port.name), "%s", tmp);
o->port.type_id = type_id;
}
if (c->fill_aliases) {
@ -3766,7 +3888,6 @@ static void registry_event_global(void *data, uint32_t id,
}
o->port.flags = flags;
o->port.type_id = type_id;
o->port.node_id = node_id;
o->port.is_monitor = is_monitor;
@ -5094,7 +5215,7 @@ jack_nframes_t jack_get_sample_rate (jack_client_t *client)
}
}
c->sample_rate = res;
pw_log_debug("sample_rate: %u", res);
pw_log_trace_fp("sample_rate: %u", res);
return res;
}
@ -5225,6 +5346,8 @@ jack_port_t * jack_port_register (jack_client_t *client,
p->get_buffer = get_buffer_input_float;
break;
case TYPE_ID_MIDI:
case TYPE_ID_OSC:
case TYPE_ID_UMP:
p->get_buffer = get_buffer_input_midi;
break;
default:
@ -5238,6 +5361,8 @@ jack_port_t * jack_port_register (jack_client_t *client,
p->get_buffer = get_buffer_output_float;
break;
case TYPE_ID_MIDI:
case TYPE_ID_OSC:
case TYPE_ID_UMP:
p->get_buffer = get_buffer_output_midi;
break;
default:
@ -5250,7 +5375,7 @@ jack_port_t * jack_port_register (jack_client_t *client,
spa_list_init(&p->mix);
pw_properties_set(p->props, PW_KEY_FORMAT_DSP, port_type);
pw_properties_set(p->props, PW_KEY_FORMAT_DSP, type_to_format_dsp(type_id));
pw_properties_set(p->props, PW_KEY_PORT_NAME, port_name);
if (flags > 0x1f) {
pw_properties_setf(p->props, PW_KEY_PORT_EXTRA,
@ -5493,15 +5618,13 @@ static void *get_buffer_input_midi(struct port *p, jack_nframes_t frames)
/* first convert to a thread local scratch buffer, then memcpy into
* the per port buffer. This makes it possible to call this function concurrently
* but also have different pointers per port */
convert_to_midi(seq, n_seq, mb, p->client->fix_midi_events);
convert_to_event(seq, n_seq, mb, p->client->fix_midi_events, p->object->port.type_id);
memcpy(ptr, mb, sizeof(struct midi_buffer) + (mb->event_count
* sizeof(struct midi_event)));
if (mb->write_pos) {
size_t offs = mb->buffer_size - 1 - mb->write_pos;
memcpy(SPA_PTROFF(ptr, offs, void), SPA_PTROFF(mb, offs, void), mb->write_pos);
}
if (mb->event_count > 0)
spa_debug_mem(0, ptr, 64);
return ptr;
}
@ -5561,7 +5684,7 @@ void * jack_port_get_buffer (jack_port_t *port, jack_nframes_t frames)
if ((b = get_mix_buffer(c, mix, frames)) == NULL)
goto done;
if (o->port.type_id == TYPE_ID_MIDI) {
if (TYPE_ID_IS_EVENT(o->port.type_id)) {
struct spa_pod_sequence *seq[1];
struct spa_data *d;
void *pod;
@ -5576,7 +5699,7 @@ void * jack_port_get_buffer (jack_port_t *port, jack_nframes_t frames)
if (!spa_pod_is_sequence(pod))
goto done;
seq[0] = pod;
convert_to_midi(seq, 1, ptr, c->fix_midi_events);
convert_to_event(seq, 1, ptr, c->fix_midi_events, o->port.type_id);
} else {
ptr = get_buffer_data(b, frames);
}
@ -5584,7 +5707,7 @@ void * jack_port_get_buffer (jack_port_t *port, jack_nframes_t frames)
ptr = p->get_buffer(p, frames);
}
done:
pw_log_warn("%p: port:%p buffer:%p frames:%d", c, p, ptr, frames);
pw_log_trace_fp("%p: port:%p buffer:%p frames:%d", c, p, ptr, frames);
return ptr;
}
@ -6140,7 +6263,7 @@ int jack_connect (jack_client_t *client,
if (src == NULL || dst == NULL ||
!(src->port.flags & JackPortIsOutput) ||
!(dst->port.flags & JackPortIsInput) ||
src->port.type_id != dst->port.type_id) {
!TYPE_ID_IS_COMPATIBLE(src->port.type_id, dst->port.type_id)) {
res = -EINVAL;
goto exit;
}
@ -6297,6 +6420,10 @@ size_t jack_port_type_get_buffer_size (jack_client_t *client, const char *port_t
return jack_get_buffer_size(client) * sizeof(float);
else if (spa_streq(JACK_DEFAULT_MIDI_TYPE, port_type))
return c->max_frames * sizeof(float);
else if (spa_streq(JACK_DEFAULT_OSC_TYPE, port_type))
return c->max_frames * sizeof(float);
else if (spa_streq(JACK_DEFAULT_UMP_TYPE, port_type))
return c->max_frames * sizeof(float);
else if (spa_streq(JACK_DEFAULT_VIDEO_TYPE, port_type))
return 320 * 240 * 4 * sizeof(float);
else
@ -6559,7 +6686,7 @@ const char ** jack_get_ports (jack_client_t *client,
continue;
pw_log_debug("%p: check port type:%d flags:%08lx name:\"%s\"", c,
o->port.type_id, o->port.flags, o->port.name);
if (o->port.type_id > TYPE_ID_VIDEO)
if (TYPE_ID_IS_HIDDEN(o->port.type_id))
continue;
if (!SPA_FLAG_IS_SET(o->port.flags, flags))
continue;