ref: d891d7e07c1b673ae61f814c84992224af19ebe8
parent: ffd8837b861f7a9853adead235204b6623cc5742
parent: 202042e2d8936d3daebfcef88f2f86806f2f9c43
author: Ulrich Klauer <ulrich@chirlu.de>
date: Mon Jan 7 21:27:12 EST 2013
Merge branch 'ladspa-multiport'
--- a/ChangeLog
+++ b/ChangeLog
@@ -32,6 +32,7 @@
o Allow sending spectrograms to stdout. (Ulrich Klauer)
o Allow mixing time and sample-count arguments for the delay
effect, and for spectrogram -S and -d. (Ulrich Klauer)
+ o Support multi-channel LADSPA plugins. (Eric Wong)
Internal improvements:
--- a/sox.1
+++ b/sox.1
@@ -2364,17 +2364,25 @@
.SP
This effect supports the \fB\-\-plot\fR global option.
.TP
-\fBladspa\fR \fBmodule\fR [\fBplugin\fR] [\fBargument\fR...]
+\fBladspa\fR [\fB-r\fR] \fImodule\fR [\fIplugin\fR] [\fIargument\fR ...]
Apply a LADSPA [5] (Linux Audio Developer's Simple Plugin API) plugin.
Despite the name, LADSPA is not Linux-specific, and a wide range of
effects is available as LADSPA plugins, such as cmt [6] (the Computer
Music Toolkit) and Steve Harris's plugin collection [7]. The first
argument is the plugin module, the second the name of the plugin (a
-module can contain more than one plugin) and any other arguments are
+module can contain more than one plugin), and any other arguments are
for the control ports of the plugin. Missing arguments are supplied by
-default values if possible. Only plugins with at most one audio input
-and one audio output port can be used. If found, the environment variable
-LADSPA_PATH will be used as search path for plugins.
+default values if possible.
+.SP
+Normally, the number of input ports of the plugin must match the number
+of input channels, and the number of output ports determines the output
+channel count. However, the
+.B \-r
+(replicate) option allows cloning a mono plugin to handle multi-channel
+input.
+.SP
+If found, the environment variable LADSPA_PATH will be used as search
+path for plugins.
.TP
\fBloudness\fR [\fIgain\fR [\fIreference\fR]]
Loudness control\*msimilar to the
--- a/src/ladspa.c
+++ b/src/ladspa.c
@@ -25,6 +25,15 @@
#include <string.h>
#include "ladspa.h"
+/*
+ * Assuming LADSPA_Data == float. This is the case in 2012 and has been
+ * the case for many years now.
+ */
+#define SOX_SAMPLE_TO_LADSPA_DATA(d,clips) \
+ SOX_SAMPLE_TO_FLOAT_32BIT((d),(clips))
+#define LADSPA_DATA_TO_SOX_SAMPLE(d,clips) \
+ SOX_FLOAT_32BIT_TO_SAMPLE((d),(clips))
+
static sox_effect_handler_t sox_ladspa_effect;
/* Private data for resampling */
@@ -31,10 +40,15 @@
typedef struct {
char *name; /* plugin name */
lt_dlhandle lth; /* dynamic object handle */
+ sox_bool clone;
const LADSPA_Descriptor *desc; /* plugin descriptor */
- LADSPA_Handle handle; /* instantiated plugin handle */
+ LADSPA_Handle *handles; /* instantiated plugin handles */
+ size_t handle_count;
LADSPA_Data *control; /* control ports */
- unsigned long input_port, output_port;
+ unsigned long *inputs;
+ size_t input_count;
+ unsigned long *outputs;
+ size_t output_count;
} priv_t;
static LADSPA_Data ladspa_default(const LADSPA_PortRangeHint *p)
@@ -89,8 +103,10 @@
double arg;
--argc, ++argv;
- l_st->input_port = ULONG_MAX;
- l_st->output_port = ULONG_MAX;
+ if (argc >= 1 && strcmp(argv[0], "-r") == 0) {
+ l_st->clone = sox_true;
+ argc--; argv++;
+ }
/* Get module name */
if (argc >= 1) {
@@ -137,8 +153,11 @@
argc--; argv++;
}
- /* Scan the ports to check there's one input and one output */
+ /* Scan the ports for inputs and outputs */
l_st->control = lsx_calloc(l_st->desc->PortCount, sizeof(LADSPA_Data));
+ l_st->inputs = lsx_malloc(l_st->desc->PortCount * sizeof(unsigned long));
+ l_st->outputs = lsx_malloc(l_st->desc->PortCount * sizeof(unsigned long));
+
for (i = 0; i < l_st->desc->PortCount; i++) {
const LADSPA_PortDescriptor port = l_st->desc->PortDescriptors[i];
@@ -154,17 +173,9 @@
if (LADSPA_IS_PORT_AUDIO(port)) {
if (LADSPA_IS_PORT_INPUT(port)) {
- if (l_st->input_port != ULONG_MAX) {
- lsx_fail("can't use a plugin with more than one audio input port");
- return SOX_EOF;
- }
- l_st->input_port = i;
+ l_st->inputs[l_st->input_count++] = i;
} else if (LADSPA_IS_PORT_OUTPUT(port)) {
- if (l_st->output_port != ULONG_MAX) {
- lsx_fail("can't use a plugin with more than one audio output port");
- return SOX_EOF;
- }
- l_st->output_port = i;
+ l_st->outputs[l_st->output_count++] = i;
}
} else { /* Control port */
if (argc == 0) {
@@ -195,25 +206,95 @@
{
priv_t * l_st = (priv_t *)effp->priv;
unsigned long i;
+ size_t h;
+ unsigned long rate = (unsigned long)effp->in_signal.rate;
/* Instantiate the plugin */
lsx_debug("rate for plugin is %g", effp->in_signal.rate);
- l_st->handle = l_st->desc->instantiate(l_st->desc, (unsigned long)effp->in_signal.rate);
- if (l_st->handle == NULL) {
- lsx_fail("could not instantiate plugin");
- return SOX_EOF;
+
+ if (l_st->input_count == 1 && l_st->output_count == 1 &&
+ effp->in_signal.channels == effp->out_signal.channels) {
+ /* for mono plugins, they are common */
+
+ if (!l_st->clone && effp->in_signal.channels > 1) {
+ lsx_fail("expected 1 input channel(s), found %u; consider using -r",
+ effp->in_signal.channels);
+ return SOX_EOF;
+ }
+
+ /*
+ * create one handle per channel for mono plugins. ecasound does this, too.
+ * mono LADSPA plugins are common and SoX supported mono LADSPA plugins
+ * exclusively for a while.
+ */
+ l_st->handles = lsx_malloc(effp->in_signal.channels *
+ sizeof(LADSPA_Handle *));
+
+ while (l_st->handle_count < effp->in_signal.channels)
+ l_st->handles[l_st->handle_count++] = l_st->desc->instantiate(l_st->desc, rate);
+
+ } else {
+ /*
+ * assume the plugin is multi-channel capable with one instance,
+ * Some LADSPA plugins are stereo (e.g. bs2b-ladspa)
+ */
+
+ if (l_st->input_count < effp->in_signal.channels) {
+ lsx_fail("fewer plugin input ports than input channels (%u < %u)",
+ (unsigned)l_st->input_count, effp->in_signal.channels);
+ return SOX_EOF;
+ }
+
+ /* warn if LADSPA audio ports are unused. ecasound does this, too */
+ if (l_st->input_count > effp->in_signal.channels)
+ lsx_warn("more plugin input ports than input channels (%u > %u)",
+ (unsigned)l_st->input_count, effp->in_signal.channels);
+
+ /*
+ * some LADSPA plugins increase/decrease the channel count
+ * (e.g. "mixer" in cmt or vocoder):
+ */
+ if (l_st->output_count != effp->out_signal.channels) {
+ lsx_debug("changing output channels to match plugin output ports (%u => %u)",
+ effp->out_signal.channels, (unsigned)l_st->output_count);
+ effp->out_signal.channels = l_st->output_count;
+ }
+
+ l_st->handle_count = 1;
+ l_st->handles = lsx_malloc(sizeof(LADSPA_Handle *));
+ l_st->handles[0] = l_st->desc->instantiate(l_st->desc, rate);
}
+ /* abandon everything completely on any failed handle instantiation */
+ for (h = 0; h < l_st->handle_count; h++) {
+ if (l_st->handles[h] == NULL) {
+ /* cleanup the handles that did instantiate successfully */
+ for (h = 0; l_st->desc->cleanup && h < l_st->handle_count; h++) {
+ if (l_st->handles[h])
+ l_st->desc->cleanup(l_st->handles[h]);
+ }
+
+ free(l_st->handles);
+ l_st->handle_count = 0;
+ lsx_fail("could not instantiate plugin");
+ return SOX_EOF;
+ }
+ }
+
for (i = 0; i < l_st->desc->PortCount; i++) {
const LADSPA_PortDescriptor port = l_st->desc->PortDescriptors[i];
- if (LADSPA_IS_PORT_CONTROL(port))
- l_st->desc->connect_port(l_st->handle, i, &(l_st->control[i]));
+ if (LADSPA_IS_PORT_CONTROL(port)) {
+ for (h = 0; h < l_st->handle_count; h++)
+ l_st->desc->connect_port(l_st->handles[h], i, &(l_st->control[i]));
+ }
}
- /* If needed, activate the plugin */
- if (l_st->desc->activate)
- l_st->desc->activate(l_st->handle);
+ /* If needed, activate the plugin instances */
+ if (l_st->desc->activate) {
+ for (h = 0; h < l_st->handle_count; h++)
+ l_st->desc->activate(l_st->handles[h]);
+ }
return SOX_SUCCESS;
}
@@ -226,37 +307,66 @@
{
priv_t * l_st = (priv_t *)effp->priv;
size_t i, len = min(*isamp, *osamp);
+ size_t j;
+ size_t h;
+ const size_t total_input_count = l_st->input_count * l_st->handle_count;
+ const size_t total_output_count = l_st->output_count * l_st->handle_count;
+ const size_t input_len = len / total_input_count;
+ size_t output_len = len / total_output_count;
- *osamp = *isamp = len;
+ if (total_output_count < total_input_count)
+ output_len = input_len;
+ *isamp = len;
+ *osamp = 0;
+
if (len) {
- LADSPA_Data *buf = lsx_malloc(sizeof(LADSPA_Data) * len);
+ LADSPA_Data *buf = lsx_calloc(len, sizeof(LADSPA_Data));
+ LADSPA_Data *outbuf = lsx_calloc(len, sizeof(LADSPA_Data));
+ LADSPA_Handle handle;
+ unsigned long port;
SOX_SAMPLE_LOCALS;
- /* Insert input if effect takes it */
- if (l_st->input_port != ULONG_MAX) {
- /* Copy the input; FIXME: Assume LADSPA_Data == float! */
- for (i = 0; i < len; i++)
- buf[i] = SOX_SAMPLE_TO_FLOAT_32BIT(ibuf[i], effp->clips);
+ /*
+ * prepare buffer for LADSPA input
+ * deinterleave sox samples and write non-interleaved data to
+ * input_port-specific buffer locations
+ */
+ for (i = 0; i < input_len; i++) {
+ for (j = 0; j < total_input_count; j++) {
+ const sox_sample_t s = *ibuf++;
+ buf[j * input_len + i] = SOX_SAMPLE_TO_LADSPA_DATA(s, effp->clips);
+ }
+ }
- /* Connect the input port */
- l_st->desc->connect_port(l_st->handle, l_st->input_port, buf);
+ /* Connect the LADSPA input port(s) to the prepared buffers */
+ for (j = 0; j < total_input_count; j++) {
+ handle = l_st->handles[j / l_st->input_count];
+ port = l_st->inputs[j / l_st->handle_count];
+ l_st->desc->connect_port(handle, port, buf + j * input_len);
}
- /* Connect the output port if used */
- if (l_st->output_port != ULONG_MAX)
- l_st->desc->connect_port(l_st->handle, l_st->output_port, buf);
+ /* Connect the LADSPA output port(s) if used */
+ for (j = 0; j < total_output_count; j++) {
+ handle = l_st->handles[j / l_st->output_count];
+ port = l_st->outputs[j / l_st->handle_count];
+ l_st->desc->connect_port(handle, port, outbuf + j * output_len);
+ }
- /* Run the plugin */
- l_st->desc->run(l_st->handle, len);
+ /* Run the plugin for each handle */
+ for (h = 0; h < l_st->handle_count; h++)
+ l_st->desc->run(l_st->handles[h], input_len);
- /* Grab output if effect produces it */
- if (l_st->output_port != ULONG_MAX)
- /* FIXME: Assume LADSPA_Data == float! */
- for (i = 0; i < len; i++) {
- obuf[i] = SOX_FLOAT_32BIT_TO_SAMPLE(buf[i], effp->clips);
+ /* Grab output if effect produces it, re-interleaving it */
+ for (i = 0; i < output_len; i++) {
+ for (j = 0; j < total_output_count; j++) {
+ LADSPA_Data d = outbuf[j * output_len + i];
+ *obuf++ = LADSPA_DATA_TO_SOX_SAMPLE(d, effp->clips);
+ (*osamp)++;
}
+ }
+ free(outbuf);
free(buf);
}
@@ -280,24 +390,43 @@
static int sox_ladspa_stop(sox_effect_t * effp)
{
priv_t * l_st = (priv_t *)effp->priv;
+ size_t h;
- /* If needed, deactivate the plugin */
- if (l_st->desc->deactivate)
- l_st->desc->deactivate(l_st->handle);
+ for (h = 0; h < l_st->handle_count; h++) {
+ /* If needed, deactivate and cleanup the plugin */
+ if (l_st->desc->deactivate)
+ l_st->desc->deactivate(l_st->handles[h]);
+ if (l_st->desc->cleanup)
+ l_st->desc->cleanup(l_st->handles[h]);
+ }
+ free(l_st->handles);
+ l_st->handle_count = 0;
return SOX_SUCCESS;
}
+static int sox_ladspa_kill(sox_effect_t * effp)
+{
+ priv_t * l_st = (priv_t *)effp->priv;
+
+ free(l_st->control);
+ free(l_st->inputs);
+ free(l_st->outputs);
+
+ return SOX_SUCCESS;
+}
+
static sox_effect_handler_t sox_ladspa_effect = {
"ladspa",
"MODULE [PLUGIN] [ARGUMENT...]",
- SOX_EFF_GAIN,
+ SOX_EFF_MCHAN | SOX_EFF_CHAN | SOX_EFF_GAIN,
sox_ladspa_getopts,
sox_ladspa_start,
sox_ladspa_flow,
sox_ladspa_drain,
sox_ladspa_stop,
- NULL, sizeof(priv_t)
+ sox_ladspa_kill,
+ sizeof(priv_t)
};
const sox_effect_handler_t *lsx_ladspa_effect_fn(void)