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
#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
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);
(*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);
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);
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));
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);
}
}
- 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);
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
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);
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);
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);
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);
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;
LilvNode *optional_pred;
LilvNode *group_pred;
LilvNode *supports_event_pred;
+LilvNode *label_pred;
LilvNode *center_role;
LilvNode *left_role;
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,
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;
+
+ /* 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);
+ }
- /* create the type */
- gst_lv2_register_element (plugin, GST_TYPE_AUDIO_FILTER, &info, lv2_meta);
+ gst_element_register (plugin, type_name, GST_RANK_NONE, element_type);
if (!parent_class)
parent_class = g_type_class_ref (GST_TYPE_AUDIO_FILTER);
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
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;
+
+ /* 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_lv2_register_element (plugin, GST_TYPE_BASE_SRC, &info, lv2_meta);
+ gst_element_register (plugin, type_name, GST_RANK_NONE, element_type);
if (!parent_class)
parent_class = g_type_class_ref (GST_TYPE_BASE_SRC);
#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);
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
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);
}
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 "-", '-');
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;
}
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);
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);
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));
{
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);
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));
-}
GstLV2Class *klass;
LilvInstance *instance;
+ GHashTable *presets;
gboolean activated;
unsigned long rate;
guint properties;
const LilvPlugin *plugin;
+ GHashTable *sym_to_name;
gint num_control_in, num_control_out;
gint num_cv_in, num_cv_out;
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);
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__ */