| /* GStreamer |
| * Copyright (C) 2016 Thibault Saunier <thibault.saunier@collabora.com> |
| * 2016 Stefan Sauer <ensonic@users.sf.net> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "config.h" |
| #endif |
| |
| #include "string.h" |
| #include "stdlib.h" |
| |
| #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); |
| #define GST_CAT_DEFAULT lv2_debug |
| |
| /* host features */ |
| |
| /* - log extension */ |
| |
| static int |
| lv2_log_printf (LV2_Log_Handle handle, LV2_URID type, const char *fmt, ...) |
| { |
| va_list ap; |
| |
| va_start (ap, fmt); |
| gst_debug_log_valist (lv2_debug, GST_LEVEL_INFO, "", "", 0, NULL, fmt, ap); |
| va_end (ap); |
| return 1; |
| } |
| |
| static int |
| lv2_log_vprintf (LV2_Log_Handle handle, LV2_URID type, |
| const char *fmt, va_list ap) |
| { |
| gst_debug_log_valist (lv2_debug, GST_LEVEL_INFO, "", "", 0, NULL, fmt, ap); |
| return 1; |
| } |
| |
| static LV2_Log_Log lv2_log = { |
| /* handle = */ NULL, lv2_log_printf, lv2_log_vprintf |
| }; |
| |
| |
| static const LV2_Feature lv2_log_feature = { LV2_LOG__log, &lv2_log }; |
| |
| /* - urid map/unmap extension */ |
| |
| static LV2_URID |
| lv2_urid_map (LV2_URID_Map_Handle handle, const char *uri) |
| { |
| return (LV2_URID) g_quark_from_string (uri); |
| } |
| |
| static const char * |
| lv2_urid_unmap (LV2_URID_Unmap_Handle handle, LV2_URID urid) |
| { |
| return g_quark_to_string ((GQuark) urid); |
| } |
| |
| static LV2_URID_Map lv2_map = { |
| /* handle = */ NULL, lv2_urid_map |
| }; |
| |
| static LV2_URID_Unmap lv2_unmap = { |
| /* handle = */ NULL, lv2_urid_unmap |
| }; |
| |
| static const LV2_Feature lv2_map_feature = { LV2_URID__map, &lv2_map }; |
| static const LV2_Feature lv2_unmap_feature = { LV2_URID__unmap, &lv2_unmap }; |
| |
| /* feature list */ |
| |
| static const LV2_Feature *lv2_features[] = { |
| &lv2_log_feature, |
| &lv2_map_feature, |
| &lv2_unmap_feature, |
| NULL |
| }; |
| |
| gboolean |
| gst_lv2_check_required_features (const LilvPlugin * lv2plugin) |
| { |
| LilvNodes *required_features = lilv_plugin_get_required_features (lv2plugin); |
| if (required_features) { |
| LilvIter *i; |
| gint j; |
| gboolean missing = FALSE; |
| |
| for (i = lilv_nodes_begin (required_features); |
| !lilv_nodes_is_end (required_features, i); |
| i = lilv_nodes_next (required_features, i)) { |
| const LilvNode *required_feature = lilv_nodes_get (required_features, i); |
| const char *required_feature_uri = lilv_node_as_uri (required_feature); |
| missing = TRUE; |
| |
| for (j = 0; lv2_features[j]; j++) { |
| if (!strcmp (lv2_features[j]->URI, required_feature_uri)) { |
| missing = FALSE; |
| break; |
| } |
| } |
| if (missing) { |
| GST_FIXME ("lv2 plugin %s needs host feature: %s", |
| lilv_node_as_uri (lilv_plugin_get_uri (lv2plugin)), |
| required_feature_uri); |
| break; |
| } |
| } |
| lilv_nodes_free (required_features); |
| return (!missing); |
| } |
| return TRUE; |
| } |
| |
| static LV2_Atom_Forge forge; |
| |
| void |
| gst_lv2_host_init (void) |
| { |
| lv2_atom_forge_init (&forge, &lv2_map); |
| } |
| |
| /* preset interface */ |
| |
| static char * |
| make_bundle_name (GstObject * obj, const gchar * name) |
| { |
| GstElementFactory *factory; |
| gchar *basename, *s, *bundle; |
| |
| factory = gst_element_get_factory ((GstElement *) obj); |
| basename = g_strdup (gst_element_factory_get_metadata (factory, |
| GST_ELEMENT_METADATA_LONGNAME)); |
| s = basename; |
| while ((s = strchr (s, ' '))) { |
| *s = '_'; |
| } |
| bundle = g_strjoin (NULL, basename, "_", name, ".preset.lv2", NULL); |
| |
| g_free (basename); |
| |
| return bundle; |
| } |
| |
| 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; |
| } |
| |
| static const void * |
| get_port_value (const char *port_symbol, void *data, 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); |
| static gfloat fvalue; |
| |
| if (!prop_name) { |
| GST_WARNING_OBJECT (obj, "Preset port '%s' is missing", port_symbol); |
| *size = *type = 0; |
| return NULL; |
| } |
| |
| *size = sizeof (float); |
| *type = forge.Float; |
| g_object_get (obj, prop_name, &fvalue, NULL); |
| /* FIXME: can we return &lv2->ports.{in,out}[x]; */ |
| return &fvalue; |
| } |
| |
| gboolean |
| gst_lv2_save_preset (GstLV2 * lv2, GstObject * obj, const gchar * name) |
| { |
| gchar *filename, *bundle, *dir, *tmp_dir; |
| gpointer user_data[] = { lv2->klass, obj }; |
| LilvState *state; |
| LilvNode *bundle_dir; |
| const LilvNode *state_uri; |
| LilvInstance *instance = lv2->instance; |
| gboolean res; |
| #ifndef HAVE_LILV_0_22 |
| gchar *filepath; |
| #endif |
| |
| filename = g_strjoin (NULL, name, ".ttl", NULL); |
| bundle = make_bundle_name (obj, name); |
| /* dir needs to end on a dir separator for the lilv_new_file_uri() to work */ |
| dir = |
| g_build_filename (g_get_home_dir (), ".lv2", bundle, G_DIR_SEPARATOR_S, |
| NULL); |
| tmp_dir = g_dir_make_tmp ("gstlv2-XXXXXX", NULL); |
| g_mkdir_with_parents (dir, 0750); |
| |
| if (!instance) { |
| /* instance is NULL until we play!! */ |
| instance = lilv_plugin_instantiate (lv2->klass->plugin, GST_AUDIO_DEF_RATE, |
| lv2_features); |
| } |
| |
| state = lilv_state_new_from_instance (lv2->klass->plugin, instance, &lv2_map, |
| tmp_dir, dir, dir, dir, get_port_value, user_data, |
| LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE, NULL); |
| |
| lilv_state_set_label (state, name); |
| |
| res = lilv_state_save (world, &lv2_map, &lv2_unmap, state, /*uri */ NULL, dir, |
| filename) != 0; |
| |
| /* reload bundle into the world */ |
| bundle_dir = lilv_new_file_uri (world, NULL, dir); |
| lilv_world_unload_bundle (world, bundle_dir); |
| lilv_world_load_bundle (world, bundle_dir); |
| lilv_node_free (bundle_dir); |
| |
| #ifdef HAVE_LILV_0_22 |
| state_uri = lilv_state_get_uri (state); |
| #else |
| filepath = g_build_filename (dir, filename, NULL); |
| state_uri = lilv_new_uri (world, filepath); |
| g_free (filepath); |
| #endif |
| lilv_world_load_resource (world, state_uri); |
| g_hash_table_insert (lv2->presets, g_strdup (name), |
| lilv_node_duplicate (state_uri)); |
| #ifndef HAVE_LILV_0_22 |
| lilv_node_free ((LilvNode *) state_uri); |
| #endif |
| |
| lilv_state_free (state); |
| if (!lv2->instance) { |
| lilv_instance_free (instance); |
| } |
| |
| g_free (tmp_dir); |
| g_free (dir); |
| g_free (bundle); |
| g_free (filename); |
| |
| return res; |
| } |
| |
| #if 0 |
| gboolean |
| gst_lv2_rename_preset (GstLV2 * lv2, GstObject * obj, |
| const gchar * old_name, const gchar * new_name) |
| { |
| /* need to relabel the preset */ |
| return FALSE; |
| } |
| #endif |
| |
| gboolean |
| gst_lv2_delete_preset (GstLV2 * lv2, GstObject * obj, const gchar * name) |
| { |
| #ifdef HAVE_LILV_0_22 |
| LilvNode *preset = g_hash_table_lookup (lv2->presets, name); |
| LilvState *state = lilv_state_new_from_world (world, &lv2_map, preset); |
| |
| lilv_world_unload_resource (world, lilv_state_get_uri (state)); |
| lilv_state_delete (world, state); |
| lilv_state_free (state); |
| #endif |
| g_hash_table_remove (lv2->presets, name); |
| |
| return FALSE; |
| } |
| |
| /* api helpers */ |
| |
| void |
| gst_lv2_init (GstLV2 * lv2, GstLV2Class * lv2_class) |
| { |
| lv2->klass = lv2_class; |
| |
| lv2->instance = NULL; |
| lv2->activated = FALSE; |
| |
| lv2->ports.control.in = g_new0 (gfloat, lv2_class->control_in_ports->len); |
| lv2->ports.control.out = g_new0 (gfloat, lv2_class->control_out_ports->len); |
| } |
| |
| 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); |
| } |
| |
| gboolean |
| gst_lv2_setup (GstLV2 * lv2, unsigned long rate) |
| { |
| GstLV2Class *lv2_class = lv2->klass; |
| GstLV2Port *port; |
| GArray *ports; |
| gint i; |
| |
| if (lv2->instance) |
| lilv_instance_free (lv2->instance); |
| |
| if (!(lv2->instance = |
| lilv_plugin_instantiate (lv2_class->plugin, rate, lv2_features))) |
| return FALSE; |
| |
| /* connect the control ports */ |
| ports = lv2_class->control_in_ports; |
| for (i = 0; i < ports->len; i++) { |
| port = &g_array_index (ports, GstLV2Port, i); |
| if (port->type != GST_LV2_PORT_CONTROL) |
| continue; |
| lilv_instance_connect_port (lv2->instance, port->index, |
| &(lv2->ports.control.in[i])); |
| } |
| ports = lv2_class->control_out_ports; |
| for (i = 0; i < ports->len; i++) { |
| port = &g_array_index (ports, GstLV2Port, i); |
| if (port->type != GST_LV2_PORT_CONTROL) |
| continue; |
| lilv_instance_connect_port (lv2->instance, port->index, |
| &(lv2->ports.control.out[i])); |
| } |
| |
| lilv_instance_activate (lv2->instance); |
| lv2->activated = TRUE; |
| |
| return TRUE; |
| } |
| |
| gboolean |
| gst_lv2_cleanup (GstLV2 * lv2, GstObject * obj) |
| { |
| if (lv2->activated == FALSE) { |
| GST_ERROR_OBJECT (obj, "Deactivating but LV2 plugin not activated"); |
| return TRUE; |
| } |
| |
| if (lv2->instance == NULL) { |
| GST_ERROR_OBJECT (obj, "Deactivating but no LV2 plugin set"); |
| return TRUE; |
| } |
| |
| GST_DEBUG_OBJECT (obj, "deactivating"); |
| |
| lilv_instance_deactivate (lv2->instance); |
| |
| lv2->activated = FALSE; |
| |
| lilv_instance_free (lv2->instance); |
| lv2->instance = NULL; |
| |
| return TRUE; |
| } |
| |
| void |
| gst_lv2_object_set_property (GstLV2 * lv2, GObject * object, |
| guint prop_id, const GValue * value, GParamSpec * pspec) |
| { |
| GType base, type = pspec->value_type; |
| /* remember, properties have an offset */ |
| prop_id -= lv2->klass->properties; |
| |
| /* only input ports */ |
| g_return_if_fail (prop_id < lv2->klass->control_in_ports->len); |
| |
| while ((base = g_type_parent (type))) |
| type = base; |
| |
| /* now see what type it is */ |
| switch (type) { |
| case G_TYPE_BOOLEAN: |
| lv2->ports.control.in[prop_id] = |
| g_value_get_boolean (value) ? 1.0f : 0.0f; |
| break; |
| case G_TYPE_INT: |
| lv2->ports.control.in[prop_id] = g_value_get_int (value); |
| break; |
| case G_TYPE_FLOAT: |
| lv2->ports.control.in[prop_id] = g_value_get_float (value); |
| break; |
| case G_TYPE_ENUM: |
| lv2->ports.control.in[prop_id] = g_value_get_enum (value); |
| break; |
| default: |
| GST_WARNING_OBJECT (object, "unhandled type: %s", |
| g_type_name (pspec->value_type)); |
| g_assert_not_reached (); |
| } |
| } |
| |
| void |
| gst_lv2_object_get_property (GstLV2 * lv2, GObject * object, |
| guint prop_id, GValue * value, GParamSpec * pspec) |
| { |
| GType base, type = pspec->value_type; |
| gfloat *controls; |
| |
| /* remember, properties have an offset */ |
| prop_id -= lv2->klass->properties; |
| |
| if (prop_id < lv2->klass->control_in_ports->len) { |
| controls = lv2->ports.control.in; |
| } else if (prop_id < lv2->klass->control_in_ports->len + |
| lv2->klass->control_out_ports->len) { |
| controls = lv2->ports.control.out; |
| prop_id -= lv2->klass->control_in_ports->len; |
| } else { |
| g_return_if_reached (); |
| } |
| |
| while ((base = g_type_parent (type))) |
| type = base; |
| |
| /* now see what type it is */ |
| switch (type) { |
| case G_TYPE_BOOLEAN: |
| g_value_set_boolean (value, controls[prop_id] > 0.0f); |
| break; |
| case G_TYPE_INT: |
| g_value_set_int (value, CLAMP (controls[prop_id], G_MININT, G_MAXINT)); |
| break; |
| case G_TYPE_FLOAT: |
| g_value_set_float (value, controls[prop_id]); |
| break; |
| case G_TYPE_ENUM: |
| g_value_set_enum (value, (gint) controls[prop_id]); |
| break; |
| default: |
| GST_WARNING_OBJECT (object, "unhandled type: %s", |
| g_type_name (pspec->value_type)); |
| g_return_if_reached (); |
| } |
| } |
| |
| |
| static gchar * |
| gst_lv2_class_get_param_name (GstLV2Class * klass, GObjectClass * object_class, |
| const gchar * port_symbol) |
| { |
| 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 "-", '-'); |
| /* satisfy glib2 (argname[0] must be [A-Za-z]) */ |
| if (!((ret[0] >= 'a' && ret[0] <= 'z') || (ret[0] >= 'A' && ret[0] <= 'Z'))) { |
| gchar *tempstr = ret; |
| |
| ret = g_strconcat ("param-", ret, NULL); |
| g_free (tempstr); |
| } |
| |
| /* check for duplicate property names */ |
| if (g_object_class_find_property (object_class, ret)) { |
| gint n = 1; |
| gchar *nret = g_strdup_printf ("%s-%d", ret, n++); |
| |
| while (g_object_class_find_property (object_class, nret)) { |
| g_free (nret); |
| nret = g_strdup_printf ("%s-%d", ret, n++); |
| } |
| g_free (ret); |
| ret = nret; |
| } |
| |
| GST_DEBUG ("built property name '%s' from port name '%s'", ret, port_symbol); |
| return ret; |
| } |
| |
| static gchar * |
| gst_lv2_class_get_param_nick (GstLV2Class * klass, const LilvPort * port) |
| { |
| const LilvPlugin *lv2plugin = klass->plugin; |
| |
| return g_strdup (lilv_node_as_string (lilv_port_get_name (lv2plugin, port))); |
| } |
| |
| static int |
| enum_val_cmp (GEnumValue * p1, GEnumValue * p2) |
| { |
| return p1->value - p2->value; |
| } |
| |
| static GParamSpec * |
| gst_lv2_class_get_param_spec (GstLV2Class * klass, GObjectClass * object_class, |
| gint portnum) |
| { |
| const LilvPlugin *lv2plugin = klass->plugin; |
| const LilvPort *port = lilv_plugin_get_port_by_index (lv2plugin, portnum); |
| LilvNode *lv2def, *lv2min, *lv2max; |
| LilvScalePoints *points; |
| GParamSpec *ret; |
| gchar *name, *nick; |
| 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_symbol); |
| |
| GST_DEBUG ("%s trying port %s : %s", |
| lilv_node_as_string (lilv_plugin_get_uri (lv2plugin)), name, nick); |
| |
| perms = G_PARAM_READABLE; |
| if (lilv_port_is_a (lv2plugin, port, input_class)) |
| perms |= G_PARAM_WRITABLE | G_PARAM_CONSTRUCT; |
| if (lilv_port_is_a (lv2plugin, port, control_class) || |
| lilv_port_is_a (lv2plugin, port, cv_class)) |
| perms |= GST_PARAM_CONTROLLABLE; |
| |
| if (lilv_port_has_property (lv2plugin, port, toggled_prop)) { |
| ret = g_param_spec_boolean (name, nick, nick, FALSE, perms); |
| goto done; |
| } |
| |
| lilv_port_get_range (lv2plugin, port, &lv2def, &lv2min, &lv2max); |
| |
| if (lv2def) |
| def = lilv_node_as_float (lv2def); |
| if (lv2min) |
| lower = lilv_node_as_float (lv2min); |
| if (lv2max) |
| upper = lilv_node_as_float (lv2max); |
| |
| lilv_node_free (lv2def); |
| lilv_node_free (lv2min); |
| lilv_node_free (lv2max); |
| |
| if (def < lower) { |
| if (lv2def && lv2min) { |
| GST_WARNING ("%s:%s has lower bound %f > default %f", |
| lilv_node_as_string (lilv_plugin_get_uri (lv2plugin)), name, lower, |
| def); |
| } |
| lower = def; |
| } |
| |
| if (def > upper) { |
| if (lv2def && lv2max) { |
| GST_WARNING ("%s:%s has upper bound %f < default %f", |
| lilv_node_as_string (lilv_plugin_get_uri (lv2plugin)), name, upper, |
| def); |
| } |
| upper = def; |
| } |
| |
| if ((points = lilv_port_get_scale_points (lv2plugin, port))) { |
| GEnumValue *enums; |
| LilvIter *i; |
| gint j = 0, n, def_ix = -1; |
| |
| n = lilv_scale_points_size (points); |
| enums = g_new (GEnumValue, n + 1); |
| |
| for (i = lilv_scale_points_begin (points); |
| !lilv_scale_points_is_end (points, i); |
| i = lilv_scale_points_next (points, i)) { |
| const LilvScalePoint *point = lilv_scale_points_get (points, i); |
| gfloat v = lilv_node_as_float (lilv_scale_point_get_value (point)); |
| const gchar *l = lilv_node_as_string (lilv_scale_point_get_label (point)); |
| |
| /* check if value can be safely converted to int */ |
| if (v != (gint) v) { |
| GST_INFO ("%s:%s non integer scale point %lf, %s", |
| lilv_node_as_string (lilv_plugin_get_uri (lv2plugin)), name, v, l); |
| break; |
| } |
| if (v == def) { |
| def_ix = j; |
| } |
| enums[j].value = (gint) v; |
| enums[j].value_nick = enums[j].value_name = l; |
| GST_LOG ("%s:%s enum: %lf, %s", |
| lilv_node_as_string (lilv_plugin_get_uri (lv2plugin)), name, v, l); |
| j++; |
| } |
| if (j == n) { |
| gchar *type_name; |
| |
| /* scalepoints are not sorted */ |
| qsort (enums, n, sizeof (GEnumValue), |
| (int (*)(const void *, const void *)) enum_val_cmp); |
| |
| if (def_ix == -1) { |
| if (lv2def) { |
| GST_WARNING ("%s:%s has default %f outside of scalepoints", |
| lilv_node_as_string (lilv_plugin_get_uri (lv2plugin)), name, def); |
| } |
| def = enums[0].value; |
| } |
| /* terminator */ |
| enums[j].value = 0; |
| enums[j].value_name = enums[j].value_nick = NULL; |
| |
| type_name = g_strdup_printf ("%s%s", |
| g_type_name (G_TYPE_FROM_CLASS (object_class)), name); |
| enum_type = g_enum_register_static (type_name, enums); |
| g_free (type_name); |
| } else { |
| g_free (enums); |
| } |
| lilv_scale_points_free (points); |
| } |
| |
| if (enum_type != G_TYPE_INVALID) { |
| ret = g_param_spec_enum (name, nick, nick, enum_type, def, perms); |
| } else if (lilv_port_has_property (lv2plugin, port, integer_prop)) |
| ret = g_param_spec_int (name, nick, nick, lower, upper, def, perms); |
| else |
| 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); |
| |
| return ret; |
| } |
| |
| void |
| gst_lv2_class_install_properties (GstLV2Class * lv2_class, |
| GObjectClass * object_class, guint offset) |
| { |
| GParamSpec *p; |
| guint i; |
| |
| lv2_class->properties = offset; |
| |
| for (i = 0; i < lv2_class->control_in_ports->len; i++, offset++) { |
| p = gst_lv2_class_get_param_spec (lv2_class, object_class, |
| g_array_index (lv2_class->control_in_ports, GstLV2Port, i).index); |
| |
| g_object_class_install_property (object_class, offset, p); |
| } |
| |
| for (i = 0; i < lv2_class->control_out_ports->len; i++, offset++) { |
| p = gst_lv2_class_get_param_spec (lv2_class, object_class, |
| g_array_index (lv2_class->control_out_ports, GstLV2Port, i).index); |
| |
| g_object_class_install_property (object_class, offset, p); |
| } |
| } |
| |
| void |
| gst_lv2_element_class_set_metadata (GstLV2Class * lv2_class, |
| GstElementClass * elem_class, const gchar * lv2_class_tags) |
| { |
| const LilvPlugin *lv2plugin = lv2_class->plugin; |
| LilvNode *val; |
| const LilvPluginClass *lv2plugin_class; |
| const LilvNode *cval; |
| gchar *longname, *author, *class_tags = NULL; |
| |
| val = lilv_plugin_get_name (lv2plugin); |
| if (val) { |
| longname = g_strdup (lilv_node_as_string (val)); |
| lilv_node_free (val); |
| } else { |
| longname = g_strdup ("no description available"); |
| } |
| val = lilv_plugin_get_author_name (lv2plugin); |
| if (val) { |
| // TODO: check lilv_plugin_get_author_email(lv2plugin); |
| author = g_strdup (lilv_node_as_string (val)); |
| lilv_node_free (val); |
| } else { |
| author = g_strdup ("no author available"); |
| } |
| |
| // TODO: better description from: |
| // lilv_plugin_get_author_homepage() and lilv_plugin_get_project() |
| |
| lv2plugin_class = lilv_plugin_get_class (lv2plugin); |
| cval = lilv_plugin_class_get_label (lv2plugin_class); |
| if (cval) { |
| class_tags = g_strconcat (lv2_class_tags, "/", lilv_node_as_string (cval), |
| NULL); |
| } |
| |
| gst_element_class_set_metadata (elem_class, longname, |
| (class_tags ? class_tags : lv2_class_tags), longname, author); |
| g_free (longname); |
| g_free (author); |
| g_free (class_tags); |
| } |
| |
| |
| void |
| gst_lv2_class_init (GstLV2Class * lv2_class, GType type) |
| { |
| const GValue *value = |
| gst_structure_get_value (lv2_meta_all, g_type_name (type)); |
| GstStructure *lv2_meta = g_value_get_boxed (value); |
| const LilvPlugin *lv2plugin; |
| guint j, in_pad_index = 0, out_pad_index = 0; |
| const LilvPlugins *plugins = lilv_world_get_all_plugins (world); |
| LilvNode *plugin_uri; |
| const gchar *element_uri; |
| |
| GST_DEBUG ("LV2 initializing class"); |
| |
| element_uri = gst_structure_get_string (lv2_meta, "element-uri"); |
| plugin_uri = lilv_new_uri (world, element_uri); |
| g_assert (plugin_uri); |
| lv2plugin = lilv_plugins_get_by_uri (plugins, plugin_uri); |
| g_assert (lv2plugin); |
| 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)); |
| lv2_class->control_out_ports = g_array_new (FALSE, TRUE, sizeof (GstLV2Port)); |
| |
| /* find ports and groups */ |
| for (j = 0; j < lilv_plugin_get_num_ports (lv2plugin); j++) { |
| const LilvPort *port = lilv_plugin_get_port_by_index (lv2plugin, j); |
| const gboolean is_input = lilv_port_is_a (lv2plugin, port, input_class); |
| const gboolean is_optional = lilv_port_has_property (lv2plugin, port, |
| optional_pred); |
| GstLV2Port desc = { j, GST_LV2_PORT_AUDIO, -1, }; |
| LilvNodes *lv2group = lilv_port_get (lv2plugin, port, group_pred); |
| /* FIXME Handle channels positionning |
| * GstAudioChannelPosition position = GST_AUDIO_CHANNEL_POSITION_INVALID; */ |
| |
| if (lv2group) { |
| /* port is part of a group */ |
| const gchar *group_uri = lilv_node_as_uri (lv2group); |
| GstLV2Group *group = is_input |
| ? &lv2_class->in_group : &lv2_class->out_group; |
| |
| if (group->uri == NULL) { |
| group->uri = g_strdup (group_uri); |
| group->pad = is_input ? in_pad_index++ : out_pad_index++; |
| group->ports = g_array_new (FALSE, TRUE, sizeof (GstLV2Port)); |
| } |
| |
| /* FIXME Handle channels positionning |
| position = GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT; |
| sub_values = lilv_port_get_value (lv2plugin, port, designation_pred); |
| if (lilv_nodes_size (sub_values) > 0) { |
| LilvNode *role = lilv_nodes_get_at (sub_values, 0); |
| position = gst_lv2_filter_role_to_position (role); |
| } |
| lilv_nodes_free (sub_values); |
| |
| if (position != GST_AUDIO_CHANNEL_POSITION_INVALID) { |
| desc.position = position; |
| } */ |
| |
| g_array_append_val (group->ports, desc); |
| } else { |
| /* port is not part of a group, or it is part of a group but that group |
| * is illegal so we just ignore it */ |
| if (lilv_port_is_a (lv2plugin, port, audio_class)) { |
| if (is_input) { |
| desc.pad = in_pad_index++; |
| g_array_append_val (lv2_class->in_group.ports, desc); |
| } else { |
| desc.pad = out_pad_index++; |
| g_array_append_val (lv2_class->out_group.ports, desc); |
| } |
| } else if (lilv_port_is_a (lv2plugin, port, control_class)) { |
| desc.type = GST_LV2_PORT_CONTROL; |
| if (is_input) { |
| lv2_class->num_control_in++; |
| g_array_append_val (lv2_class->control_in_ports, desc); |
| } else { |
| lv2_class->num_control_out++; |
| g_array_append_val (lv2_class->control_out_ports, desc); |
| } |
| } else if (lilv_port_is_a (lv2plugin, port, cv_class)) { |
| desc.type = GST_LV2_PORT_CV; |
| if (is_input) { |
| lv2_class->num_cv_in++; |
| g_array_append_val (lv2_class->control_in_ports, desc); |
| } else { |
| lv2_class->num_cv_out++; |
| g_array_append_val (lv2_class->control_out_ports, desc); |
| } |
| } else if (lilv_port_is_a (lv2plugin, port, event_class)) { |
| LilvNodes *supported = lilv_port_get_value (lv2plugin, port, |
| supports_event_pred); |
| |
| GST_INFO ("%s: unhandled event port %d: %s, optional=%d, input=%d", |
| element_uri, j, |
| lilv_node_as_string (lilv_port_get_symbol (lv2plugin, port)), |
| is_optional, is_input); |
| |
| if (lilv_nodes_size (supported) > 0) { |
| LilvIter *i; |
| |
| for (i = lilv_nodes_begin (supported); |
| !lilv_nodes_is_end (supported, i); |
| i = lilv_nodes_next (supported, i)) { |
| const LilvNode *value = lilv_nodes_get (supported, i); |
| GST_INFO (" type = %s", lilv_node_as_uri (value)); |
| } |
| } |
| lilv_nodes_free (supported); |
| // FIXME: handle them |
| } else { |
| /* unhandled port type */ |
| const LilvNodes *classes = lilv_port_get_classes (lv2plugin, port); |
| GST_INFO ("%s: unhandled port %d: %s, optional=%d, input=%d", |
| element_uri, j, |
| lilv_node_as_string (lilv_port_get_symbol (lv2plugin, port)), |
| is_optional, is_input); |
| if (classes && lilv_nodes_size (classes) > 0) { |
| LilvIter *i; |
| |
| // FIXME: we getting the same classe multiple times |
| for (i = lilv_nodes_begin (classes); |
| !lilv_nodes_is_end (classes, i); |
| i = lilv_nodes_next (classes, i)) { |
| const LilvNode *value = lilv_nodes_get (classes, i); |
| GST_INFO (" class = %s", lilv_node_as_uri (value)); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| void |
| 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); |
| lv2_class->out_group.ports = NULL; |
| g_array_free (lv2_class->control_in_ports, TRUE); |
| lv2_class->control_in_ports = NULL; |
| g_array_free (lv2_class->control_out_ports, TRUE); |
| lv2_class->control_out_ports = NULL; |
| } |