lv2: support CVPorts
authorStefan Sauer <ensonic@users.sf.net>
Thu, 19 May 2016 04:29:15 +0000 (21:29 -0700)
committerStefan Sauer <ensonic@users.sf.net>
Thu, 19 May 2016 04:33:43 +0000 (21:33 -0700)
CVPorts are ports that take a buffer. For now we just fill the buffers with
the control value.

ext/lv2/README
ext/lv2/gstlv2.c
ext/lv2/gstlv2.h
ext/lv2/gstlv2filter.c
ext/lv2/gstlv2source.c
ext/lv2/gstlv2utils.c
ext/lv2/gstlv2utils.h

index 0e456db..98fab55 100644 (file)
@@ -34,9 +34,6 @@ gst-launch-1.0 calf-sourceforge-net-plugins-Organ event-in="C-3" name=s ! interl
 
 
 TODO
-* support http://lv2plug.in/ns/lv2core/#CVPort
-  - these ports need a buffer with the property value
-  - we should sync, then fill the buffer and connect the port
 * support presets
   for pl in $(lv2ls); do if test "$(lv2info "$pl" | grep -A1 "Presets:" | tail -n1)" != ""; then echo "$pl"; fi; done
 * support more host features
index de8f4be..6ad8330 100644 (file)
@@ -42,6 +42,7 @@
 
 #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"
 
 GST_DEBUG_CATEGORY (lv2_debug);
 #define GST_CAT_DEFAULT lv2_debug
@@ -198,8 +199,19 @@ plugin_init (GstPlugin * plugin)
   world = lilv_world_new ();
   lilv_world_load_all (world);
 
+/* have been added after lilv-0.22.0, which is the last release */
+#ifndef LILV_URI_ATOM_PORT
+#define LILV_URI_ATOM_PORT    "http://lv2plug.in/ns/ext/atom#AtomPort"
+#endif
+#ifndef LILV_URI_CV_PORT
+#define LILV_URI_CV_PORT      "http://lv2plug.in/ns/lv2core#CVPort"
+#endif
+
+  atom_class = lilv_new_uri (world, LILV_URI_ATOM_PORT);
   audio_class = lilv_new_uri (world, LILV_URI_AUDIO_PORT);
   control_class = lilv_new_uri (world, LILV_URI_CONTROL_PORT);
+  cv_class = lilv_new_uri (world, LILV_URI_CV_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);
 
@@ -207,7 +219,9 @@ plugin_init (GstPlugin * plugin)
   toggled_prop = lilv_new_uri (world, LV2_CORE__toggled);
   designation_pred = lilv_new_uri (world, LV2_CORE__designation);
   in_place_broken_pred = lilv_new_uri (world, LV2_CORE__inPlaceBroken);
+  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);
 
   center_role = lilv_new_uri (world, LV2_PORT_GROUPS__center);
   left_role = lilv_new_uri (world, LV2_PORT_GROUPS__left);
@@ -275,8 +289,11 @@ __attribute__ ((destructor))
 #endif
      static void plugin_cleanup (GstPlugin * plugin)
 {
+  lilv_node_free (atom_class);
   lilv_node_free (audio_class);
   lilv_node_free (control_class);
+  lilv_node_free (cv_class);
+  lilv_node_free (event_class);
   lilv_node_free (input_class);
   lilv_node_free (output_class);
 
@@ -284,7 +301,9 @@ __attribute__ ((destructor))
   lilv_node_free (toggled_prop);
   lilv_node_free (designation_pred);
   lilv_node_free (in_place_broken_pred);
+  lilv_node_free (optional_pred);
   lilv_node_free (group_pred);
+  lilv_node_free (supports_event_pred);
 
   lilv_node_free (center_role);
   lilv_node_free (left_role);
index 340752a..2a98885 100644 (file)
 #include "gstlv2utils.h"
 
 LilvWorld *world;
+LilvNode *atom_class;
 LilvNode *audio_class;
 LilvNode *control_class;
+LilvNode *cv_class;
+LilvNode *event_class;
 LilvNode *input_class;
 LilvNode *output_class;
 LilvNode *integer_prop;
 LilvNode *toggled_prop;
 LilvNode *designation_pred;
 LilvNode *in_place_broken_pred;
+LilvNode *optional_pred;
 LilvNode *group_pred;
+LilvNode *supports_event_pred;
 
 LilvNode *center_role;
 LilvNode *left_role;
index 0f98463..dc7ea90 100644 (file)
@@ -305,18 +305,19 @@ static GstFlowReturn
 gst_lv2_filter_transform_data (GstLV2Filter * self,
     GstMapInfo * in_map, GstMapInfo * out_map)
 {
-  GstLV2FilterClass *lv2_class;
+  GstLV2FilterClass *klass =
+      (GstLV2FilterClass *) GST_AUDIO_FILTER_GET_CLASS (self);
+  GstLV2Class *lv2_class = &klass->lv2;
   GstLV2Group *lv2_group;
   GstLV2Port *lv2_port;
-  guint j, nframes, samples, out_samples;
-  gfloat *in = NULL, *out = NULL;
+  guint j, k, l, nframes, samples, out_samples;
+  gfloat *in = NULL, *out = NULL, *cv = NULL, *mem;
+  gfloat val;
 
   nframes = in_map->size / sizeof (float);
 
-  lv2_class = (GstLV2FilterClass *) GST_AUDIO_FILTER_GET_CLASS (self);
-
   /* multi channel inputs */
-  lv2_group = &lv2_class->lv2.in_group;
+  lv2_group = &lv2_class->in_group;
   samples = nframes / lv2_group->ports->len;
   in = g_new0 (gfloat, nframes);
   GST_LOG_OBJECT (self, "in : samples=%u, nframes=%u, ports=%d", samples,
@@ -333,7 +334,7 @@ gst_lv2_filter_transform_data (GstLV2Filter * self,
   }
 
   /* multi channel outputs */
-  lv2_group = &lv2_class->lv2.out_group;
+  lv2_group = &lv2_class->out_group;
   out_samples = nframes / lv2_group->ports->len;
   out = g_new0 (gfloat, samples * lv2_group->ports->len);
   GST_LOG_OBJECT (self, "out: samples=%u, nframes=%u, ports=%d", out_samples,
@@ -344,6 +345,22 @@ gst_lv2_filter_transform_data (GstLV2Filter * self,
         out + (j * out_samples));
   }
 
+  /* cv ports */
+  cv = g_new (gfloat, samples * lv2_class->num_cv_in);
+  for (j = k = 0; j < lv2_class->control_in_ports->len; j++) {
+    lv2_port = &g_array_index (lv2_class->control_in_ports, GstLV2Port, j);
+    if (lv2_port->type != GST_LV2_PORT_CV)
+      continue;
+
+    mem = cv + (k * samples);
+    val = self->lv2.ports.control.in[j];
+    /* FIXME: use gst_control_binding_get_value_array */
+    for (l = 0; l < samples; l++)
+      mem[l] = val;
+    lilv_instance_connect_port (self->lv2.instance, lv2_port->index, mem);
+    k++;
+  }
+
   lilv_instance_run (self->lv2.instance, samples);
 
   if (lv2_group->ports->len > 1)
@@ -351,6 +368,7 @@ gst_lv2_filter_transform_data (GstLV2Filter * self,
         (gfloat *) out_map->data, out_samples, out);
   g_free (out);
   g_free (in);
+  g_free (cv);
 
   return GST_FLOW_OK;
 }
index 23317a2..3d41c04 100644 (file)
@@ -292,8 +292,8 @@ gst_lv2_source_fill (GstBaseSrc * base, guint64 offset,
     guint length, GstBuffer * buffer)
 {
   GstLV2Source *lv2 = (GstLV2Source *) base;
-  GstLV2SourceClass *lv2_class =
-      (GstLV2SourceClass *) GST_BASE_SRC_GET_CLASS (lv2);
+  GstLV2SourceClass *klass = (GstLV2SourceClass *) GST_BASE_SRC_GET_CLASS (lv2);
+  GstLV2Class *lv2_class = &klass->lv2;
   GstLV2Group *lv2_group;
   GstLV2Port *lv2_port;
   GstClockTime next_time;
@@ -302,8 +302,9 @@ gst_lv2_source_fill (GstBaseSrc * base, guint64 offset,
   GstElementClass *eclass;
   GstMapInfo map;
   gint samplerate, bpf;
-  guint j;
-  gfloat *out = NULL;
+  guint j, k, l;
+  gfloat *out = NULL, *cv = NULL, *mem;
+  gfloat val;
 
   /* example for tagging generated data */
   if (!lv2->tags_pushed) {
@@ -399,7 +400,7 @@ gst_lv2_source_fill (GstBaseSrc * base, guint64 offset,
   gst_buffer_map (buffer, &map, GST_MAP_WRITE);
 
   /* multi channel outputs */
-  lv2_group = &lv2_class->lv2.out_group;
+  lv2_group = &lv2_class->out_group;
   if (lv2_group->ports->len > 1) {
     out = g_new0 (gfloat, samples * lv2_group->ports->len);
     for (j = 0; j < lv2_group->ports->len; ++j) {
@@ -415,6 +416,22 @@ gst_lv2_source_fill (GstBaseSrc * base, guint64 offset,
     GST_LOG_OBJECT (lv2, "connected port 0");
   }
 
+  /* cv ports */
+  cv = g_new (gfloat, samples * lv2_class->num_cv_in);
+  for (j = k = 0; j < lv2_class->control_in_ports->len; j++) {
+    lv2_port = &g_array_index (lv2_class->control_in_ports, GstLV2Port, j);
+    if (lv2_port->type != GST_LV2_PORT_CV)
+      continue;
+
+    mem = cv + (k * samples);
+    val = lv2->lv2.ports.control.in[j];
+    /* FIXME: use gst_control_binding_get_value_array */
+    for (l = 0; l < samples; l++)
+      mem[l] = val;
+    lilv_instance_connect_port (lv2->lv2.instance, lv2_port->index, mem);
+    k++;
+  }
+
   lilv_instance_run (lv2->lv2.instance, samples);
 
   if (lv2_group->ports->len > 1) {
@@ -423,6 +440,8 @@ gst_lv2_source_fill (GstBaseSrc * base, guint64 offset,
     g_free (out);
   }
 
+  g_free (cv);
+
   gst_buffer_unmap (buffer, &map);
 
   return GST_FLOW_OK;
index cedd6bc..08d9d94 100644 (file)
@@ -158,6 +158,8 @@ gboolean
 gst_lv2_setup (GstLV2 * lv2, unsigned long rate)
 {
   GstLV2Class *lv2_class = lv2->klass;
+  GstLV2Port *port;
+  GArray *ports;
   gint i;
 
   if (lv2->instance)
@@ -168,15 +170,22 @@ gst_lv2_setup (GstLV2 * lv2, unsigned long rate)
     return FALSE;
 
   /* connect the control ports */
-  for (i = 0; i < lv2_class->control_in_ports->len; i++)
-    lilv_instance_connect_port (lv2->instance,
-        g_array_index (lv2_class->control_in_ports, GstLV2Port, i).index,
+  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]));
-
-  for (i = 0; i < lv2_class->control_out_ports->len; i++)
-    lilv_instance_connect_port (lv2->instance,
-        g_array_index (lv2_class->control_out_ports, GstLV2Port, i).index,
+  }
+  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;
@@ -365,7 +374,8 @@ gst_lv2_class_get_param_spec (GstLV2Class * klass, GObjectClass * object_class,
   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))
+  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)) {
@@ -547,8 +557,6 @@ gst_lv2_class_init (GstLV2Class * lv2_class, GType type)
       gst_structure_get_value (lv2_meta_all, g_type_name (type));
   GstStructure *lv2_meta = g_value_get_boxed (value);
   const LilvPlugin *lv2plugin;
-  /* FIXME Handle channels positionning
-   * GstAudioChannelPosition position = GST_AUDIO_CHANNEL_POSITION_INVALID; */
   guint j, in_pad_index = 0, out_pad_index = 0;
   const LilvPlugins *plugins = lilv_world_get_all_plugins (world);
   LilvNode *plugin_uri;
@@ -573,8 +581,12 @@ gst_lv2_class_init (GstLV2Class * lv2_class, GType type)
   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 _GstLV2Port desc = { j, 0, };
+    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 */
@@ -590,7 +602,7 @@ gst_lv2_class_init (GstLV2Class * lv2_class, GType type)
 
       /* FIXME Handle channels positionning
          position = GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT;
-         sub_values = lilv_port_get_value (lv2plugin, port, has_role_pred);
+         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);
@@ -606,21 +618,70 @@ gst_lv2_class_init (GstLV2Class * lv2_class, GType type)
       /* 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)
+        if (is_input) {
+          desc.pad = in_pad_index++;
           g_array_append_val (lv2_class->in_group.ports, desc);
-        else
+        } 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)) {
-        if (is_input)
+        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
+        } 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 {
-        /* unknown port type */
-        GST_INFO ("unhandled port %d: %s", j,
-            lilv_node_as_string (lilv_port_get_symbol (lv2plugin, port)));
-        continue;
+        /* 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));
+          }
+        }
       }
     }
   }
index 6ff6482..e5213b2 100644 (file)
@@ -43,13 +43,23 @@ struct _GstLV2Group
   guint pad; /**< Gst pad index */
   gchar *symbol; /**< Gst pad name / LV2 group symbol */
   GArray *ports; /**< Array of GstLV2Port */
+  /* FIXME: not set as of now */
   gboolean has_roles; /**< TRUE iff all ports have a known role */
 };
 
+typedef enum {
+  GST_LV2_PORT_AUDIO = 0,
+  GST_LV2_PORT_CONTROL,
+  GST_LV2_PORT_CV
+} GstLV2PortType;
+
 struct _GstLV2Port
 {
   gint index; /**< LV2 port index (on LV2 plugin) */
-  gint pad; /**< Gst pad index (iff not part of a group) */
+  GstLV2PortType type; /**< Port type */
+  /**< Gst pad index (iff not part of a group), only for audio ports */
+  gint pad;
+  /* FIXME: not set as of now */
   LilvNode *role; /**< Channel position / port role */
   GstAudioChannelPosition position; /**< Channel position */
 };
@@ -79,6 +89,9 @@ struct _GstLV2Class
 
   const LilvPlugin *plugin;
 
+  gint num_control_in, num_control_out;
+  gint num_cv_in, num_cv_out;
+
   GstLV2Group in_group; /**< Array of GstLV2Group */
   GstLV2Group out_group; /**< Array of GstLV2Group */
   GArray *control_in_ports; /**< Array of GstLV2Port */