lv2: support CVPorts

CVPorts are ports that take a buffer. For now we just fill the buffers with
the control value.
This commit is contained in:
Stefan Sauer 2016-05-18 21:29:15 -07:00
parent c66d3906a4
commit a1bf2e17cc
7 changed files with 169 additions and 37 deletions

View 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

View 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);

View file

@ -29,15 +29,20 @@
#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;

View 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;
}

View 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;

View 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
} 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 {
/* 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));
}
}
}
}
}

View 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 */