Merge branch 'master' into 'master'

Draft: filter-graph: add support for controlling LV2 plugins via atom control port

See merge request pipewire/pipewire!2497
This commit is contained in:
Nick 2025-10-17 14:13:44 +00:00
commit 71ce914e2e

View file

@ -18,22 +18,26 @@
# if __has_include (<lv2/atom/atom.h>)
#include <lv2/atom/atom.h>
#include <lv2/atom/forge.h>
#include <lv2/buf-size/buf-size.h>
#include <lv2/worker/worker.h>
#include <lv2/state/state.h>
#include <lv2/options/options.h>
#include <lv2/parameters/parameters.h>
#include <lv2/log/log.h>
#include <lv2/patch/patch.h>
# else
#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
#include <lv2/lv2plug.in/ns/ext/atom/forge.h>
#include <lv2/lv2plug.in/ns/ext/buf-size/buf-size.h>
#include <lv2/lv2plug.in/ns/ext/worker/worker.h>
#include <lv2/lv2plug.in/ns/ext/state/state.h>
#include <lv2/lv2plug.in/ns/ext/options/options.h>
#include <lv2/lv2plug.in/ns/ext/parameters/parameters.h>
#include <lv2/lv2plug.in/ns/ext/log/log.h>
#include <lv2/lv2plug.in/ns/ext/patch/patch.h>
# endif
@ -97,16 +101,23 @@ struct context {
LilvNode *lv2_OutputPort;
LilvNode *lv2_AudioPort;
LilvNode *lv2_ControlPort;
LilvNode *lv2_Optional;
LilvNode *lv2_control;
LilvNode *lv2_minimum;
LilvNode *lv2_maximum;
LilvNode *lv2_default;
LilvNode *lv2_connectionOptional;
LilvNode *atom_AtomPort;
LilvNode *atom_Sequence;
LilvNode *urid_map;
LilvNode *powerOf2BlockLength;
LilvNode *fixedBlockLength;
LilvNode *boundedBlockLength;
LilvNode* worker_schedule;
LilvNode* worker_iface;
LilvNode* state_iface;
LilvNode *patch_Set;
LilvNode *patch_property;
LilvNode *patch_value;
LilvNode *buf_size_powerOf2BlockLength;
LilvNode *buf_size_fixedBlockLength;
LilvNode *buf_size_boundedBlockLength;
LilvNode *worker_schedule;
LilvNode *worker_interface;
LilvNode *state_interface;
URITable uri_table;
LV2_URID_Map map;
@ -114,8 +125,14 @@ struct context {
LV2_URID_Unmap unmap;
LV2_Feature unmap_feature;
LV2_URID atom_Int;
LV2_URID atom_Float;
LV2_Atom_Forge forge;
LV2_URID atom_Int_ID;
LV2_URID atom_Float_ID;
LV2_URID atom_Sequence_ID;
LV2_URID patch_Set_ID;
LV2_URID patch_property_ID;
LV2_URID patch_value_ID;
};
#define context_map(c,uri) ((c)->map.map((c)->map.handle,(uri)))
@ -124,13 +141,14 @@ static void context_free(struct context *c)
{
if (c->world) {
lilv_node_free(c->worker_schedule);
lilv_node_free(c->powerOf2BlockLength);
lilv_node_free(c->fixedBlockLength);
lilv_node_free(c->boundedBlockLength);
lilv_node_free(c->buf_size_powerOf2BlockLength);
lilv_node_free(c->buf_size_fixedBlockLength);
lilv_node_free(c->buf_size_boundedBlockLength);
lilv_node_free(c->urid_map);
lilv_node_free(c->atom_Sequence);
lilv_node_free(c->atom_AtomPort);
lilv_node_free(c->lv2_Optional);
lilv_node_free(c->lv2_connectionOptional);
lilv_node_free(c->lv2_control);
lilv_node_free(c->lv2_ControlPort);
lilv_node_free(c->lv2_AudioPort);
lilv_node_free(c->lv2_OutputPort);
@ -166,16 +184,23 @@ static struct context *context_new(void)
c->lv2_OutputPort = lilv_new_uri(c->world, LV2_CORE__OutputPort);
c->lv2_AudioPort = lilv_new_uri(c->world, LV2_CORE__AudioPort);
c->lv2_ControlPort = lilv_new_uri(c->world, LV2_CORE__ControlPort);
c->lv2_Optional = lilv_new_uri(c->world, LV2_CORE__connectionOptional);
c->lv2_control = lilv_new_uri(c->world, LV2_CORE__control);
c->lv2_minimum = lilv_new_uri(c->world, LV2_CORE__minimum);
c->lv2_maximum = lilv_new_uri(c->world, LV2_CORE__maximum);
c->lv2_default = lilv_new_uri(c->world, LV2_CORE__default);
c->lv2_connectionOptional = lilv_new_uri(c->world, LV2_CORE__connectionOptional);
c->atom_AtomPort = lilv_new_uri(c->world, LV2_ATOM__AtomPort);
c->atom_Sequence = lilv_new_uri(c->world, LV2_ATOM__Sequence);
c->urid_map = lilv_new_uri(c->world, LV2_URID__map);
c->powerOf2BlockLength = lilv_new_uri(c->world, LV2_BUF_SIZE__powerOf2BlockLength);
c->fixedBlockLength = lilv_new_uri(c->world, LV2_BUF_SIZE__fixedBlockLength);
c->boundedBlockLength = lilv_new_uri(c->world, LV2_BUF_SIZE__boundedBlockLength);
c->patch_Set = lilv_new_uri(c->world, LV2_PATCH__Set);
c->patch_property = lilv_new_uri(c->world, LV2_PATCH__property);
c->patch_value = lilv_new_uri(c->world, LV2_PATCH__value);
c->buf_size_powerOf2BlockLength = lilv_new_uri(c->world, LV2_BUF_SIZE__powerOf2BlockLength);
c->buf_size_fixedBlockLength = lilv_new_uri(c->world, LV2_BUF_SIZE__fixedBlockLength);
c->buf_size_boundedBlockLength = lilv_new_uri(c->world, LV2_BUF_SIZE__boundedBlockLength);
c->worker_schedule = lilv_new_uri(c->world, LV2_WORKER__schedule);
c->worker_iface = lilv_new_uri(c->world, LV2_WORKER__interface);
c->state_iface = lilv_new_uri(c->world, LV2_STATE__interface);
c->worker_interface = lilv_new_uri(c->world, LV2_WORKER__interface);
c->state_interface = lilv_new_uri(c->world, LV2_STATE__interface);
c->map.handle = &c->uri_table;
c->map.map = uri_table_map;
@ -186,8 +211,14 @@ static struct context *context_new(void)
c->unmap_feature.URI = LV2_URID__unmap;
c->unmap_feature.data = &c->unmap;
c->atom_Int = context_map(c, LV2_ATOM__Int);
c->atom_Float = context_map(c, LV2_ATOM__Float);
lv2_atom_forge_init(&c->forge, &c->map);
c->atom_Int_ID = context_map(c, LV2_ATOM__Int);
c->atom_Float_ID = context_map(c, LV2_ATOM__Float);
c->atom_Sequence_ID = context_map(c, LV2_ATOM__Sequence);
c->patch_Set_ID = context_map(c, LV2_PATCH__Set);
c->patch_property_ID = context_map(c, LV2_PATCH__property);
c->patch_value_ID = context_map(c, LV2_PATCH__value);
return c;
error:
@ -229,6 +260,9 @@ struct plugin {
struct descriptor {
struct spa_fga_descriptor desc;
struct plugin *p;
uint32_t n_regular_ports;
uint32_t n_property_ports;
};
struct instance {
@ -250,6 +284,14 @@ struct instance {
int32_t block_length;
LV2_Atom empty_atom;
struct control_port_endpoint *control_port_endpoint;
};
struct control_port_endpoint {
float **new_values;
float *old_values;
LV2_Atom_Sequence seq;
};
static int
@ -370,6 +412,7 @@ static void *lv2_instantiate(const struct spa_fga_plugin *plugin, const struct s
static const int32_t max_block_length = 8192;
static const int32_t seq_size = 32768;
float fsample_rate = SampleRate;
const LilvPort *control_port = lilv_plugin_get_port_by_designation(p->p, c->lv2_InputPort, c->lv2_control);
i = calloc(1, sizeof(*i));
if (i == NULL)
@ -399,19 +442,19 @@ static void *lv2_instantiate(const struct spa_fga_plugin *plugin, const struct s
i->options[0] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0,
context_map(c, LV2_BUF_SIZE__minBlockLength), sizeof(int32_t),
c->atom_Int, &min_block_length };
c->atom_Int_ID, &min_block_length };
i->options[1] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0,
context_map(c, LV2_BUF_SIZE__maxBlockLength), sizeof(int32_t),
c->atom_Int, &max_block_length };
c->atom_Int_ID, &max_block_length };
i->options[2] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0,
context_map(c, LV2_BUF_SIZE__sequenceSize), sizeof(int32_t),
c->atom_Int, &seq_size };
c->atom_Int_ID, &seq_size };
i->options[3] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0,
context_map(c, "http://lv2plug.in/ns/ext/buf-size#nominalBlockLength"), sizeof(int32_t),
c->atom_Int, &i->block_length },
c->atom_Int_ID, &i->block_length };
i->options[4] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0,
context_map(c, LV2_PARAMETERS__sampleRate), sizeof(float),
c->atom_Float, &fsample_rate };
c->atom_Float_ID, &fsample_rate };
i->options[5] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0, 0, 0, 0, NULL };
i->options_feature.URI = LV2_OPTIONS__options;
@ -425,15 +468,33 @@ static void *lv2_instantiate(const struct spa_fga_plugin *plugin, const struct s
free(i);
return NULL;
}
if (lilv_plugin_has_extension_data(p->p, c->worker_iface)) {
if (lilv_plugin_has_extension_data(p->p, c->worker_interface)) {
i->work_iface = (const LV2_Worker_Interface*)
lilv_instance_get_extension_data(i->instance, LV2_WORKER__interface);
}
if (lilv_plugin_has_extension_data(p->p, c->state_iface)) {
if (lilv_plugin_has_extension_data(p->p, c->state_interface)) {
i->state_iface = (const LV2_State_Interface*)
lilv_instance_get_extension_data(i->instance, LV2_STATE__interface);
}
if (control_port != NULL) {
i->control_port_endpoint = calloc(1, sizeof(struct control_port_endpoint) + seq_size);
i->control_port_endpoint->new_values = calloc(d->n_property_ports, sizeof(float*));
i->control_port_endpoint->old_values = calloc(d->n_property_ports, sizeof(float));
i->control_port_endpoint->seq = (LV2_Atom_Sequence) {
.atom = (LV2_Atom) {
.size = sizeof(LV2_Atom_Sequence_Body),
.type = c->atom_Sequence_ID,
},
};
lilv_instance_connect_port(i->instance, lilv_port_get_index(p->p, control_port), &i->control_port_endpoint->seq);
}
for (n = 0; n < desc->n_ports; n++) {
if (n >= lilv_plugin_get_num_ports(p->p)) {
lilv_instance_connect_port(i->instance, lilv_port_get_index(p->p, control_port), &i->control_port_endpoint->seq);
continue;
}
const LilvPort *port = lilv_plugin_get_port_by_index(p->p, n);
if (lilv_port_is_a(p->p, port, c->atom_AtomPort)) {
lilv_instance_connect_port(i->instance, n, &i->empty_atom);
@ -454,13 +515,75 @@ static void lv2_cleanup(void *instance)
spa_loop_invoke(i->p->data_loop, NULL, 0, NULL, 0, true, NULL);
spa_loop_invoke(i->p->main_loop, NULL, 0, NULL, 0, true, NULL);
lilv_instance_free(i->instance);
if (i->control_port_endpoint != NULL) {
free(i->control_port_endpoint->old_values);
free(i->control_port_endpoint->new_values);
free(i->control_port_endpoint);
}
free(i);
}
static void lv2_connect_port(void *instance, unsigned long port, void *data)
{
struct instance *i = instance;
struct descriptor *d = i->desc;
struct plugin *p = i->p;
lilv_instance_connect_port(i->instance, port, data);
if (port >= d->n_regular_ports) {
const uint32_t value_buffer_index = port - d->n_regular_ports;
spa_log_info(p->log, "connected atom port at value buffer index %d", value_buffer_index);
i->control_port_endpoint->new_values[value_buffer_index] = data;
lilv_instance_connect_port(i->instance, port, &i->control_port_endpoint->seq);
}
}
static void lv2_control_changed(void *instance)
{
struct instance *i = instance;
struct descriptor *d = i->desc;
struct plugin *p = i->p;
struct context *c = p->c;
for (uint32_t prop_port_index = d->n_regular_ports; prop_port_index < d->desc.n_ports; prop_port_index++) {
const char *plugin_name = lilv_node_as_string(lilv_plugin_get_uri(p->p));
const char *delimiter = ":";
const char *property_name = d->desc.ports[prop_port_index].name;
char *full_property_name = alloca(strlen(plugin_name) + strlen(delimiter) + strlen(property_name) + 1);
const uint32_t value_buffer_index = prop_port_index - d->n_regular_ports;
strcpy(full_property_name, plugin_name);
strcat(full_property_name, delimiter);
strcat(full_property_name, property_name);
LV2_URID property_id = c->map.map(c->map.handle, full_property_name);
if (*i->control_port_endpoint->new_values[value_buffer_index]
!= i->control_port_endpoint->old_values[value_buffer_index]) {
spa_log_info(p->log, "atom control changed: %f -> %f at value buffer index %d for property %s with URID %d",
i->control_port_endpoint->old_values[value_buffer_index],
*i->control_port_endpoint->new_values[value_buffer_index],
value_buffer_index, full_property_name, property_id);
uint8_t *buffer = alloca(1024);
LV2_Atom_Forge_Frame frame;
lv2_atom_forge_set_buffer(&c->forge, buffer, 1024);
lv2_atom_forge_object(&c->forge, &frame, 0, c->patch_Set_ID);
lv2_atom_forge_key(&c->forge, c->patch_property_ID);
lv2_atom_forge_urid(&c->forge, property_id);
lv2_atom_forge_key(&c->forge, c->patch_value_ID);
lv2_atom_forge_atom(&c->forge, sizeof(float), c->atom_Float_ID);
lv2_atom_forge_write(&c->forge, i->control_port_endpoint->new_values[value_buffer_index], sizeof(float));
const LV2_Atom *atom = lv2_atom_forge_deref(&c->forge, frame.ref);
LV2_Atom_Event *event = alloca(sizeof(LV2_Atom_Event) + lv2_atom_total_size(atom));
event->time.frames = 0;
memcpy(&event->body, atom, lv2_atom_total_size(atom));
lv2_atom_sequence_append_event(&i->control_port_endpoint->seq, 32768, event);
i->control_port_endpoint->old_values[value_buffer_index] = *i->control_port_endpoint->new_values[value_buffer_index];
}
}
}
static void lv2_activate(void *instance)
@ -481,6 +604,8 @@ static void lv2_run(void *instance, unsigned long SampleCount)
lilv_instance_run(i->instance, SampleCount);
if (i->work_iface != NULL && i->work_iface->end_run != NULL)
i->work_iface->end_run(i->instance);
if (i->control_port_endpoint != NULL)
lv2_atom_sequence_clear(&i->control_port_endpoint->seq);
}
static void lv2_free(const struct spa_fga_descriptor *desc)
@ -512,6 +637,7 @@ static const struct spa_fga_descriptor *lv2_plugin_make_desc(void *plugin, const
desc->desc.instantiate = lv2_instantiate;
desc->desc.cleanup = lv2_cleanup;
desc->desc.connect_port = lv2_connect_port;
desc->desc.control_changed = lv2_control_changed;
desc->desc.activate = lv2_activate;
desc->desc.deactivate = lv2_deactivate;
desc->desc.run = lv2_run;
@ -521,7 +647,13 @@ static const struct spa_fga_descriptor *lv2_plugin_make_desc(void *plugin, const
desc->desc.name = strdup(name);
desc->desc.flags = 0;
desc->desc.n_ports = lilv_plugin_get_num_ports(p->p);
desc->n_regular_ports = lilv_plugin_get_num_ports(p->p);
desc->n_property_ports = lilv_nodes_size(
lilv_world_find_nodes(c->world,
lilv_plugin_get_uri(p->p),
lilv_new_uri(c->world, LV2_PATCH__writable),
NULL));
desc->desc.n_ports = desc->n_regular_ports + desc->n_property_ports;
desc->desc.ports = calloc(desc->desc.n_ports, sizeof(struct spa_fga_port));
mins = alloca(desc->desc.n_ports * sizeof(float));
@ -533,7 +665,7 @@ static const struct spa_fga_descriptor *lv2_plugin_make_desc(void *plugin, const
lilv_plugin_get_port_ranges_float(p->p, mins, maxes, controls);
for (i = 0; i < desc->desc.n_ports; i++) {
for (i = 0; i < desc->n_regular_ports; i++) {
const LilvPort *port = lilv_plugin_get_port_by_index(p->p, i);
const LilvNode *symbol = lilv_port_get_symbol(p->p, port);
struct spa_fga_port *fp = &desc->desc.ports[i];
@ -560,6 +692,35 @@ static const struct spa_fga_descriptor *lv2_plugin_make_desc(void *plugin, const
fp->min = mins[i];
fp->max = maxes[i];
fp->def = controls[i];
spa_log_info(p->log, "registered regular control port: %20s", desc->desc.ports[i].name);
}
LilvNodes *properties = lilv_world_find_nodes(c->world,
lilv_plugin_get_uri(p->p),
lilv_new_uri(c->world, LV2_PATCH__writable),
NULL);
i = desc->n_regular_ports;
LILV_FOREACH(nodes, it, properties) {
struct spa_fga_port *fp = &desc->desc.ports[i++];
const LilvNode *property = lilv_nodes_get(properties, it);
const char *short_name = strrchr(lilv_node_as_string(property), ':');
if (short_name == NULL) {
spa_log_error(p->log, "failed to extract port info for property %s", lilv_node_as_string(property));
continue;
}
fp->name = strdup(short_name + 1);
fp->flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL;
fp->hint = 0;
fp->min = lilv_node_as_float(lilv_world_get(c->world, property, c->lv2_minimum, NULL));
fp->max = lilv_node_as_float(lilv_world_get(c->world, property, c->lv2_maximum, NULL));
fp->def = lilv_node_as_float(lilv_world_get(c->world, property, c->lv2_default, NULL));
fp->index = context_map(c, lilv_node_as_uri(property));
spa_log_info(p->log, "registered property control port: %16s\t(min:\t% 3.5f\tmax:\t% 3.5f\tdef:\t% 3.5f)",
fp->name, fp->min, fp->max, fp->def);
}
return &desc->desc;
}