diff --git a/spa/plugins/filter-graph/lv2_plugin.c b/spa/plugins/filter-graph/lv2_plugin.c index 47566bc9f..0173ab4b4 100644 --- a/spa/plugins/filter-graph/lv2_plugin.c +++ b/spa/plugins/filter-graph/lv2_plugin.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -19,6 +20,7 @@ #include #include #include + #include #include #include @@ -27,6 +29,7 @@ #include #include #include + #include #include #include @@ -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); } diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 8b4e54ac0..6b72ea025 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -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