mixer: handle control.ump property

Add a control.ump port property. When true, the port wants UMP and the
mixer will convert to it. When false, the port supports both UMP and
Midi1 and no conversions will happen. When unset, the mixer will always
convert UMP to midi1.

Remove the CONTROL_types property from the filter. This causes problems
because this is the format negotiated with peers, which might not
support the types but can still be linked because the mixer will
convert.

The control.ump port property is supposed to be a temporary fix until we
can negotiate the mixer ports properly with the CONTROL_types.

Remove UMP handling from bluetooth midi, just use the raw Midi1 events
now that the mixer will give those and we are supposed to output our
unconverted format.

Fix midi events in-place in netjack because we can.

Update docs and pw-mididump to note that we are back to midi1 as the
default format.

With this, most of the midi<->UMP conversion should be gone again and we
should be able to avoid conversion problems in ALSA and PipeWire.

Fixes #5183
This commit is contained in:
Wim Taymans 2026-03-25 11:47:01 +01:00
parent 7fe191bc10
commit 9eeb2f1930
9 changed files with 88 additions and 105 deletions

View file

@ -641,7 +641,6 @@ node_port_enum_params(struct impl *impl, int seq,
if (count == num)
break;
}
return 0;
}

View file

@ -235,16 +235,13 @@ struct data_info {
bool filled;
};
static inline bool fix_midi_event(const uint8_t *data, size_t size, uint8_t tmp[3])
static inline void fix_midi_event(uint8_t *data, size_t size)
{
/* fixup NoteOn with vel 0 */
if (size > 2 && (data[0] & 0xF0) == 0x90 && data[2] == 0x00) {
tmp[0] = 0x80 + (data[0] & 0x0F);
tmp[1] = data[1];
tmp[2] = 0x40;
return true;
data[0] = 0x80 + (data[0] & 0x0F);
data[2] = 0x40;
}
return false;
}
static inline void *n2j_midi_buffer_reserve(struct nj2_midi_buffer *buf,
@ -276,39 +273,18 @@ static inline void *n2j_midi_buffer_reserve(struct nj2_midi_buffer *buf,
}
static inline void n2j_midi_buffer_write(struct nj2_midi_buffer *buf,
uint32_t offset, const void *data, uint32_t size)
uint32_t offset, const void *data, uint32_t size, bool fix)
{
void *ptr = n2j_midi_buffer_reserve(buf, offset, size);
if (ptr != NULL)
uint8_t *ptr = n2j_midi_buffer_reserve(buf, offset, size);
if (ptr != NULL) {
memcpy(ptr, data, size);
if (fix)
fix_midi_event(ptr, size);
}
else
buf->lost_events++;
}
static inline void n2j_midi_buffer_append(struct nj2_midi_buffer *buf,
void *data, uint32_t size)
{
struct nj2_midi_event *ev;
uint32_t old_size;
uint8_t *old_ptr, *new_ptr;
ev = &buf->event[--buf->event_count];
old_size = ev->size;
if (old_size <= MIDI_INLINE_MAX) {
old_ptr = ev->buffer;
} else {
buf->write_pos -= old_size;
old_ptr = SPA_PTROFF(buf, ev->offset, void);
}
new_ptr = n2j_midi_buffer_reserve(buf, ev->time, old_size + size);
if (new_ptr == NULL) {
buf->lost_events++;
} else {
memmove(new_ptr, old_ptr, old_size);
memcpy(new_ptr+old_size, data, size);
}
}
static void midi_to_netjack2(struct netjack2_peer *peer,
struct nj2_midi_buffer *buf, float *src, uint32_t n_samples)
{
@ -336,7 +312,6 @@ static void midi_to_netjack2(struct netjack2_peer *peer,
while (spa_pod_parser_get_control_body(&parser, &c, &c_body) >= 0) {
uint32_t size = c.value.size;
const uint8_t *data = c_body;
uint8_t tmp[3];
if (c.type != SPA_CONTROL_Midi)
continue;
@ -345,11 +320,7 @@ static void midi_to_netjack2(struct netjack2_peer *peer,
buf->lost_events++;
continue;
}
if (peer->fix_midi && fix_midi_event(data, size, tmp)) {
data = tmp;
size = 3;
}
n2j_midi_buffer_write(buf, c.offset, data, size);
n2j_midi_buffer_write(buf, c.offset, data, size, peer->fix_midi);
}
if (buf->write_pos > 0)
memmove(SPA_PTROFF(buf, sizeof(*buf) + buf->event_count * sizeof(struct nj2_midi_event), void),

View file

@ -1793,11 +1793,13 @@ static void add_control_dsp_port_params(struct filter *impl, struct port *port,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control),
0);
#if 0
if (types != 0) {
spa_pod_builder_add(&b,
SPA_FORMAT_CONTROL_types, SPA_POD_CHOICE_FLAGS_Int(types),
0);
}
#endif
add_param(impl, port, SPA_PARAM_EnumFormat, PARAM_FLAG_LOCKED,
spa_pod_builder_pop(&b, &f[0]));
}
@ -1857,10 +1859,13 @@ void *pw_filter_add_port(struct pw_filter *filter,
add_video_dsp_port_params(impl, p);
else if (spa_streq(str, "8 bit raw midi"))
add_control_dsp_port_params(impl, p, 1u << SPA_CONTROL_Midi);
else if (spa_streq(str, "8 bit raw control"))
else if (spa_streq(str, "8 bit raw control")) {
add_control_dsp_port_params(impl, p, 0);
pw_properties_set(props, "control.ump", "false");
}
else if (spa_streq(str, "32 bit raw UMP")) {
add_control_dsp_port_params(impl, p, 1u << SPA_CONTROL_UMP);
pw_properties_set(props, "control.ump", "true");
pw_properties_set(props, PW_KEY_FORMAT_DSP, "8 bit raw midi");
}
}

View file

@ -1094,11 +1094,11 @@ int pw_impl_port_set_mix(struct pw_impl_port *port, struct spa_node *node, uint3
static int setup_mixer(struct pw_impl_port *port, const struct spa_pod *param)
{
uint32_t media_type, media_subtype;
uint32_t media_type, media_subtype, n_items;
int res;
const char *fallback_lib, *factory_name;
const char *fallback_lib, *factory_name, *str;
struct spa_handle *handle;
struct spa_dict_item items[3];
struct spa_dict_item items[4];
char quantum_limit[16];
void *iface;
struct pw_context *context = port->node->context;
@ -1150,14 +1150,17 @@ static int setup_mixer(struct pw_impl_port *port, const struct spa_pod *param)
return -ENOTSUP;
}
items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_LIBRARY_NAME, fallback_lib);
n_items = 0;
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_LIBRARY_NAME, fallback_lib);
spa_scnprintf(quantum_limit, sizeof(quantum_limit), "%u",
context->settings.clock_quantum_limit);
items[1] = SPA_DICT_ITEM_INIT("clock.quantum-limit", quantum_limit);
items[2] = SPA_DICT_ITEM_INIT(PW_KEY_NODE_LOOP_NAME, port->node->data_loop->name);
items[n_items++] = SPA_DICT_ITEM_INIT("clock.quantum-limit", quantum_limit);
items[n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_NODE_LOOP_NAME, port->node->data_loop->name);
if ((str = pw_properties_get(port->properties, "control.ump")) != NULL)
items[n_items++] = SPA_DICT_ITEM_INIT("control.ump", str);
handle = pw_context_load_spa_handle(context, factory_name,
&SPA_DICT_INIT_ARRAY(items));
&SPA_DICT_INIT(items, n_items));
if (handle == NULL)
return -errno;

View file

@ -33,7 +33,7 @@ struct data {
struct pw_filter *filter;
struct port *in_port;
int64_t clock_time;
bool opt_midi1;
bool force_ump;
};
@ -174,7 +174,8 @@ static int dump_filter(struct data *data)
PW_FILTER_PORT_FLAG_MAP_BUFFERS,
sizeof(struct port),
pw_properties_new(
PW_KEY_FORMAT_DSP, data->opt_midi1 ? "8 bit raw midi" : "32 bit raw UMP",
PW_KEY_FORMAT_DSP,
data->force_ump ? "32 bit raw UMP" : "8 bit raw midi",
PW_KEY_PORT_NAME, "input",
NULL),
NULL, 0);
@ -198,7 +199,7 @@ static void show_help(const char *name, bool error)
" -h, --help Show this help\n"
" --version Show version\n"
" -r, --remote Remote daemon name\n"
" -M, --force-midi Force midi format, one of \"midi\" or \"ump\",(default ump)\n",
" -M, --force-midi Force midi format, one of \"midi\" or \"ump\",(default midi)\n",
name);
}
@ -238,9 +239,9 @@ int main(int argc, char *argv[])
case 'M':
if (spa_streq(optarg, "midi"))
data.opt_midi1 = true;
data.force_ump = false;
else if (spa_streq(optarg, "ump"))
data.opt_midi1 = false;
data.force_ump = true;
else {
fprintf(stderr, "error: bad force-midi %s\n", optarg);
show_help(argv[0], true);