shithub: sox

Download patch

ref: 58ab5ac87b86c897d9edd0cf6e8e98c0d6162bfb
parent: efde7c1aaa47350dccaed2753f6cb5e4be805697
author: Eric Wong <normalperson@yhbt.net>
date: Fri Oct 5 13:07:11 EDT 2012

ladspa: support M:N channel mapping in plugins

This allows plugins such as the LADSPA vocoder to work.

Using the formant.wav and carrier.wav samples from
http://www.sirlab.de/linux/descr_vocoder.html the following
command-line is successful:

sox -M formant.wav carrier.wav \
  -b 16 result.wav rate 44100 \
  ladspa vocoder 16 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

Using 16-bit (or higher) output reduces the noise-floor hiss
greatly since LADSPA processing is done as 32-bit float.

--- a/src/ladspa.c
+++ b/src/ladspa.c
@@ -195,18 +195,6 @@
     }
   }
 
-  /*
-   * very few LADSPA plugins support changing the number of channels,
-   * so we won't support it unless somebody has the time and resources
-   * for it.  The "mixer" plugin in cmt is one example that would fail here,
-   * but SoX already has the native capability to remix channels.
-   */
-  if (l_st->input_count != l_st->output_count) {
-    lsx_fail("mismatched input/output port count (%u != %u)",
-             (unsigned)l_st->input_count, (unsigned)l_st->output_count);
-    return SOX_EOF;
-  }
-
   /* Stop if we have any unused arguments */
   return argc? lsx_usage(effp) : SOX_SUCCESS;
 }
@@ -226,6 +214,7 @@
 
   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",
@@ -255,20 +244,22 @@
                (unsigned)l_st->input_count, effp->in_signal.channels);
       return SOX_EOF;
     }
-    if (l_st->output_count < effp->out_signal.channels) {
-      lsx_fail("fewer plugin output ports than output channels (%u < %u)",
-               (unsigned)l_st->output_count, effp->out_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);
-    if (l_st->output_count > effp->out_signal.channels)
-      lsx_warn("more plugin output ports than input channels (%u > %u)",
-               (unsigned)l_st->output_count, effp->out_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);
@@ -321,12 +312,17 @@
   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;
-  const size_t output_len = len / total_output_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;
@@ -354,7 +350,7 @@
     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, buf + j * output_len);
+      l_st->desc->connect_port(handle, port, outbuf + j * output_len);
     }
 
     /* Run the plugin for each handle */
@@ -364,11 +360,13 @@
     /* 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 = buf[j * output_len + i];
+        LADSPA_Data d = outbuf[j * output_len + i];
         *obuf++ = LADSPA_DATA_TO_SOX_SAMPLE(d, effp->clips);
+        (*osamp)++;
       }
     }
 
+    free(outbuf);
     free(buf);
   }
 
@@ -421,7 +419,7 @@
 static sox_effect_handler_t sox_ladspa_effect = {
   "ladspa",
   "MODULE [PLUGIN] [ARGUMENT...]",
-  SOX_EFF_MCHAN | SOX_EFF_GAIN,
+  SOX_EFF_MCHAN | SOX_EFF_CHAN | SOX_EFF_GAIN,
   sox_ladspa_getopts,
   sox_ladspa_start,
   sox_ladspa_flow,