lv2: support for loading presets

Detect if plugins can do presets. Lazily read a list of presets and add support
for loading.
This commit is contained in:
Stefan Sauer 2016-06-15 22:22:58 +02:00
parent 6b511fdcd1
commit e9a2c4f1e1
7 changed files with 362 additions and 35 deletions

View file

@ -34,8 +34,7 @@ gst-launch-1.0 calf-sourceforge-net-plugins-Organ event-in="C-3" name=s ! interl
TODO
* support presets
for pl in $(lv2ls); do if test "$(lv2info "$pl" | grep -A1 "Presets:" | tail -n1)" != ""; then echo "$pl"; fi; done
* make filters gap-aware
* support more host features
GST_DEBUG="lv2:4" GST_DEBUG_FILE=/tmp/gst.log gst-inspect lv2
grep -o "needs host feature: .*$" /tmp/gst.log | sort | uniq -c | sort -n

View file

@ -43,6 +43,8 @@
#include <gst/audio/audio-channels.h>
#include <lv2/lv2plug.in/ns/ext/port-groups/port-groups.h>
#include "lv2/lv2plug.in/ns/ext/event/event.h"
#include "lv2/lv2plug.in/ns/ext/presets/presets.h"
#include "lv2/lv2plug.in/ns/ext/state/state.h"
GST_DEBUG_CATEGORY (lv2_debug);
#define GST_CAT_DEFAULT lv2_debug
@ -71,13 +73,13 @@ lv2_plugin_register_element (GstPlugin * plugin, GstStructure * lv2_meta)
static void
lv2_count_ports (const LilvPlugin * lv2plugin, guint * audio_in,
guint * audio_out)
guint * audio_out, guint * control)
{
GHashTable *port_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, NULL);
guint i;
*audio_in = *audio_out = 0;
*audio_in = *audio_out = *control = 0;
for (i = 0; i < lilv_plugin_get_num_ports (lv2plugin); i++) {
const LilvPort *port = lilv_plugin_get_port_by_index (lv2plugin, i);
@ -99,6 +101,9 @@ lv2_count_ports (const LilvPlugin * lv2plugin, guint * audio_in,
(*audio_in)++;
else
(*audio_out)++;
} else if (lilv_port_is_a (lv2plugin, port, control_class) ||
lilv_port_is_a (lv2plugin, port, cv_class)) {
(*control)++;
}
}
g_hash_table_unref (port_groups);
@ -108,7 +113,7 @@ lv2_count_ports (const LilvPlugin * lv2plugin, guint * audio_in,
static gboolean
lv2_plugin_discover (GstPlugin * plugin)
{
guint audio_in, audio_out;
guint audio_in, audio_out, control;
LilvIter *i;
const LilvPlugins *plugins = lilv_world_get_all_plugins (world);
@ -119,6 +124,7 @@ lv2_plugin_discover (GstPlugin * plugin)
const LilvPlugin *lv2plugin = lilv_plugins_get (plugins, i);
const gchar *plugin_uri, *p;
gchar *type_name;
gboolean can_do_presets;
plugin_uri = lilv_node_as_uri (lilv_plugin_get_uri (lv2plugin));
@ -142,7 +148,7 @@ lv2_plugin_discover (GstPlugin * plugin)
goto next;
/* check if this has any audio ports */
lv2_count_ports (lv2plugin, &audio_in, &audio_out);
lv2_count_ports (lv2plugin, &audio_in, &audio_out, &control);
if (audio_in == 0 && audio_out == 0) {
GST_FIXME ("plugin %s has no audio pads", type_name);
@ -165,12 +171,19 @@ lv2_plugin_discover (GstPlugin * plugin)
}
}
lv2_meta = gst_structure_new_empty ("lv2");
gst_structure_set (lv2_meta,
/* check supported extensions */
can_do_presets = lilv_plugin_has_extension_data (lv2plugin, state_iface)
|| lilv_plugin_has_feature (lv2plugin, state_uri)
|| (control > 0);
GST_INFO ("plugin %s can%s do presets", type_name,
(can_do_presets ? "" : "'t"));
lv2_meta = gst_structure_new ("lv2",
"element-uri", G_TYPE_STRING, plugin_uri,
"element-type-name", G_TYPE_STRING, type_name,
"audio-in", G_TYPE_UINT, audio_in,
"audio-out", G_TYPE_UINT, audio_out, NULL);
"audio-out", G_TYPE_UINT, audio_out,
"can-do-presets", G_TYPE_BOOLEAN, can_do_presets, NULL);
g_value_init (&value, GST_TYPE_STRUCTURE);
g_value_set_boxed (&value, lv2_meta);
@ -198,6 +211,7 @@ plugin_init (GstPlugin * plugin)
world = lilv_world_new ();
lilv_world_load_all (world);
gst_lv2_host_init ();
/* have been added after lilv-0.22.0, which is the last release */
#ifndef LILV_URI_ATOM_PORT
@ -214,6 +228,9 @@ plugin_init (GstPlugin * plugin)
event_class = lilv_new_uri (world, LILV_URI_EVENT_PORT);
input_class = lilv_new_uri (world, LILV_URI_INPUT_PORT);
output_class = lilv_new_uri (world, LILV_URI_OUTPUT_PORT);
preset_class = lilv_new_uri (world, LV2_PRESETS__Preset);
state_iface = lilv_new_uri (world, LV2_STATE__interface);
state_uri = lilv_new_uri (world, LV2_STATE_URI);
integer_prop = lilv_new_uri (world, LV2_CORE__integer);
toggled_prop = lilv_new_uri (world, LV2_CORE__toggled);
@ -222,6 +239,7 @@ plugin_init (GstPlugin * plugin)
optional_pred = lilv_new_uri (world, LV2_CORE__optionalFeature);
group_pred = lilv_new_uri (world, LV2_PORT_GROUPS__group);
supports_event_pred = lilv_new_uri (world, LV2_EVENT__supportsEvent);
label_pred = lilv_new_uri (world, LILV_NS_RDFS "label");
center_role = lilv_new_uri (world, LV2_PORT_GROUPS__center);
left_role = lilv_new_uri (world, LV2_PORT_GROUPS__left);
@ -296,6 +314,9 @@ __attribute__ ((destructor))
lilv_node_free (event_class);
lilv_node_free (input_class);
lilv_node_free (output_class);
lilv_node_free (preset_class);
lilv_node_free (state_iface);
lilv_node_free (state_uri);
lilv_node_free (integer_prop);
lilv_node_free (toggled_prop);
@ -304,6 +325,7 @@ __attribute__ ((destructor))
lilv_node_free (optional_pred);
lilv_node_free (group_pred);
lilv_node_free (supports_event_pred);
lilv_node_free (label_pred);
lilv_node_free (center_role);
lilv_node_free (left_role);

View file

@ -36,6 +36,10 @@ LilvNode *cv_class;
LilvNode *event_class;
LilvNode *input_class;
LilvNode *output_class;
LilvNode *preset_class;
LilvNode *state_iface;
LilvNode *state_uri;
LilvNode *integer_prop;
LilvNode *toggled_prop;
LilvNode *designation_pred;
@ -43,6 +47,7 @@ LilvNode *in_place_broken_pred;
LilvNode *optional_pred;
LilvNode *group_pred;
LilvNode *supports_event_pred;
LilvNode *label_pred;
LilvNode *center_role;
LilvNode *left_role;

View file

@ -70,6 +70,73 @@ struct _GstLV2FilterClass
static GstAudioFilter *parent_class = NULL;
/* preset interface */
static gchar **
gst_lv2_filter_get_preset_names (GstPreset * preset)
{
GstLV2Filter *self = (GstLV2Filter *) preset;
return gst_lv2_get_preset_names (&self->lv2, (GstObject *) self);
}
static gboolean
gst_lv2_filter_load_preset (GstPreset * preset, const gchar * name)
{
GstLV2Filter *self = (GstLV2Filter *) preset;
return gst_lv2_load_preset (&self->lv2, (GstObject *) self, name);
}
static gboolean
gst_lv2_filter_save_preset (GstPreset * preset, const gchar * name)
{
return FALSE;
}
static gboolean
gst_lv2_filter_rename_preset (GstPreset * preset, const gchar * old_name,
const gchar * new_name)
{
return FALSE;
}
static gboolean
gst_lv2_filter_delete_preset (GstPreset * preset, const gchar * name)
{
return FALSE;
}
static gboolean
gst_lv2_filter_set_meta (GstPreset * preset, const gchar * name,
const gchar * tag, const gchar * value)
{
return FALSE;
}
static gboolean
gst_lv2_filter_get_meta (GstPreset * preset, const gchar * name,
const gchar * tag, gchar ** value)
{
*value = NULL;
return FALSE;
}
static void
gst_lv2_filter_preset_interface_init (gpointer g_iface, gpointer iface_data)
{
GstPresetInterface *iface = g_iface;
iface->get_preset_names = gst_lv2_filter_get_preset_names;
iface->load_preset = gst_lv2_filter_load_preset;
iface->save_preset = gst_lv2_filter_save_preset;
iface->rename_preset = gst_lv2_filter_rename_preset;
iface->delete_preset = gst_lv2_filter_delete_preset;
iface->set_meta = gst_lv2_filter_set_meta;
iface->get_meta = gst_lv2_filter_get_meta;
}
/* GObject vmethods implementation */
static void
gst_lv2_filter_set_property (GObject * object, guint prop_id,
@ -472,9 +539,25 @@ gst_lv2_filter_register_element (GstPlugin * plugin, GstStructure * lv2_meta)
0,
(GInstanceInitFunc) gst_lv2_filter_init,
};
const gchar *type_name =
gst_structure_get_string (lv2_meta, "element-type-name");
GType element_type =
g_type_register_static (GST_TYPE_AUDIO_FILTER, type_name, &info, 0);
gboolean can_do_presets;
/* create the type */
gst_lv2_register_element (plugin, GST_TYPE_AUDIO_FILTER, &info, lv2_meta);
/* register interfaces */
gst_structure_get_boolean (lv2_meta, "can-do-presets", &can_do_presets);
if (can_do_presets) {
const GInterfaceInfo preset_interface_info = {
(GInterfaceInitFunc) gst_lv2_filter_preset_interface_init,
NULL,
NULL
};
g_type_add_interface_static (element_type, GST_TYPE_PRESET,
&preset_interface_info);
}
gst_element_register (plugin, type_name, GST_RANK_NONE, element_type);
if (!parent_class)
parent_class = g_type_class_ref (GST_TYPE_AUDIO_FILTER);

View file

@ -87,6 +87,73 @@ enum
static GstBaseSrc *parent_class = NULL;
/* preset interface */
static gchar **
gst_lv2_source_get_preset_names (GstPreset * preset)
{
GstLV2Source *self = (GstLV2Source *) preset;
return gst_lv2_get_preset_names (&self->lv2, (GstObject *) self);
}
static gboolean
gst_lv2_source_load_preset (GstPreset * preset, const gchar * name)
{
GstLV2Source *self = (GstLV2Source *) preset;
return gst_lv2_load_preset (&self->lv2, (GstObject *) self, name);
}
static gboolean
gst_lv2_source_save_preset (GstPreset * preset, const gchar * name)
{
return FALSE;
}
static gboolean
gst_lv2_source_rename_preset (GstPreset * preset, const gchar * old_name,
const gchar * new_name)
{
return FALSE;
}
static gboolean
gst_lv2_source_delete_preset (GstPreset * preset, const gchar * name)
{
return FALSE;
}
static gboolean
gst_lv2_source_set_meta (GstPreset * preset, const gchar * name,
const gchar * tag, const gchar * value)
{
return FALSE;
}
static gboolean
gst_lv2_source_get_meta (GstPreset * preset, const gchar * name,
const gchar * tag, gchar ** value)
{
*value = NULL;
return FALSE;
}
static void
gst_lv2_source_preset_interface_init (gpointer g_iface, gpointer iface_data)
{
GstPresetInterface *iface = g_iface;
iface->get_preset_names = gst_lv2_source_get_preset_names;
iface->load_preset = gst_lv2_source_load_preset;
iface->save_preset = gst_lv2_source_save_preset;
iface->rename_preset = gst_lv2_source_rename_preset;
iface->delete_preset = gst_lv2_source_delete_preset;
iface->set_meta = gst_lv2_source_set_meta;
iface->get_meta = gst_lv2_source_get_meta;
}
/* GstBasesrc vmethods implementation */
static gboolean
@ -658,8 +725,26 @@ gst_lv2_source_register_element (GstPlugin * plugin, GstStructure * lv2_meta)
0,
(GInstanceInitFunc) gst_lv2_source_init,
};
const gchar *type_name =
gst_structure_get_string (lv2_meta, "element-type-name");
GType element_type =
g_type_register_static (GST_TYPE_BASE_SRC, type_name, &info, 0);
gboolean can_do_presets;
gst_lv2_register_element (plugin, GST_TYPE_BASE_SRC, &info, lv2_meta);
/* register interfaces */
gst_structure_get_boolean (lv2_meta, "can-do-presets", &can_do_presets);
if (can_do_presets) {
const GInterfaceInfo preset_interface_info = {
(GInterfaceInitFunc) gst_lv2_source_preset_interface_init,
NULL,
NULL
};
g_type_add_interface_static (element_type, GST_TYPE_PRESET,
&preset_interface_info);
}
gst_element_register (plugin, type_name, GST_RANK_NONE, element_type);
if (!parent_class)
parent_class = g_type_class_ref (GST_TYPE_BASE_SRC);

View file

@ -28,7 +28,10 @@
#include "gstlv2.h"
#include "gstlv2utils.h"
#include "lv2/lv2plug.in/ns/ext/atom/atom.h"
#include "lv2/lv2plug.in/ns/ext/atom/forge.h"
#include <lv2/lv2plug.in/ns/ext/log/log.h>
#include <lv2/lv2plug.in/ns/ext/state/state.h>
#include <lv2/lv2plug.in/ns/ext/urid/urid.h>
GST_DEBUG_CATEGORY_EXTERN (lv2_debug);
@ -133,6 +136,134 @@ gst_lv2_check_required_features (const LilvPlugin * lv2plugin)
return TRUE;
}
static LV2_Atom_Forge forge;
void
gst_lv2_host_init (void)
{
lv2_atom_forge_init (&forge, &lv2_map);
}
/* preset interface */
gchar **
gst_lv2_get_preset_names (GstLV2 * lv2, GstObject * obj)
{
/* lazily scan for presets when first called */
if (!lv2->presets) {
LilvNodes *presets;
if ((presets = lilv_plugin_get_related (lv2->klass->plugin, preset_class))) {
LilvIter *j;
lv2->presets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
(GDestroyNotify) lilv_node_free);
for (j = lilv_nodes_begin (presets);
!lilv_nodes_is_end (presets, j); j = lilv_nodes_next (presets, j)) {
const LilvNode *preset = lilv_nodes_get (presets, j);
LilvNodes *titles;
lilv_world_load_resource (world, preset);
titles = lilv_world_find_nodes (world, preset, label_pred, NULL);
if (titles) {
const LilvNode *title = lilv_nodes_get_first (titles);
g_hash_table_insert (lv2->presets,
g_strdup (lilv_node_as_string (title)),
lilv_node_duplicate (preset));
lilv_nodes_free (titles);
} else {
GST_WARNING_OBJECT (obj, "plugin has preset '%s' without rdfs:label",
lilv_node_as_string (preset));
}
}
lilv_nodes_free (presets);
}
}
if (lv2->presets) {
GList *node, *keys = g_hash_table_get_keys (lv2->presets);
gchar **names = g_new0 (gchar *, g_hash_table_size (lv2->presets) + 1);
gint i = 0;
for (node = keys; node; node = g_list_next (node)) {
names[i++] = g_strdup (node->data);
}
g_list_free (keys);
return names;
}
return NULL;
}
static void
set_port_value (const char *port_symbol, void *data, const void *value,
uint32_t size, uint32_t type)
{
gpointer *user_data = (gpointer *) data;
GstLV2Class *klass = user_data[0];
GstObject *obj = user_data[1];
gchar *prop_name = g_hash_table_lookup (klass->sym_to_name, port_symbol);
gfloat fvalue;
if (!prop_name) {
GST_WARNING_OBJECT (obj, "Preset port '%s' is missing", port_symbol);
return;
}
if (type == forge.Float) {
fvalue = *(const gfloat *) value;
} else if (type == forge.Double) {
fvalue = *(const gdouble *) value;
} else if (type == forge.Int) {
fvalue = *(const gint32 *) value;
} else if (type == forge.Long) {
fvalue = *(const gint64 *) value;
} else {
GST_WARNING_OBJECT (obj, "Preset '%s' value has bad type '%s'",
port_symbol, lv2_unmap.unmap (lv2_unmap.handle, type));
return;
}
g_object_set (obj, prop_name, fvalue, NULL);
}
gboolean
gst_lv2_load_preset (GstLV2 * lv2, GstObject * obj, const gchar * name)
{
LilvNode *preset = g_hash_table_lookup (lv2->presets, name);
LilvState *state = lilv_state_new_from_world (world, &lv2_map, preset);
gpointer user_data[] = { lv2->klass, obj };
GST_INFO_OBJECT (obj, "loading preset <%s>", lilv_node_as_string (preset));
lilv_state_restore (state, lv2->instance, set_port_value,
(gpointer) user_data, 0, NULL);
lilv_state_free (state);
return FALSE;
}
#if 0
gboolean
gst_lv2_save_preset (GstLV2 * lv2, GstObject * obj, const gchar * name)
{
return FALSE;
}
gboolean
gst_lv2_rename_preset (GstLV2 * lv2, GstObject * obj,
const gchar * old_name, const gchar * new_name)
{
return FALSE;
}
gboolean
gst_lv2_delete_preset (GstLV2 * lv2, GstObject * obj, const gchar * name)
{
return FALSE;
}
#endif
/* api helpers */
void
@ -150,6 +281,9 @@ gst_lv2_init (GstLV2 * lv2, GstLV2Class * lv2_class)
void
gst_lv2_finalize (GstLV2 * lv2)
{
if (lv2->presets) {
g_hash_table_destroy (lv2->presets);
}
g_free (lv2->ports.control.in);
g_free (lv2->ports.control.out);
}
@ -301,12 +435,9 @@ gst_lv2_object_get_property (GstLV2 * lv2, GObject * object,
static gchar *
gst_lv2_class_get_param_name (GstLV2Class * klass, GObjectClass * object_class,
const LilvPort * port)
const gchar * port_symbol)
{
const LilvPlugin *lv2plugin = klass->plugin;
gchar *ret;
ret = g_strdup (lilv_node_as_string (lilv_port_get_symbol (lv2plugin, port)));
gchar *ret = g_strdup (port_symbol);
/* this is the same thing that param_spec_* will do */
g_strcanon (ret, G_CSET_A_2_Z G_CSET_a_2_z G_CSET_DIGITS "-", '-');
@ -331,9 +462,7 @@ gst_lv2_class_get_param_name (GstLV2Class * klass, GObjectClass * object_class,
ret = nret;
}
GST_DEBUG ("built property name '%s' from port name '%s'", ret,
lilv_node_as_string (lilv_port_get_symbol (lv2plugin, port)));
GST_DEBUG ("built property name '%s' from port name '%s'", ret, port_symbol);
return ret;
}
@ -364,9 +493,11 @@ gst_lv2_class_get_param_spec (GstLV2Class * klass, GObjectClass * object_class,
gint perms;
gfloat lower = 0.0f, upper = 1.0f, def = 0.0f;
GType enum_type = G_TYPE_INVALID;
const gchar *port_symbol =
lilv_node_as_string (lilv_port_get_symbol (lv2plugin, port));
nick = gst_lv2_class_get_param_nick (klass, port);
name = gst_lv2_class_get_param_name (klass, object_class, port);
name = gst_lv2_class_get_param_name (klass, object_class, port_symbol);
GST_DEBUG ("%s trying port %s : %s",
lilv_node_as_string (lilv_plugin_get_uri (lv2plugin)), name, nick);
@ -480,6 +611,10 @@ gst_lv2_class_get_param_spec (GstLV2Class * klass, GObjectClass * object_class,
ret = g_param_spec_float (name, nick, nick, lower, upper, def, perms);
done:
// build a map of (port_symbol to ret->name) for extensions
g_hash_table_insert (klass->sym_to_name, (gchar *) port_symbol,
(gchar *) ret->name);
g_free (name);
g_free (nick);
@ -572,6 +707,8 @@ gst_lv2_class_init (GstLV2Class * lv2_class, GType type)
lv2_class->plugin = lv2plugin;
lilv_node_free (plugin_uri);
lv2_class->sym_to_name = g_hash_table_new (g_str_hash, g_str_equal);
lv2_class->in_group.ports = g_array_new (FALSE, TRUE, sizeof (GstLV2Port));
lv2_class->out_group.ports = g_array_new (FALSE, TRUE, sizeof (GstLV2Port));
lv2_class->control_in_ports = g_array_new (FALSE, TRUE, sizeof (GstLV2Port));
@ -692,6 +829,8 @@ gst_lv2_class_finalize (GstLV2Class * lv2_class)
{
GST_DEBUG ("LV2 finalizing class");
g_hash_table_destroy (lv2_class->sym_to_name);
g_array_free (lv2_class->in_group.ports, TRUE);
lv2_class->in_group.ports = NULL;
g_array_free (lv2_class->out_group.ports, TRUE);
@ -701,14 +840,3 @@ gst_lv2_class_finalize (GstLV2Class * lv2_class)
g_array_free (lv2_class->control_out_ports, TRUE);
lv2_class->control_out_ports = NULL;
}
void
gst_lv2_register_element (GstPlugin * plugin, GType parent_type,
const GTypeInfo * info, GstStructure * lv2_meta)
{
const gchar *type_name =
gst_structure_get_string (lv2_meta, "element-type-name");
gst_element_register (plugin, type_name, GST_RANK_NONE,
g_type_register_static (parent_type, type_name, info, 0));
}

View file

@ -69,6 +69,7 @@ struct _GstLV2
GstLV2Class *klass;
LilvInstance *instance;
GHashTable *presets;
gboolean activated;
unsigned long rate;
@ -88,6 +89,7 @@ struct _GstLV2Class
guint properties;
const LilvPlugin *plugin;
GHashTable *sym_to_name;
gint num_control_in, num_control_out;
gint num_cv_in, num_cv_out;
@ -98,7 +100,12 @@ struct _GstLV2Class
GArray *control_out_ports; /**< Array of GstLV2Port */
};
gboolean gst_lv2_check_required_features (const LilvPlugin *lv2plugin);
gboolean gst_lv2_check_required_features (const LilvPlugin * lv2plugin);
void gst_lv2_host_init (void);
gchar **gst_lv2_get_preset_names (GstLV2 * lv2, GstObject * obj);
gboolean gst_lv2_load_preset (GstLV2 * lv2, GstObject * obj, const gchar * name);
void gst_lv2_init (GstLV2 * lv2, GstLV2Class * lv2_class);
void gst_lv2_finalize (GstLV2 * lv2);
@ -119,8 +126,6 @@ void gst_lv2_element_class_set_metadata (GstLV2Class * lv2_class,
void gst_lv2_class_init (GstLV2Class * lv2_class, GType type);
void gst_lv2_class_finalize (GstLV2Class * lv2_class);
void gst_lv2_register_element (GstPlugin * plugin, GType parent_type,
const GTypeInfo * info, GstStructure * lv2_meta);
G_END_DECLS
#endif /* __GST_LV2_UTILS_H__ */