mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
filter-graph: add support for lv2 State
If the lv2 plugin supports State restore, use it to load state from the config section in the lv2 plugin definition.
This commit is contained in:
parent
f7eab4172e
commit
ccb7a51c3a
2 changed files with 124 additions and 5 deletions
|
|
@ -10,6 +10,7 @@
|
|||
#include <spa/utils/defs.h>
|
||||
#include <spa/utils/list.h>
|
||||
#include <spa/utils/string.h>
|
||||
#include <spa/utils/json.h>
|
||||
#include <spa/support/loop.h>
|
||||
#include <spa/support/log.h>
|
||||
|
||||
|
|
@ -19,6 +20,7 @@
|
|||
#include <lv2/atom/atom.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>
|
||||
|
||||
|
|
@ -27,6 +29,7 @@
|
|||
#include <lv2/lv2plug.in/ns/ext/atom/atom.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>
|
||||
|
||||
|
|
@ -101,6 +104,7 @@ struct context {
|
|||
LilvNode *boundedBlockLength;
|
||||
LilvNode* worker_schedule;
|
||||
LilvNode* worker_iface;
|
||||
LilvNode* state_iface;
|
||||
|
||||
URITable uri_table;
|
||||
LV2_URID_Map map;
|
||||
|
|
@ -169,6 +173,7 @@ static struct context *context_new(void)
|
|||
c->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->map.handle = &c->uri_table;
|
||||
c->map.map = uri_table_map;
|
||||
|
|
@ -234,9 +239,10 @@ struct instance {
|
|||
LV2_Options_Option options[6];
|
||||
LV2_Feature options_feature;
|
||||
|
||||
const LV2_Feature *features[7];
|
||||
const LV2_Feature *features[10];
|
||||
|
||||
const LV2_Worker_Interface *work_iface;
|
||||
const LV2_State_Interface *state_iface;
|
||||
|
||||
int32_t block_length;
|
||||
LV2_Atom empty_atom;
|
||||
|
|
@ -278,6 +284,57 @@ work_schedule(LV2_Worker_Schedule_Handle handle, uint32_t size, const void *data
|
|||
return LV2_WORKER_SUCCESS;
|
||||
}
|
||||
|
||||
struct state_data {
|
||||
struct instance *i;
|
||||
const char *config;
|
||||
char *tmp;
|
||||
};
|
||||
|
||||
static const void *state_retrieve_function(LV2_State_Handle handle,
|
||||
uint32_t key, size_t *size, uint32_t *type, uint32_t *flags)
|
||||
{
|
||||
struct state_data *sd = (struct state_data*)handle;
|
||||
struct plugin *p = sd->i->p;
|
||||
struct context *c = p->c;
|
||||
const char *uri = c->unmap.unmap(c->unmap.handle, key), *val;
|
||||
struct spa_json it[3];
|
||||
char k[strlen(uri)+3];
|
||||
int len;
|
||||
|
||||
if (sd->config == NULL) {
|
||||
spa_log_info(p->log, "lv2: restore %d %s without a config", key, uri);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (spa_json_begin_object(&it[0], sd->config, strlen(sd->config)) <= 0) {
|
||||
spa_log_error(p->log, "lv2: config must be an object");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while ((len = spa_json_object_next(&it[0], k, sizeof(k), &val)) > 0) {
|
||||
if (!spa_streq(k, uri))
|
||||
continue;
|
||||
|
||||
if (spa_json_is_container(val, len))
|
||||
if ((len = spa_json_container_len(&it[0], val, len)) <= 0)
|
||||
return NULL;
|
||||
|
||||
sd->tmp = realloc(sd->tmp, len+1);
|
||||
spa_json_parse_stringn(val, len, sd->tmp, len+1);
|
||||
|
||||
spa_log_info(p->log, "lv2: restore %d %s %s", key, uri, sd->tmp);
|
||||
if (size)
|
||||
*size = strlen(sd->tmp);
|
||||
if (type)
|
||||
*type = 0;
|
||||
if (flags)
|
||||
*flags = LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE;
|
||||
return sd->tmp;
|
||||
}
|
||||
spa_log_info(p->log, "lv2: restore %d %s not found in config", key, uri);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *lv2_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor *desc,
|
||||
unsigned long SampleRate, int index, const char *config)
|
||||
{
|
||||
|
|
@ -328,9 +385,11 @@ static void *lv2_instantiate(const struct spa_fga_plugin *plugin, const struct s
|
|||
c->atom_Float, &fsample_rate };
|
||||
i->options[5] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0, 0, 0, 0, NULL };
|
||||
|
||||
i->options_feature.URI = LV2_OPTIONS__options;
|
||||
i->options_feature.data = i->options;
|
||||
i->features[n_features++] = &i->options_feature;
|
||||
i->options_feature.URI = LV2_OPTIONS__options;
|
||||
i->options_feature.data = i->options;
|
||||
i->features[n_features++] = &i->options_feature;
|
||||
i->features[n_features++] = NULL;
|
||||
spa_assert(n_features < SPA_N_ELEMENTS(i->features));
|
||||
|
||||
i->instance = lilv_plugin_instantiate(p->p, SampleRate, i->features);
|
||||
if (i->instance == NULL) {
|
||||
|
|
@ -341,19 +400,30 @@ static void *lv2_instantiate(const struct spa_fga_plugin *plugin, const struct s
|
|||
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)) {
|
||||
i->state_iface = (const LV2_State_Interface*)
|
||||
lilv_instance_get_extension_data(i->instance, LV2_STATE__interface);
|
||||
}
|
||||
for (n = 0; n < desc->n_ports; n++) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (i->state_iface && i->state_iface->restore) {
|
||||
struct state_data sd = { .i = i, .config = config, .tmp = NULL };
|
||||
i->state_iface->restore(i->instance->lv2_handle, state_retrieve_function,
|
||||
&sd, 0, i->features);
|
||||
free(sd.tmp);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static void lv2_cleanup(void *instance)
|
||||
{
|
||||
struct instance *i = 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);
|
||||
free(i);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,10 +110,59 @@ extern struct spa_handle_factory spa_filter_graph_factory;
|
|||
*
|
||||
* - `config` contains a filter specific configuration section. Some plugins need
|
||||
* this. (convolver, sofa, delay, ...)
|
||||
* - For lv2, the config can contain a set of state key/value pairs. If the lv2
|
||||
* plugin supports the LV2_STATE__interface, these values will be provided for
|
||||
* the given keys.
|
||||
* - `control` contains the initial values for the control ports of the filter.
|
||||
* normally these are given with the port name but it is also possible
|
||||
* to give the control index as the key.
|
||||
*
|
||||
* Some examples ladspa and lv2 plugins:
|
||||
*
|
||||
*\code{.unparsed}
|
||||
* filter.graph = {
|
||||
* nodes = [
|
||||
* {
|
||||
* # an example ladspa plugin
|
||||
* type = ladspa
|
||||
* name = pitch
|
||||
* plugin = "/usr/lib64/ladspa/ladspa-rubberband.so"
|
||||
* label = "rubberband-r3-pitchshifter-mono"
|
||||
* control = {
|
||||
* # controls are using the ladspa port names as seen in analyseplugin
|
||||
* "Semitones" = -3
|
||||
* }
|
||||
* }
|
||||
* {
|
||||
* # an example lv2 plugin
|
||||
* type = lv2
|
||||
* name = pitch
|
||||
* plugin = "http://breakfastquay.com/rdf/lv2-rubberband#mono"
|
||||
* control = {
|
||||
* # controls are using the lv2 symbol as seen with lv2info
|
||||
* "semitones" = -3
|
||||
* }
|
||||
* }
|
||||
* {
|
||||
* # an example lv2 plugin with a state
|
||||
* type = lv2
|
||||
* name = neural
|
||||
* plugin = "http://aidadsp.cc/plugins/aidadsp-bundle/rt-neural-generic"
|
||||
* control = {
|
||||
* # use the port symbols as seen with lv2info
|
||||
* PRESENCE = 1.0
|
||||
* }
|
||||
* config = {
|
||||
* # the config contains state keys and values
|
||||
* "http://aidadsp.cc/plugins/aidadsp-bundle/rt-neural-generic#json" =
|
||||
* "/usr/lib64/lv2/rt-neural-generic.lv2/models/deer ink studios/tw40_blues_solo_deerinkstudios.json"
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ...
|
||||
* }
|
||||
*\endcode
|
||||
*
|
||||
* ### Links
|
||||
*
|
||||
* Links can be made between ports of nodes. The `portname` is given as
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue