* Copyright (C) 1999 Erik Walthinsen <omega@cse.ogi.edu>
* 2001 Steve Baker <stevebaker_org@yahoo.co.uk>
* 2003 Andy Wingo <wingo at pobox.com>
+ * 2016 Thibault Saunier <thibault.saunier@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
-#include <string.h>
-#include <math.h>
-#include <glib.h>
-#include <gst/audio/audio.h>
-#include <gst/audio/multichannel.h>
+#include <string.h>
#include "gstlv2.h"
-#include <slv2/slv2.h>
+
+#include <gst/audio/audio-channels.h>
+#include <lv2/lv2plug.in/ns/ext/port-groups/port-groups.h>
+
+GST_DEBUG_CATEGORY (lv2_debug);
+#define GST_CAT_DEFAULT lv2_debug
#define GST_LV2_DEFAULT_PATH \
"/usr/lib/lv2" G_SEARCHPATH_SEPARATOR_S \
"/usr/local/lib/lv2" G_SEARCHPATH_SEPARATOR_S \
LIBDIR "/lv2"
-static void gst_lv2_set_property (GObject * object,
- guint prop_id, const GValue * value, GParamSpec * pspec);
-
-static void gst_lv2_get_property (GObject * object,
- guint prop_id, GValue * value, GParamSpec * pspec);
-
-static gboolean gst_lv2_setup (GstSignalProcessor * sigproc, GstCaps * caps);
-static gboolean gst_lv2_start (GstSignalProcessor * sigproc);
-static void gst_lv2_stop (GstSignalProcessor * sigproc);
-static void gst_lv2_cleanup (GstSignalProcessor * sigproc);
-static void gst_lv2_process (GstSignalProcessor * sigproc, guint nframes);
-
-static SLV2World world;
-static SLV2Value audio_class;
-static SLV2Value control_class;
-static SLV2Value input_class;
-static SLV2Value output_class;
-static SLV2Value integer_prop;
-static SLV2Value toggled_prop;
-static SLV2Value in_place_broken_pred;
-static SLV2Value in_group_pred;
-static SLV2Value has_role_pred;
-static SLV2Value lv2_symbol_pred;
-
-static SLV2Value center_role;
-static SLV2Value left_role;
-static SLV2Value right_role;
-static SLV2Value rear_center_role;
-static SLV2Value rear_left_role;
-static SLV2Value rear_right_role;
-static SLV2Value lfe_role;
-static SLV2Value center_left_role;
-static SLV2Value center_right_role;
-static SLV2Value side_left_role;
-static SLV2Value side_right_role;
-
-static GstSignalProcessorClass *parent_class;
-
-static GstPlugin *gst_lv2_plugin;
-
-GST_DEBUG_CATEGORY_STATIC (lv2_debug);
-#define GST_CAT_DEFAULT lv2_debug
-
-static GQuark descriptor_quark = 0;
-
-
-/* Convert an LV2 port role to a Gst channel positon
- * WARNING: If the group has only a single port,
- * GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER will be returned for pg:centerRole
- * (which is used by LV2 for mono groups), but this is not correct. In this
- * case the value must be changed to GST_AUDIO_CHANNEL_POSITION_FRONT_MONO
- * (this can't be done by this function because this information isn't known
- * at the time it is used).
- */
-static GstAudioChannelPosition
-gst_lv2_role_to_position (SLV2Value role)
-{
- /* Front. Mono and left/right are mututally exclusive */
- if (slv2_value_equals (role, center_role)) {
-
- return GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER;
- } else if (slv2_value_equals (role, left_role)) {
- return GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT;
- } else if (slv2_value_equals (role, right_role)) {
- return GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT;
-
- /* Rear. Left/right and center are mututally exclusive */
- } else if (slv2_value_equals (role, rear_center_role)) {
- return GST_AUDIO_CHANNEL_POSITION_REAR_CENTER;
- } else if (slv2_value_equals (role, rear_left_role)) {
- return GST_AUDIO_CHANNEL_POSITION_REAR_LEFT;
- } else if (slv2_value_equals (role, rear_right_role)) {
- return GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT;
-
- /* Subwoofer/low-frequency-effects */
- } else if (slv2_value_equals (role, lfe_role)) {
- return GST_AUDIO_CHANNEL_POSITION_LFE;
-
- /* Center front speakers. Center and left/right_of_center
- * are mutually exclusive */
- } else if (slv2_value_equals (role, center_left_role)) {
- return GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
- } else if (slv2_value_equals (role, center_right_role)) {
- return GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
-
- /* sides */
- } else if (slv2_value_equals (role, side_left_role)) {
- return GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT;
- } else if (slv2_value_equals (role, side_right_role)) {
- return GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT;
- }
-
- return GST_AUDIO_CHANNEL_POSITION_INVALID;
-}
-
-/* Find and return the group @a uri in @a groups, or NULL if not found */
-static GstLV2Group *
-gst_lv2_class_find_group (GArray * groups, SLV2Value uri)
-{
- int i = 0;
- for (; i < groups->len; ++i)
- if (slv2_value_equals (g_array_index (groups, GstLV2Group, i).uri, uri))
- return &g_array_index (groups, GstLV2Group, i);
- return NULL;
-}
-
-static GstAudioChannelPosition *
-gst_lv2_build_positions (GstLV2Group * group)
-{
- GstAudioChannelPosition *positions = NULL;
-
- /* don't do anything for mono */
- if (group->ports->len > 1) {
- gint i;
-
- positions = g_new (GstAudioChannelPosition, group->ports->len);
- for (i = 0; i < group->ports->len; ++i)
- positions[i] = g_array_index (group->ports, GstLV2Port, i).position;
- }
- return positions;
-}
-
-static void
-gst_lv2_base_init (gpointer g_class)
-{
- GstLV2Class *klass = (GstLV2Class *) g_class;
- GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
- GstSignalProcessorClass *gsp_class = GST_SIGNAL_PROCESSOR_CLASS (g_class);
- SLV2Plugin lv2plugin;
- SLV2Value val;
- SLV2Values values, sub_values;
- GstLV2Group *group = NULL;
- GstAudioChannelPosition position = GST_AUDIO_CHANNEL_POSITION_INVALID;
- guint j, in_pad_index = 0, out_pad_index = 0;
- const gchar *klass_tags;
- gchar *longname, *author;
-
- lv2plugin = (SLV2Plugin) g_type_get_qdata (G_OBJECT_CLASS_TYPE (klass),
- descriptor_quark);
-
- g_assert (lv2plugin);
-
- GST_DEBUG ("base_init %p, plugin %s", g_class,
- slv2_value_as_string (slv2_plugin_get_uri (lv2plugin)));
-
- gsp_class->num_group_in = 0;
- gsp_class->num_group_out = 0;
- gsp_class->num_audio_in = 0;
- gsp_class->num_audio_out = 0;
- gsp_class->num_control_in = 0;
- gsp_class->num_control_out = 0;
-
- klass->in_groups = g_array_new (FALSE, TRUE, sizeof (GstLV2Group));
- klass->out_groups = g_array_new (FALSE, TRUE, sizeof (GstLV2Group));
- klass->audio_in_ports = g_array_new (FALSE, TRUE, sizeof (GstLV2Port));
- klass->audio_out_ports = g_array_new (FALSE, TRUE, sizeof (GstLV2Port));
- klass->control_in_ports = g_array_new (FALSE, TRUE, sizeof (GstLV2Port));
- klass->control_out_ports = g_array_new (FALSE, TRUE, sizeof (GstLV2Port));
-
- /* find ports and groups */
- for (j = 0; j < slv2_plugin_get_num_ports (lv2plugin); j++) {
- const SLV2Port port = slv2_plugin_get_port_by_index (lv2plugin, j);
- const gboolean is_input = slv2_port_is_a (lv2plugin, port, input_class);
- gboolean in_group = FALSE;
- struct _GstLV2Port desc = { j, 0, };
- values = slv2_port_get_value (lv2plugin, port, in_group_pred);
-
- if (slv2_values_size (values) > 0) {
- /* port is part of a group */
- SLV2Value group_uri = slv2_values_get_at (values, 0);
- GArray *groups = is_input ? klass->in_groups : klass->out_groups;
- GstLV2Group *group = gst_lv2_class_find_group (groups, group_uri);
- in_group = TRUE;
- if (group == NULL) {
- GstLV2Group g;
- g.uri = slv2_value_duplicate (group_uri);
- g.pad = is_input ? in_pad_index++ : out_pad_index++;
- g.ports = g_array_new (FALSE, TRUE, sizeof (GstLV2Port));
- g.has_roles = TRUE;
- g.symbol = NULL;
- sub_values = slv2_plugin_get_value_for_subject (lv2plugin, group_uri,
- lv2_symbol_pred);
- /* symbol is mandatory */
- if (slv2_values_size (sub_values) > 0) {
- g.symbol = slv2_value_duplicate (slv2_values_get_at (sub_values, 0));
- if (!gst_element_class_get_pad_template (element_class,
- slv2_value_as_string (g.symbol))) {
- g_array_append_val (groups, g);
- group = &g_array_index (groups, GstLV2Group, groups->len - 1);
- assert (group);
- assert (slv2_value_equals (group->uri, group_uri));
- } else {
- GST_WARNING ("plugin %s has duplicate group symbol '%s'",
- slv2_value_as_string (slv2_plugin_get_uri (lv2plugin)),
- slv2_value_as_string (g.symbol));
- in_group = FALSE;
- }
- } else {
- GST_WARNING ("plugin %s has illegal group with no symbol",
- slv2_value_as_string (slv2_plugin_get_uri (lv2plugin)));
- in_group = FALSE;
- }
- }
-
- if (in_group) {
- position = GST_AUDIO_CHANNEL_POSITION_INVALID;
- sub_values = slv2_port_get_value (lv2plugin, port, has_role_pred);
- if (slv2_values_size (sub_values) > 0) {
- SLV2Value role = slv2_values_get_at (sub_values, 0);
- position = gst_lv2_role_to_position (role);
- }
- slv2_values_free (sub_values);
- if (position != GST_AUDIO_CHANNEL_POSITION_INVALID) {
- desc.position = position;
- g_array_append_val (group->ports, desc);
- } else {
- in_group = FALSE;
- }
- }
- }
-
- if (!in_group) {
- /* 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 (slv2_port_is_a (lv2plugin, port, audio_class)) {
- desc.pad = is_input ? in_pad_index++ : out_pad_index++;
- if (is_input)
- g_array_append_val (klass->audio_in_ports, desc);
- else
- g_array_append_val (klass->audio_out_ports, desc);
- } else if (slv2_port_is_a (lv2plugin, port, control_class)) {
- if (is_input)
- g_array_append_val (klass->control_in_ports, desc);
- else
- g_array_append_val (klass->control_out_ports, desc);
- } else {
- /* unknown port type */
- GST_INFO ("unhandled port %d", j);
- continue;
- }
- }
- slv2_values_free (values);
- }
-
- gsp_class->num_group_in = klass->in_groups->len;
- gsp_class->num_group_out = klass->out_groups->len;
- gsp_class->num_audio_in = klass->audio_in_ports->len;
- gsp_class->num_audio_out = klass->audio_out_ports->len;
- gsp_class->num_control_in = klass->control_in_ports->len;
- gsp_class->num_control_out = klass->control_out_ports->len;
-
- /* add input group pad templates */
- for (j = 0; j < gsp_class->num_group_in; ++j) {
- group = &g_array_index (klass->in_groups, GstLV2Group, j);
-
- gst_signal_processor_class_add_pad_template (gsp_class,
- slv2_value_as_string (group->symbol), GST_PAD_SINK, j,
- group->ports->len);
- }
-
- /* add output group pad templates */
- for (j = 0; j < gsp_class->num_group_out; ++j) {
- group = &g_array_index (klass->out_groups, GstLV2Group, j);
-
- gst_signal_processor_class_add_pad_template (gsp_class,
- slv2_value_as_string (group->symbol), GST_PAD_SRC, j,
- group->ports->len);
- }
-
- /* add non-grouped input port pad templates */
- for (j = 0; j < gsp_class->num_audio_in; ++j) {
- struct _GstLV2Port *desc =
- &g_array_index (klass->audio_in_ports, GstLV2Port, j);
- SLV2Port port = slv2_plugin_get_port_by_index (lv2plugin, desc->index);
- const gchar *name =
- slv2_value_as_string (slv2_port_get_symbol (lv2plugin, port));
- gst_signal_processor_class_add_pad_template (gsp_class, name, GST_PAD_SINK,
- j, 1);
- }
-
- /* add non-grouped output port pad templates */
- for (j = 0; j < gsp_class->num_audio_out; ++j) {
- struct _GstLV2Port *desc =
- &g_array_index (klass->audio_out_ports, GstLV2Port, j);
- SLV2Port port = slv2_plugin_get_port_by_index (lv2plugin, desc->index);
- const gchar *name =
- slv2_value_as_string (slv2_port_get_symbol (lv2plugin, port));
- gst_signal_processor_class_add_pad_template (gsp_class, name, GST_PAD_SRC,
- j, 1);
- }
-
- val = slv2_plugin_get_name (lv2plugin);
- if (val) {
- longname = g_strdup (slv2_value_as_string (val));
- slv2_value_free (val);
- } else {
- longname = g_strdup ("no description available");
- }
- val = slv2_plugin_get_author_name (lv2plugin);
- if (val) {
- author = g_strdup (slv2_value_as_string (val));
- slv2_value_free (val);
- } else {
- author = g_strdup ("no author available");
- }
-
- if (gsp_class->num_audio_in == 0)
- klass_tags = "Source/Audio/LV2";
- else if (gsp_class->num_audio_out == 0) {
- if (gsp_class->num_control_out == 0)
- klass_tags = "Sink/Audio/LV2";
- else
- klass_tags = "Sink/Analyzer/Audio/LV2";
- } else
- klass_tags = "Filter/Effect/Audio/LV2";
-
- GST_INFO ("tags : %s", klass_tags);
- gst_element_class_set_metadata (element_class, longname,
- klass_tags, longname, author);
- g_free (longname);
- g_free (author);
-
- if (!slv2_plugin_has_feature (lv2plugin, in_place_broken_pred))
- GST_SIGNAL_PROCESSOR_CLASS_SET_CAN_PROCESS_IN_PLACE (klass);
-
- klass->plugin = lv2plugin;
-}
-
-static gchar *
-gst_lv2_class_get_param_name (GstLV2Class * klass, SLV2Port port)
-{
- SLV2Plugin lv2plugin = klass->plugin;
- gchar *ret;
-
- ret = g_strdup (slv2_value_as_string (slv2_port_get_symbol (lv2plugin,
- port)));
-
- /* 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 (G_OBJECT_CLASS (klass), ret)) {
- gint n = 1;
- gchar *nret = g_strdup_printf ("%s-%d", ret, n++);
-
- while (g_object_class_find_property (G_OBJECT_CLASS (klass), 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,
- slv2_value_as_string (slv2_port_get_symbol (lv2plugin, port)));
-
- return ret;
-}
-
-static gchar *
-gst_lv2_class_get_param_nick (GstLV2Class * klass, SLV2Port port)
-{
- SLV2Plugin lv2plugin = klass->plugin;
-
- return g_strdup (slv2_value_as_string (slv2_port_get_name (lv2plugin, port)));
-}
-
-static GParamSpec *
-gst_lv2_class_get_param_spec (GstLV2Class * klass, gint portnum)
-{
- SLV2Plugin lv2plugin = klass->plugin;
- SLV2Port port = slv2_plugin_get_port_by_index (lv2plugin, portnum);
- SLV2Value lv2def, lv2min, lv2max;
- GParamSpec *ret;
- gchar *name, *nick;
- gint perms;
- gfloat lower = 0.0f, upper = 1.0f, def = 0.0f;
-
- nick = gst_lv2_class_get_param_nick (klass, port);
- name = gst_lv2_class_get_param_name (klass, port);
-
- GST_DEBUG ("%s trying port %s : %s",
- slv2_value_as_string (slv2_plugin_get_uri (lv2plugin)), name, nick);
-
- perms = G_PARAM_READABLE;
- if (slv2_port_is_a (lv2plugin, port, input_class))
- perms |= G_PARAM_WRITABLE | G_PARAM_CONSTRUCT;
- if (slv2_port_is_a (lv2plugin, port, control_class))
- perms |= GST_PARAM_CONTROLLABLE;
-
- if (slv2_port_has_property (lv2plugin, port, toggled_prop)) {
- ret = g_param_spec_boolean (name, nick, nick, FALSE, perms);
- goto done;
- }
-
- slv2_port_get_range (lv2plugin, port, &lv2def, &lv2min, &lv2max);
-
- if (lv2def)
- def = slv2_value_as_float (lv2def);
- if (lv2min)
- lower = slv2_value_as_float (lv2min);
- if (lv2max)
- upper = slv2_value_as_float (lv2max);
-
- slv2_value_free (lv2def);
- slv2_value_free (lv2min);
- slv2_value_free (lv2max);
-
- if (def < lower) {
- GST_WARNING ("%s has lower bound %f > default %f",
- slv2_value_as_string (slv2_plugin_get_uri (lv2plugin)), lower, def);
- lower = def;
- }
-
- if (def > upper) {
- GST_WARNING ("%s has upper bound %f < default %f",
- slv2_value_as_string (slv2_plugin_get_uri (lv2plugin)), upper, def);
- upper = def;
- }
-
- if (slv2_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:
- g_free (name);
- g_free (nick);
-
- return ret;
-}
-
-static void
-gst_lv2_class_init (GstLV2Class * klass, SLV2Plugin lv2plugin)
-{
- GObjectClass *gobject_class;
- GstSignalProcessorClass *gsp_class;
- GParamSpec *p;
- gint i, ix;
-
- GST_DEBUG ("class_init %p", klass);
-
- gobject_class = (GObjectClass *) klass;
- gobject_class->set_property = gst_lv2_set_property;
- gobject_class->get_property = gst_lv2_get_property;
-
- gsp_class = GST_SIGNAL_PROCESSOR_CLASS (klass);
- gsp_class->setup = gst_lv2_setup;
- gsp_class->start = gst_lv2_start;
- gsp_class->stop = gst_lv2_stop;
- gsp_class->cleanup = gst_lv2_cleanup;
- gsp_class->process = gst_lv2_process;
-
- klass->plugin = lv2plugin;
-
- /* properties have an offset of 1 */
- ix = 1;
-
- /* register properties */
-
- for (i = 0; i < gsp_class->num_control_in; i++, ix++) {
- p = gst_lv2_class_get_param_spec (klass,
- g_array_index (klass->control_in_ports, GstLV2Port, i).index);
-
- g_object_class_install_property (gobject_class, ix, p);
- }
-
- for (i = 0; i < gsp_class->num_control_out; i++, ix++) {
- p = gst_lv2_class_get_param_spec (klass,
- g_array_index (klass->control_out_ports, GstLV2Port, i).index);
-
- g_object_class_install_property (gobject_class, ix, p);
- }
-}
-
-static void
-gst_lv2_init (GstLV2 * lv2, GstLV2Class * klass)
-{
- lv2->plugin = klass->plugin;
- lv2->instance = NULL;
- lv2->activated = FALSE;
-}
-
-static void
-gst_lv2_set_property (GObject * object, guint prop_id, const GValue * value,
- GParamSpec * pspec)
-{
- GstSignalProcessor *gsp;
- GstSignalProcessorClass *gsp_class;
-
- gsp = GST_SIGNAL_PROCESSOR (object);
- gsp_class = GST_SIGNAL_PROCESSOR_GET_CLASS (object);
-
- /* remember, properties have an offset of 1 */
- prop_id--;
-
- /* only input ports */
- g_return_if_fail (prop_id < gsp_class->num_control_in);
-
- /* now see what type it is */
- switch (pspec->value_type) {
- case G_TYPE_BOOLEAN:
- gsp->control_in[prop_id] = g_value_get_boolean (value) ? 0.0f : 1.0f;
- break;
- case G_TYPE_INT:
- gsp->control_in[prop_id] = g_value_get_int (value);
- break;
- case G_TYPE_FLOAT:
- gsp->control_in[prop_id] = g_value_get_float (value);
- break;
- default:
- g_assert_not_reached ();
- }
-}
-
-static void
-gst_lv2_get_property (GObject * object, guint prop_id, GValue * value,
- GParamSpec * pspec)
-{
- GstSignalProcessor *gsp;
- GstSignalProcessorClass *gsp_class;
- gfloat *controls;
-
- gsp = GST_SIGNAL_PROCESSOR (object);
- gsp_class = GST_SIGNAL_PROCESSOR_GET_CLASS (object);
-
- /* remember, properties have an offset of 1 */
- prop_id--;
-
- if (prop_id < gsp_class->num_control_in) {
- controls = gsp->control_in;
- } else if (prop_id < gsp_class->num_control_in + gsp_class->num_control_out) {
- controls = gsp->control_out;
- prop_id -= gsp_class->num_control_in;
- } else {
- g_return_if_reached ();
- }
-
- /* now see what type it is */
- switch (pspec->value_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;
- default:
- g_return_if_reached ();
- }
-}
-
-static gboolean
-gst_lv2_setup (GstSignalProcessor * gsp, GstCaps * caps)
-{
- GstLV2 *lv2;
- GstLV2Class *oclass;
- GstSignalProcessorClass *gsp_class;
- GstStructure *s;
- gint i;
- GstLV2Group *group = NULL;
- GstAudioChannelPosition *positions = NULL;
- GstPad *pad;
-
- gsp_class = GST_SIGNAL_PROCESSOR_GET_CLASS (gsp);
- lv2 = (GstLV2 *) gsp;
- oclass = (GstLV2Class *) gsp_class;
-
- g_return_val_if_fail (lv2->activated == FALSE, FALSE);
-
- GST_DEBUG_OBJECT (lv2, "instantiating the plugin at %d Hz", gsp->sample_rate);
-
- if (!(lv2->instance =
- slv2_plugin_instantiate (oclass->plugin, gsp->sample_rate, NULL)))
- goto no_instance;
-
- /* connect the control ports */
- for (i = 0; i < gsp_class->num_control_in; i++)
- slv2_instance_connect_port (lv2->instance,
- g_array_index (oclass->control_in_ports, GstLV2Port, i).index,
- &(gsp->control_in[i]));
- for (i = 0; i < gsp_class->num_control_out; i++)
- slv2_instance_connect_port (lv2->instance,
- g_array_index (oclass->control_out_ports, GstLV2Port, i).index,
- &(gsp->control_out[i]));
-
- /* set input group pad audio channel position */
- for (i = 0; i < gsp_class->num_group_in; ++i) {
- group = &g_array_index (oclass->in_groups, GstLV2Group, i);
- if (group->has_roles) {
- if ((positions = gst_lv2_build_positions (group))) {
- if ((pad = gst_element_get_static_pad (GST_ELEMENT (gsp),
- slv2_value_as_string (group->symbol)))) {
- GST_INFO_OBJECT (lv2, "set audio channel positions on sink pad %s",
- slv2_value_as_string (group->symbol));
- s = gst_caps_get_structure (caps, 0);
- gst_audio_set_channel_positions (s, positions);
- gst_object_unref (pad);
- }
- g_free (positions);
- positions = NULL;
- }
- }
- }
- /* set output group pad audio channel position */
- for (i = 0; i < gsp_class->num_group_out; ++i) {
- group = &g_array_index (oclass->out_groups, GstLV2Group, i);
- if (group->has_roles) {
- if ((positions = gst_lv2_build_positions (group))) {
- if ((pad = gst_element_get_static_pad (GST_ELEMENT (gsp),
- slv2_value_as_string (group->symbol)))) {
- GST_INFO_OBJECT (lv2, "set audio channel positions on src pad %s",
- slv2_value_as_string (group->symbol));
- s = gst_caps_get_structure (caps, 0);
- gst_audio_set_channel_positions (s, positions);
- gst_object_unref (pad);
- }
- g_free (positions);
- positions = NULL;
- }
- }
- }
- return TRUE;
-
-no_instance:
- {
- GST_WARNING_OBJECT (gsp, "could not create instance");
- return FALSE;
- }
-}
-
-static gboolean
-gst_lv2_start (GstSignalProcessor * gsp)
-{
- GstLV2 *lv2 = (GstLV2 *) gsp;
-
- g_return_val_if_fail (lv2->activated == FALSE, FALSE);
- g_return_val_if_fail (lv2->instance != NULL, FALSE);
-
- GST_DEBUG_OBJECT (lv2, "activating");
-
- slv2_instance_activate (lv2->instance);
-
- lv2->activated = TRUE;
-
- return TRUE;
-}
-
-static void
-gst_lv2_stop (GstSignalProcessor * gsp)
-{
- GstLV2 *lv2 = (GstLV2 *) gsp;
-
- g_return_if_fail (lv2->activated == TRUE);
- g_return_if_fail (lv2->instance != NULL);
-
- GST_DEBUG_OBJECT (lv2, "deactivating");
-
- slv2_instance_deactivate (lv2->instance);
-
- lv2->activated = FALSE;
-}
-
-static void
-gst_lv2_cleanup (GstSignalProcessor * gsp)
-{
- GstLV2 *lv2 = (GstLV2 *) gsp;
-
- g_return_if_fail (lv2->activated == FALSE);
- g_return_if_fail (lv2->instance != NULL);
-
- GST_DEBUG_OBJECT (lv2, "cleaning up");
-
- slv2_instance_free (lv2->instance);
-
- lv2->instance = NULL;
-}
-
-static void
-gst_lv2_process (GstSignalProcessor * gsp, guint nframes)
-{
- GstSignalProcessorClass *gsp_class;
- GstLV2 *lv2;
- GstLV2Class *lv2_class;
- GstLV2Group *lv2_group;
- GstLV2Port *lv2_port;
- GstSignalProcessorGroup *gst_group;
- guint i, j;
-
- gsp_class = GST_SIGNAL_PROCESSOR_GET_CLASS (gsp);
- lv2 = (GstLV2 *) gsp;
- lv2_class = (GstLV2Class *) gsp_class;
-
- /* multi channel inputs */
- for (i = 0; i < gsp_class->num_group_in; i++) {
- lv2_group = &g_array_index (lv2_class->in_groups, GstLV2Group, i);
- gst_group = &gsp->group_in[i];
- for (j = 0; j < lv2_group->ports->len; ++j) {
- lv2_port = &g_array_index (lv2_group->ports, GstLV2Port, j);
- slv2_instance_connect_port (lv2->instance, lv2_port->index,
- gst_group->buffer + (j * nframes));
- }
- }
- /* mono inputs */
- for (i = 0; i < gsp_class->num_audio_in; i++) {
- lv2_port = &g_array_index (lv2_class->audio_in_ports, GstLV2Port, i);
- slv2_instance_connect_port (lv2->instance, lv2_port->index,
- gsp->audio_in[i]);
- }
- /* multi channel outputs */
- for (i = 0; i < gsp_class->num_group_out; i++) {
- lv2_group = &g_array_index (lv2_class->out_groups, GstLV2Group, i);
- gst_group = &gsp->group_out[i];
- for (j = 0; j < lv2_group->ports->len; ++j) {
- lv2_port = &g_array_index (lv2_group->ports, GstLV2Port, j);
- slv2_instance_connect_port (lv2->instance, lv2_port->index,
- gst_group->buffer + (j * nframes));
- }
- }
- /* mono outputs */
- for (i = 0; i < gsp_class->num_audio_out; i++) {
- lv2_port = &g_array_index (lv2_class->audio_out_ports, GstLV2Port, i);
- slv2_instance_connect_port (lv2->instance, lv2_port->index,
- gsp->audio_out[i]);
- }
-
- slv2_instance_run (lv2->instance, nframes);
-}
-
/* search the plugin path
*/
static gboolean
-lv2_plugin_discover (void)
+lv2_plugin_discover (GstPlugin * plugin)
{
- guint i, j;
- SLV2Plugins plugins = slv2_world_get_all_plugins (world);
+ guint j, num_sink_pads, num_src_pads;
+ LilvIter *i;
+ const LilvPlugins *plugins = lilv_world_get_all_plugins (world);
- for (i = 0; i < slv2_plugins_size (plugins); ++i) {
- SLV2Plugin lv2plugin = slv2_plugins_get_at (plugins, i);
- gint num_audio_ports = 0;
+ for (i = lilv_plugins_begin (plugins); !lilv_plugins_is_end (plugins, i);
+ i = lilv_plugins_next (plugins, i)) {
+ const LilvPlugin *lv2plugin = lilv_plugins_get (plugins, i);
const gchar *plugin_uri, *p;
gchar *type_name;
- GTypeInfo typeinfo = {
- sizeof (GstLV2Class),
- (GBaseInitFunc) gst_lv2_base_init,
- NULL,
- (GClassInitFunc) gst_lv2_class_init,
- NULL,
- lv2plugin,
- sizeof (GstLV2),
- 0,
- (GInstanceInitFunc) gst_lv2_init,
- };
- GType type;
-
- plugin_uri = slv2_value_as_uri (slv2_plugin_get_uri (lv2plugin));
+ GHashTable *port_groups = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, NULL);
+
+ plugin_uri = lilv_node_as_uri (lilv_plugin_get_uri (lv2plugin));
/* construct the type name from plugin URI */
if ((p = strstr (plugin_uri, "://"))) {
/* cut off the protocol (e.g. http://) */
goto next;
/* check if this has any audio ports */
- for (j = 0; j < slv2_plugin_get_num_ports (lv2plugin); j++) {
- const SLV2Port port = slv2_plugin_get_port_by_index (lv2plugin, j);
- if (slv2_port_is_a (lv2plugin, port, audio_class)) {
- num_audio_ports++;
+ num_sink_pads = num_src_pads = 0;
+ 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);
+
+ if (lilv_port_is_a (lv2plugin, port, audio_class)) {
+ LilvNodes *lv2group = lilv_port_get (lv2plugin, port, group_pred);
+ if (lv2group) {
+ const gchar *uri = lilv_node_as_uri (lv2group);
+
+ if (g_hash_table_contains (port_groups, uri))
+ continue;
+
+ g_hash_table_add (port_groups, g_strdup (uri));
+ lilv_node_free (lv2group);
+ }
+
+
+ if (is_input)
+ num_sink_pads++;
+ else
+ num_src_pads++;
}
+
}
- if (!num_audio_ports) {
- GST_INFO ("plugin %s has no audio ports", type_name);
+ if (num_sink_pads != 1 || num_src_pads != 1) {
+ GST_FIXME ("plugin %s is not a GstAudioFilter (num_sink_pads: %d"
+ " num_src_pads: %d)", type_name, num_sink_pads, num_src_pads);
goto next;
}
- /* create the type */
- type =
- g_type_register_static (GST_TYPE_SIGNAL_PROCESSOR, type_name, &typeinfo,
- 0);
-
- /* FIXME: not needed anymore when we can add pad templates, etc in class_init
- * as class_data contains the LADSPA_Descriptor too */
- g_type_set_qdata (type, descriptor_quark, (gpointer) lv2plugin);
-
- if (!gst_element_register (gst_lv2_plugin, type_name, GST_RANK_NONE, type))
- goto next;
+ gst_lv2_filter_register_element (plugin, type_name, (gpointer) lv2plugin);
next:
g_free (type_name);
+ g_hash_table_unref (port_groups);
}
+
return TRUE;
}
GST_DEBUG_CATEGORY_INIT (lv2_debug, "lv2",
GST_DEBUG_FG_GREEN | GST_DEBUG_BG_BLACK | GST_DEBUG_BOLD, "LV2");
- world = slv2_world_new ();
- slv2_world_load_all (world);
+ world = lilv_world_new ();
+ lilv_world_load_all (world);
- audio_class = slv2_value_new_uri (world, SLV2_PORT_CLASS_AUDIO);
- control_class = slv2_value_new_uri (world, SLV2_PORT_CLASS_CONTROL);
- input_class = slv2_value_new_uri (world, SLV2_PORT_CLASS_INPUT);
- output_class = slv2_value_new_uri (world, SLV2_PORT_CLASS_OUTPUT);
+ audio_class = lilv_new_uri (world, LILV_URI_AUDIO_PORT);
+ control_class = lilv_new_uri (world, LILV_URI_CONTROL_PORT);
+ input_class = lilv_new_uri (world, LILV_URI_INPUT_PORT);
+ output_class = lilv_new_uri (world, LILV_URI_OUTPUT_PORT);
#define NS_LV2 "http://lv2plug.in/ns/lv2core#"
-#define NS_PG "http://lv2plug.in/ns/ext/port-groups"
-
- integer_prop = slv2_value_new_uri (world, NS_LV2 "integer");
- toggled_prop = slv2_value_new_uri (world, NS_LV2 "toggled");
- in_place_broken_pred = slv2_value_new_uri (world, NS_LV2 "inPlaceBroken");
- in_group_pred = slv2_value_new_uri (world, NS_PG "inGroup");
- has_role_pred = slv2_value_new_uri (world, NS_PG "role");
- lv2_symbol_pred = slv2_value_new_string (world, NS_LV2 "symbol");
-
- center_role = slv2_value_new_uri (world, NS_PG "centerChannel");
- left_role = slv2_value_new_uri (world, NS_PG "leftChannel");
- right_role = slv2_value_new_uri (world, NS_PG "rightChannel");
- rear_center_role = slv2_value_new_uri (world, NS_PG "rearCenterChannel");
- rear_left_role = slv2_value_new_uri (world, NS_PG "rearLeftChannel");
- rear_right_role = slv2_value_new_uri (world, NS_PG "rearRightChannel");
- lfe_role = slv2_value_new_uri (world, NS_PG "lfeChannel");
- center_left_role = slv2_value_new_uri (world, NS_PG "centerLeftChannel");
- center_right_role = slv2_value_new_uri (world, NS_PG "centerRightChannel");
- side_left_role = slv2_value_new_uri (world, NS_PG "sideLeftChannel");
- side_right_role = slv2_value_new_uri (world, NS_PG "sideRightChannel");
+#define NS_PG "http://lv2plug.in/ns/ext/port-groups#"
+
+ integer_prop = lilv_new_uri (world, NS_LV2 "integer");
+ toggled_prop = lilv_new_uri (world, NS_LV2 "toggled");
+ in_place_broken_pred = lilv_new_uri (world, NS_LV2 "inPlaceBroken");
+ group_pred = lilv_new_uri (world, LV2_PORT_GROUPS__group);
+ has_role_pred = lilv_new_uri (world, NS_PG "role");
+
+ /* FIXME Verify what should be used here */
+ lv2_symbol_pred = lilv_new_uri (world, LILV_NS_LV2 "symbol");
+
+ center_role = lilv_new_uri (world, LV2_PORT_GROUPS__center);
+ left_role = lilv_new_uri (world, LV2_PORT_GROUPS__left);
+ right_role = lilv_new_uri (world, LV2_PORT_GROUPS__right);
+ rear_center_role = lilv_new_uri (world, LV2_PORT_GROUPS__rearCenter);
+ rear_left_role = lilv_new_uri (world, LV2_PORT_GROUPS__rearLeft);
+ rear_right_role = lilv_new_uri (world, LV2_PORT_GROUPS__rearLeft);
+ lfe_role = lilv_new_uri (world, LV2_PORT_GROUPS__lowFrequencyEffects);
+ center_left_role = lilv_new_uri (world, LV2_PORT_GROUPS__centerLeft);
+ center_right_role = lilv_new_uri (world, LV2_PORT_GROUPS__centerRight);
+ side_left_role = lilv_new_uri (world, LV2_PORT_GROUPS__sideLeft);
+ side_right_role = lilv_new_uri (world, LV2_PORT_GROUPS__sideRight);
gst_plugin_add_dependency_simple (plugin,
"LV2_PATH", GST_LV2_DEFAULT_PATH, NULL, GST_PLUGIN_DEPENDENCY_FLAG_NONE);
- parent_class = g_type_class_ref (GST_TYPE_SIGNAL_PROCESSOR);
-
- gst_lv2_plugin = plugin;
- descriptor_quark = g_quark_from_static_string ("slv2-plugin");
+ descriptor_quark = g_quark_from_static_string ("lilv-plugin");
/* ensure GstAudioChannelPosition type is registered */
if (!gst_audio_channel_position_get_type ())
return FALSE;
- if (!lv2_plugin_discover ()) {
+ if (!lv2_plugin_discover (plugin)) {
GST_WARNING ("no lv2 plugins found, check LV2_PATH");
}
+
+
/* we don't want to fail, even if there are no elements registered */
return TRUE;
}
#endif
static void plugin_cleanup (GstPlugin * plugin)
{
- slv2_value_free (audio_class);
- slv2_value_free (control_class);
- slv2_value_free (input_class);
- slv2_value_free (output_class);
-
- slv2_value_free (integer_prop);
- slv2_value_free (toggled_prop);
- slv2_value_free (in_place_broken_pred);
- slv2_value_free (in_group_pred);
- slv2_value_free (has_role_pred);
- slv2_value_free (lv2_symbol_pred);
-
- slv2_value_free (center_role);
- slv2_value_free (left_role);
- slv2_value_free (right_role);
- slv2_value_free (rear_center_role);
- slv2_value_free (rear_left_role);
- slv2_value_free (rear_right_role);
- slv2_value_free (lfe_role);
- slv2_value_free (center_left_role);
- slv2_value_free (center_right_role);
- slv2_value_free (side_left_role);
- slv2_value_free (side_right_role);
-
- slv2_world_free (world);
+ lilv_node_free (audio_class);
+ lilv_node_free (control_class);
+ lilv_node_free (input_class);
+ lilv_node_free (output_class);
+
+ lilv_node_free (integer_prop);
+ lilv_node_free (toggled_prop);
+ lilv_node_free (in_place_broken_pred);
+ lilv_node_free (group_pred);
+ lilv_node_free (has_role_pred);
+ lilv_node_free (lv2_symbol_pred);
+
+ lilv_node_free (center_role);
+ lilv_node_free (left_role);
+ lilv_node_free (right_role);
+ lilv_node_free (rear_center_role);
+ lilv_node_free (rear_left_role);
+ lilv_node_free (rear_right_role);
+ lilv_node_free (lfe_role);
+ lilv_node_free (center_left_role);
+ lilv_node_free (center_right_role);
+ lilv_node_free (side_left_role);
+ lilv_node_free (side_right_role);
+
+ lilv_world_free (world);
}
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
--- /dev/null
+/* GStreamer
+ * Copyright (C) 1999 Erik Walthinsen <omega@cse.ogi.edu>
+ * 2001 Steve Baker <stevebaker_org@yahoo.co.uk>
+ * 2003 Andy Wingo <wingo at pobox.com>
+ * 2016 Thibault Saunier <thibault.saunier@collabora.com>
+ *
+ * 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.
+ */
+
+/**
+ * SECTION:element-lv2
+ * @short_description: bridge for LV2.
+ *
+ * LV2 is a standard for plugins and matching host applications,
+ * mainly targeted at audio processing and generation. It is intended as
+ * a successor to LADSPA (Linux Audio Developer's Simple Plugin API).
+ *
+ * The LV2 element is a bridge for plugins using the
+ * <ulink url="http://www.lv2plug.in/">LV2</ulink> API. It scans all
+ * installed LV2 plugins and registers them as gstreamer elements.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "gstlv2.h"
+
+#include <string.h>
+#include <math.h>
+#include <glib.h>
+
+#include <lilv/lilv.h>
+
+#include <gst/audio/audio.h>
+#include <gst/audio/gstaudiofilter.h>
+#include <gst/audio/audio-channels.h>
+
+GST_DEBUG_CATEGORY_EXTERN (lv2_debug);
+#define GST_CAT_DEFAULT lv2_debug
+
+typedef struct _lv2_control_info
+{
+ gchar *name;
+ gchar *param_name;
+ gfloat lowerbound, upperbound;
+ gfloat def;
+ gboolean lower, upper, samplerate;
+ gboolean toggled, logarithmic, integer, writable;
+} lv2_control_info;
+
+
+typedef struct _GstLV2Filter GstLV2Filter;
+typedef struct _GstLV2FilterClass GstLV2FilterClass;
+typedef struct _GstLV2FilterGroup GstLV2FilterGroup;
+typedef struct _GstLV2FilterPort GstLV2FilterPort;
+
+
+struct _GstLV2Filter
+{
+ GstAudioFilter parent;
+
+ LilvPlugin *plugin;
+ LilvInstance *instance;
+
+ gboolean activated;
+
+ /* TODO refactor in the same way as LADSPA plugin */
+ struct
+ {
+ struct
+ {
+ gfloat *in;
+ gfloat *out;
+ } control;
+ } ports;
+};
+
+struct _GstLV2FilterGroup
+{
+ gchar *uri; /**< RDF resource (URI or blank node) */
+ guint pad; /**< Gst pad index */
+ gchar *symbol; /**< Gst pad name / LV2 group symbol */
+ GArray *ports; /**< Array of GstLV2FilterPort */
+ gboolean has_roles; /**< TRUE iff all ports have a known role */
+};
+
+struct _GstLV2FilterPort
+{
+ gint index; /**< LV2 port index (on LV2 plugin) */
+ gint pad; /**< Gst pad index (iff not part of a group) */
+ LilvNode *role; /**< Channel position / port role */
+ GstAudioChannelPosition position; /**< Channel position */
+};
+
+struct _GstLV2FilterClass
+{
+ GstAudioFilterClass parent_class;
+
+ LilvPlugin *plugin;
+
+ GstLV2FilterGroup in_group; /**< Array of GstLV2FilterGroup */
+ GstLV2FilterGroup out_group; /**< Array of GstLV2FilterGroup */
+ GArray *control_in_ports; /**< Array of GstLV2FilterPort */
+ GArray *control_out_ports; /**< Array of GstLV2FilterPort */
+
+};
+
+static GstAudioFilter *parent_class = NULL;
+
+/* GObject vmethods implementation */
+static void
+gst_lv2_filter_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstLV2Filter *self = (GstLV2Filter *) (object);
+ GstLV2FilterClass *klass =
+ (GstLV2FilterClass *) GST_AUDIO_FILTER_GET_CLASS (object);
+
+ /* remember, properties have an offset of 1 */
+ prop_id--;
+
+ /* only input ports */
+ g_return_if_fail (prop_id < klass->control_in_ports->len);
+
+ /* now see what type it is */
+ switch (pspec->value_type) {
+ case G_TYPE_BOOLEAN:
+ self->ports.control.in[prop_id] =
+ g_value_get_boolean (value) ? 0.0f : 1.0f;
+ break;
+ case G_TYPE_INT:
+ self->ports.control.in[prop_id] = g_value_get_int (value);
+ break;
+ case G_TYPE_FLOAT:
+ self->ports.control.in[prop_id] = g_value_get_float (value);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+gst_lv2_filter_get_property (GObject * object, guint prop_id, GValue * value,
+ GParamSpec * pspec)
+{
+ GstLV2Filter *self = (GstLV2Filter *) (object);
+ GstLV2FilterClass *klass =
+ (GstLV2FilterClass *) GST_AUDIO_FILTER_GET_CLASS (object);
+
+ gfloat *controls;
+
+ /* remember, properties have an offset of 1 */
+ prop_id--;
+
+ if (prop_id < klass->control_in_ports->len) {
+ controls = self->ports.control.in;
+ } else if (prop_id < klass->control_in_ports->len +
+ klass->control_out_ports->len) {
+ controls = self->ports.control.out;
+ prop_id -= klass->control_in_ports->len;
+ } else {
+ g_return_if_reached ();
+ }
+
+ /* now see what type it is */
+ switch (pspec->value_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;
+ default:
+ g_return_if_reached ();
+ }
+}
+
+#if 0
+/* Convert an LV2 port role to a Gst channel positon
+ * WARNING: If the group has only a single port,
+ * GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER will be returned for pg:centerRole
+ * (which is used by LV2 for mono groups), but this is not correct. In this
+ * case the value must be changed to GST_AUDIO_CHANNEL_POSITION_FRONT_MONO
+ * (this can't be done by this function because this information isn't known
+ * at the time it is used).
+ */
+static GstAudioChannelPosition
+gst_lv2_filter_role_to_position (LilvNode * role)
+{
+ /* Front. Mono and left/right are mututally exclusive */
+ if (lilv_node_equals (role, center_role)) {
+
+ return GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER;
+ } else if (lilv_node_equals (role, left_role)) {
+ return GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT;
+ } else if (lilv_node_equals (role, right_role)) {
+ return GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT;
+
+ /* Rear. Left/right and center are mututally exclusive */
+ } else if (lilv_node_equals (role, rear_center_role)) {
+ return GST_AUDIO_CHANNEL_POSITION_REAR_CENTER;
+ } else if (lilv_node_equals (role, rear_left_role)) {
+ return GST_AUDIO_CHANNEL_POSITION_REAR_LEFT;
+ } else if (lilv_node_equals (role, rear_right_role)) {
+ return GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT;
+
+ /* Subwoofer/low-frequency-effects */
+ } else if (lilv_node_equals (role, lfe_role)) {
+ return GST_AUDIO_CHANNEL_POSITION_LFE1;
+
+ /* Center front speakers. Center and left/right_of_center
+ * are mutually exclusive */
+ } else if (lilv_node_equals (role, center_left_role)) {
+ return GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
+ } else if (lilv_node_equals (role, center_right_role)) {
+ return GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
+
+ /* sides */
+ } else if (lilv_node_equals (role, side_left_role)) {
+ return GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT;
+ } else if (lilv_node_equals (role, side_right_role)) {
+ return GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT;
+ }
+
+ return GST_AUDIO_CHANNEL_POSITION_INVALID;
+}
+
+static GstAudioChannelPosition *
+gst_lv2_filter_build_positions (GstLV2FilterGroup * group)
+{
+ GstAudioChannelPosition *positions = NULL;
+
+ /* don't do anything for mono */
+ if (group->ports->len > 1) {
+ gint i;
+
+ positions = g_new (GstAudioChannelPosition, group->ports->len);
+ for (i = 0; i < group->ports->len; ++i)
+ positions[i] = g_array_index (group->ports, GstLV2FilterPort, i).position;
+ }
+ return positions;
+}
+#endif
+
+/* Find and return the group @a uri in @a groups, or NULL if not found */
+static void
+gst_lv2_filter_type_class_add_pad_templates (GstLV2FilterClass * klass)
+{
+ GstCaps *srccaps, *sinkcaps;
+ GstPadTemplate *pad_template;
+ GstElementClass *elem_class = GST_ELEMENT_CLASS (klass);
+
+ gint in_channels = 1, out_channels = 1;
+
+ in_channels = klass->in_group.ports->len;
+
+ out_channels = klass->out_group.ports->len;
+
+ /* FIXME Implement deintereleaved audio support */
+ sinkcaps = gst_caps_new_simple ("audio/x-raw",
+ "format", G_TYPE_STRING, GST_AUDIO_NE (F32),
+ "channels", G_TYPE_INT, in_channels,
+ "rate", GST_TYPE_INT_RANGE, 1, G_MAXINT,
+ "layout", G_TYPE_STRING, "interleaved", NULL);
+
+ srccaps = gst_caps_new_simple ("audio/x-raw",
+ "format", G_TYPE_STRING, GST_AUDIO_NE (F32),
+ "channels", G_TYPE_INT, out_channels,
+ "rate", GST_TYPE_INT_RANGE, 1, G_MAXINT,
+ "layout", G_TYPE_STRING, "interleaved", NULL);
+
+ pad_template =
+ gst_pad_template_new (GST_BASE_TRANSFORM_SINK_NAME, GST_PAD_SINK,
+ GST_PAD_ALWAYS, sinkcaps);
+ gst_element_class_add_pad_template (elem_class, pad_template);
+
+ pad_template =
+ gst_pad_template_new (GST_BASE_TRANSFORM_SRC_NAME, GST_PAD_SRC,
+ GST_PAD_ALWAYS, srccaps);
+ gst_element_class_add_pad_template (elem_class, pad_template);
+
+ gst_caps_unref (sinkcaps);
+ gst_caps_unref (srccaps);
+}
+
+static gboolean
+gst_lv2_filter_setup (GstAudioFilter * gsp, const GstAudioInfo * info)
+{
+ GstLV2Filter *self;
+ GstLV2FilterClass *oclass;
+ GstAudioFilterClass *audiofilter_class;
+ gint i;
+
+ audiofilter_class = GST_AUDIO_FILTER_GET_CLASS (gsp);
+ self = (GstLV2Filter *) gsp;
+ oclass = (GstLV2FilterClass *) audiofilter_class;
+
+ g_return_val_if_fail (self->activated == FALSE, FALSE);
+
+ GST_DEBUG_OBJECT (self, "instantiating the plugin at %d Hz",
+ GST_AUDIO_INFO_RATE (info));
+
+ if (self->instance)
+ lilv_instance_free (self->instance);
+
+ if (!(self->instance =
+ lilv_plugin_instantiate (oclass->plugin, GST_AUDIO_INFO_RATE (info),
+ NULL)))
+ goto no_instance;
+
+ /* connect the control ports */
+ for (i = 0; i < oclass->control_in_ports->len; i++)
+ lilv_instance_connect_port (self->instance,
+ g_array_index (oclass->control_in_ports, GstLV2FilterPort, i).index,
+ &(self->ports.control.in[i]));
+
+ for (i = 0; i < oclass->control_out_ports->len; i++)
+ lilv_instance_connect_port (self->instance,
+ g_array_index (oclass->control_out_ports, GstLV2FilterPort, i).index,
+ &(self->ports.control.out[i]));
+
+ /* FIXME Handle audio channel positionning while negotiating CAPS */
+#if 0
+ /* set input group pad audio channel position */
+ for (i = 0; i < oclass->in_groups->len; ++i) {
+ group = &g_array_index (oclass->in_groups, GstLV2FilterGroup, i);
+ if (group->has_roles) {
+ if ((positions = gst_lv2_filter_build_positions (group))) {
+ if ((pad = gst_element_get_static_pad (GST_ELEMENT (gsp),
+ lilv_node_as_string (group->symbol)))) {
+ GST_INFO_OBJECT (self, "set audio channel positions on sink pad %s",
+ lilv_node_as_string (group->symbol));
+ s = gst_caps_get_structure (caps, 0);
+ gst_audio_set_channel_positions (s, positions);
+ gst_object_unref (pad);
+ }
+ g_free (positions);
+ positions = NULL;
+ }
+ }
+ }
+ /* set output group pad audio channel position */
+ for (i = 0; i < oclass->out_groups->len; ++i) {
+ group = &g_array_index (oclass->out_groups, GstLV2FilterGroup, i);
+ if (group->has_roles) {
+ if ((positions = gst_lv2_filter_build_positions (group))) {
+ if ((pad = gst_element_get_static_pad (GST_ELEMENT (gsp),
+ lilv_node_as_string (group->symbol)))) {
+ GST_INFO_OBJECT (self, "set audio channel positions on src pad %s",
+ lilv_node_as_string (group->symbol));
+ s = gst_caps_get_structure (caps, 0);
+ gst_audio_set_channel_positions (s, positions);
+ gst_object_unref (pad);
+ }
+ g_free (positions);
+ positions = NULL;
+ }
+ }
+ }
+#endif
+
+ lilv_instance_activate (self->instance);
+ self->activated = TRUE;
+
+ return TRUE;
+
+no_instance:
+ {
+ GST_ERROR_OBJECT (gsp, "could not create instance");
+ return FALSE;
+ }
+}
+
+static gboolean
+gst_lv2_filter_stop (GstBaseTransform * transform)
+{
+ GstLV2Filter *lv2 = (GstLV2Filter *) transform;
+
+ if (lv2->activated == FALSE) {
+ GST_ERROR_OBJECT (transform, "Deactivating but LV2 plugin not activated");
+
+ return TRUE;
+ }
+
+ if (lv2->instance == NULL) {
+ GST_ERROR_OBJECT (transform, "Deactivating but no LV2 plugin set");
+
+ return TRUE;
+ }
+
+ GST_DEBUG_OBJECT (lv2, "deactivating");
+
+ lilv_instance_deactivate (lv2->instance);
+
+ lv2->activated = FALSE;
+
+ lilv_instance_free (lv2->instance);
+ lv2->instance = NULL;
+
+ return TRUE;
+}
+
+static inline void
+gst_lv2_filter_deinterleave_data (guint n_channels, gfloat * outdata,
+ guint samples, gfloat * indata)
+{
+ guint i, j;
+
+ for (i = 0; i < n_channels; i++)
+ for (j = 0; j < samples; j++)
+ outdata[i * samples + j] = indata[j * n_channels + i];
+}
+
+static inline void
+gst_lv2_filter_interleave_data (guint n_channels, gfloat * outdata,
+ guint samples, gfloat * indata)
+{
+ guint i, j;
+
+ for (i = 0; i < n_channels; i++)
+ for (j = 0; j < samples; j++) {
+ outdata[j * n_channels + i] = indata[i * samples + j];
+ }
+}
+
+static GstFlowReturn
+gst_lv2_filter_transform_data (GstLV2Filter * self,
+ GstMapInfo * in_map, GstMapInfo * out_map)
+{
+ GstAudioFilterClass *audiofilter_class;
+ GstLV2FilterClass *lv2_class;
+ GstLV2FilterGroup *lv2_group;
+ GstLV2FilterPort *lv2_port;
+ guint j, nframes, samples, out_samples;
+
+ gfloat *in = NULL, *out = NULL;
+
+ nframes = in_map->size / sizeof (float);
+
+ audiofilter_class = GST_AUDIO_FILTER_GET_CLASS (self);
+ lv2_class = (GstLV2FilterClass *) audiofilter_class;
+
+ samples = nframes / lv2_class->in_group.ports->len;
+
+ /* multi channel inputs */
+ lv2_group = &lv2_class->in_group;
+
+ in = g_new0 (gfloat, nframes);
+
+ if (lv2_group->ports->len > 1)
+ gst_lv2_filter_deinterleave_data (lv2_group->ports->len, in,
+ samples, (gfloat *) in_map->data);
+
+ for (j = 0; j < lv2_group->ports->len; ++j) {
+ lv2_port = &g_array_index (lv2_group->ports, GstLV2FilterPort, j);
+
+ lilv_instance_connect_port (self->instance, lv2_port->index,
+ in + (j * samples));
+ }
+
+ lv2_group = &lv2_class->out_group;
+ out_samples = nframes / lv2_group->ports->len;
+ out = g_new0 (gfloat, samples * lv2_group->ports->len);
+ for (j = 0; j < lv2_group->ports->len; ++j) {
+ lv2_port = &g_array_index (lv2_group->ports, GstLV2FilterPort, j);
+ lilv_instance_connect_port (self->instance, lv2_port->index,
+ out + (j * out_samples));
+ }
+
+ lilv_instance_run (self->instance, samples);
+
+ if (lv2_group->ports->len > 1)
+ gst_lv2_filter_interleave_data (lv2_group->ports->len,
+ (gfloat *) out_map->data, out_samples, out);
+ g_free (out);
+ g_free (in);
+
+ return GST_FLOW_OK;
+}
+
+static GstFlowReturn
+gst_lv2_filter_transform_ip (GstBaseTransform * transform, GstBuffer * buf)
+{
+ GstFlowReturn ret;
+ GstMapInfo map;
+
+ gst_buffer_map (buf, &map, GST_MAP_READWRITE);
+
+ ret = gst_lv2_filter_transform_data ((GstLV2Filter *) transform, &map, &map);
+
+ gst_buffer_unmap (buf, &map);
+
+ return ret;
+}
+
+static GstFlowReturn
+gst_lv2_filter_transform (GstBaseTransform * transform,
+ GstBuffer * inbuf, GstBuffer * outbuf)
+{
+ GstMapInfo in_map, out_map;
+ GstFlowReturn ret;
+
+ gst_buffer_map (inbuf, &in_map, GST_MAP_READ);
+ gst_buffer_map (outbuf, &out_map, GST_MAP_WRITE);
+
+ ret = gst_lv2_filter_transform_data ((GstLV2Filter *) transform,
+ &in_map, &out_map);
+
+ gst_buffer_unmap (inbuf, &in_map);
+ gst_buffer_unmap (outbuf, &out_map);
+
+ return ret;
+}
+
+static void
+gst_lv2_filter_base_init (gpointer g_class)
+{
+ GstLV2FilterClass *klass = (GstLV2FilterClass *) g_class;
+ GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
+ LilvPlugin *lv2plugin;
+ LilvNode *val;
+ /* FIXME Handle channels positionning
+ * GstAudioChannelPosition position = GST_AUDIO_CHANNEL_POSITION_INVALID; */
+ guint j, in_pad_index = 0, out_pad_index = 0;
+ const gchar *klass_tags;
+ gchar *longname, *author;
+
+ lv2plugin = (LilvPlugin *) g_type_get_qdata (G_OBJECT_CLASS_TYPE (klass),
+ descriptor_quark);
+
+ g_assert (lv2plugin);
+
+ GST_INFO ("base_init %p, plugin %s", g_class,
+ lilv_node_get_turtle_token (lilv_plugin_get_uri (lv2plugin)));
+
+ klass->in_group.ports = g_array_new (FALSE, TRUE, sizeof (GstLV2FilterPort));
+ klass->out_group.ports = g_array_new (FALSE, TRUE, sizeof (GstLV2FilterPort));
+ klass->control_in_ports =
+ g_array_new (FALSE, TRUE, sizeof (GstLV2FilterPort));
+ klass->control_out_ports =
+ g_array_new (FALSE, TRUE, sizeof (GstLV2FilterPort));
+
+ /* 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);
+ struct _GstLV2FilterPort desc = { j, 0, };
+ LilvNodes *lv2group = lilv_port_get (lv2plugin, port, group_pred);
+
+ if (lv2group) {
+ /* port is part of a group */
+ const gchar *group_uri = lilv_node_as_uri (lv2group);
+ GstLV2FilterGroup *group =
+ is_input ? &klass->in_group : &klass->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 (GstLV2FilterPort));
+ }
+
+ /* FIXME Handle channels positionning
+ position = GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT;
+ sub_values = lilv_port_get_value (lv2plugin, port, has_role_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)) {
+
+ desc.pad = is_input ? in_pad_index++ : out_pad_index++;
+ if (is_input)
+ g_array_append_val (klass->in_group.ports, desc);
+ else
+ g_array_append_val (klass->out_group.ports, desc);
+ } else if (lilv_port_is_a (lv2plugin, port, control_class)) {
+ if (is_input)
+ g_array_append_val (klass->control_in_ports, desc);
+ else
+ g_array_append_val (klass->control_out_ports, desc);
+ } else {
+ /* unknown port type */
+ GST_INFO ("unhandled port %d", j);
+ continue;
+ }
+ }
+ }
+ gst_lv2_filter_type_class_add_pad_templates (klass);
+
+ 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) {
+ author = g_strdup (lilv_node_as_string (val));
+ lilv_node_free (val);
+ } else {
+ author = g_strdup ("no author available");
+ }
+
+ klass_tags = "Filter/Effect/Audio/LV2";
+
+ GST_INFO ("tags : %s", klass_tags);
+ gst_element_class_set_metadata (element_class, longname,
+ klass_tags, longname, author);
+ g_free (longname);
+ g_free (author);
+
+ klass->plugin = lv2plugin;
+}
+
+static gchar *
+gst_lv2_filter_class_get_param_name (GstLV2FilterClass * klass,
+ const LilvPort * port)
+{
+ LilvPlugin *lv2plugin = klass->plugin;
+ gchar *ret;
+
+ ret = g_strdup (lilv_node_as_string (lilv_port_get_symbol (lv2plugin, port)));
+
+ /* 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 (G_OBJECT_CLASS (klass), ret)) {
+ gint n = 1;
+ gchar *nret = g_strdup_printf ("%s-%d", ret, n++);
+
+ while (g_object_class_find_property (G_OBJECT_CLASS (klass), 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,
+ lilv_node_as_string (lilv_port_get_symbol (lv2plugin, port)));
+
+ return ret;
+}
+
+static gchar *
+gst_lv2_filter_class_get_param_nick (GstLV2FilterClass * klass,
+ const LilvPort * port)
+{
+ LilvPlugin *lv2plugin = klass->plugin;
+
+ return g_strdup (lilv_node_as_string (lilv_port_get_name (lv2plugin, port)));
+}
+
+static GParamSpec *
+gst_lv2_filter_class_get_param_spec (GstLV2FilterClass * klass, gint portnum)
+{
+ LilvPlugin *lv2plugin = klass->plugin;
+ const LilvPort *port = lilv_plugin_get_port_by_index (lv2plugin, portnum);
+ LilvNode *lv2def, *lv2min, *lv2max;
+ GParamSpec *ret;
+ gchar *name, *nick;
+ gint perms;
+ gfloat lower = 0.0f, upper = 1.0f, def = 0.0f;
+
+ nick = gst_lv2_filter_class_get_param_nick (klass, port);
+ name = gst_lv2_filter_class_get_param_name (klass, port);
+
+ 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))
+ 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) {
+ GST_WARNING ("%s has lower bound %f > default %f",
+ lilv_node_as_string (lilv_plugin_get_uri (lv2plugin)), lower, def);
+ lower = def;
+ }
+
+ if (def > upper) {
+ GST_WARNING ("%s has upper bound %f < default %f",
+ lilv_node_as_string (lilv_plugin_get_uri (lv2plugin)), upper, def);
+ upper = def;
+ }
+
+ 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:
+ g_free (name);
+ g_free (nick);
+
+ return ret;
+}
+
+static void
+gst_lv2_filter_class_init (GstLV2FilterClass * klass, LilvPlugin * lv2plugin)
+{
+ GObjectClass *gobject_class;
+ GstBaseTransformClass *transform_class;
+ GstAudioFilterClass *audiofilter_class;
+ GParamSpec *p;
+ gint i, ix;
+
+ GST_DEBUG ("class_init %p", klass);
+
+ gobject_class = (GObjectClass *) klass;
+ gobject_class->set_property = gst_lv2_filter_set_property;
+ gobject_class->get_property = gst_lv2_filter_get_property;
+
+ audiofilter_class = GST_AUDIO_FILTER_CLASS (klass);
+ audiofilter_class->setup = gst_lv2_filter_setup;
+
+ transform_class = GST_BASE_TRANSFORM_CLASS (klass);
+ transform_class->stop = gst_lv2_filter_stop;
+ transform_class->transform = gst_lv2_filter_transform;
+ transform_class->transform_ip = gst_lv2_filter_transform_ip;
+
+ klass->plugin = lv2plugin;
+
+ /* properties have an offset of 1 */
+ ix = 1;
+
+ /* register properties */
+
+ for (i = 0; i < klass->control_in_ports->len; i++, ix++) {
+ p = gst_lv2_filter_class_get_param_spec (klass,
+ g_array_index (klass->control_in_ports, GstLV2FilterPort, i).index);
+
+ g_object_class_install_property (gobject_class, ix, p);
+ }
+
+ for (i = 0; i < klass->control_out_ports->len; i++, ix++) {
+ p = gst_lv2_filter_class_get_param_spec (klass,
+ g_array_index (klass->control_out_ports, GstLV2FilterPort, i).index);
+
+ g_object_class_install_property (gobject_class, ix, p);
+ }
+}
+
+static void
+gst_lv2_filter_init (GstLV2Filter * self, GstLV2FilterClass * klass)
+{
+ self->plugin = klass->plugin;
+ self->instance = NULL;
+ self->activated = FALSE;
+
+ self->ports.control.in = g_new0 (gfloat, klass->control_in_ports->len);
+ self->ports.control.out = g_new0 (gfloat, klass->control_out_ports->len);
+
+ if (!lilv_plugin_has_feature (self->plugin, in_place_broken_pred))
+ gst_base_transform_set_in_place (GST_BASE_TRANSFORM (self), TRUE);
+}
+
+gboolean
+gst_lv2_filter_register_element (GstPlugin * plugin, const gchar * type_name,
+ gpointer * lv2plugin)
+{
+ GType type;
+ GTypeInfo typeinfo = {
+ sizeof (GstLV2FilterClass),
+ (GBaseInitFunc) gst_lv2_filter_base_init,
+ NULL,
+ (GClassInitFunc) gst_lv2_filter_class_init,
+ NULL,
+ lv2plugin,
+ sizeof (GstLV2Filter),
+ 0,
+ (GInstanceInitFunc) gst_lv2_filter_init,
+ };
+
+ /* create the type */
+ type =
+ g_type_register_static (GST_TYPE_AUDIO_FILTER, type_name, &typeinfo, 0);
+
+ if (!parent_class)
+ parent_class = g_type_class_ref (GST_TYPE_AUDIO_FILTER);
+
+
+ /* FIXME: not needed anymore when we can add pad templates, etc in class_init
+ * as class_data contains the LADSPA_Descriptor too */
+ g_type_set_qdata (type, descriptor_quark, lv2plugin);
+
+ return gst_element_register (plugin, type_name, GST_RANK_NONE, type);
+}