shithub: sox

Download patch

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)