diff --git a/pipewire-jack/src/pipewire-jack-extensions.h b/pipewire-jack/src/pipewire-jack-extensions.h index 0caec40c9..dc3b9f94b 100644 --- a/pipewire-jack/src/pipewire-jack-extensions.h +++ b/pipewire-jack/src/pipewire-jack-extensions.h @@ -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 diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index 6f2b897b0..9537b8e3b 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -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;