shithub: sox

Download patch

ref: e7b81f74082543c3175a6a670879b5584e24ff04
parent: 50b30d651e24804bca1d27739330a659a45dd2f5
author: robs <robs>
date: Fri May 25 17:25:06 EDT 2007

multi-channel effects chain

--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -95,9 +95,11 @@
   DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/soxstdint.h.cmake
   )
 
-add_library(lib${PROJECT_NAME} 8svx adpcm adpcms aifc-fmt aiff aiff-fmt al-fmt au auto avr biquad biquads cdr chorus compand compandt cvsd cvsd-fmt dat dcshift dither dvms-fmt earwax echo echos fade FFT filter flanger g711 g721 g723_24 g723_40 g72x getopt1 getopt gsm handlers hcom ima-fmt ima_rw la-fmt lpc10 lu-fmt maud mcompand misc mixer noiseprof noisered nulfile pad pan phaser pitch polyphas prc rate raw raw-fmt repeat resample reverb reverse s1-fmt s2-fmt s3-fmt s4-fmt sf silence skeleff skelform smp sndrtool soxio speed sphere stat stretch swap synth tremolo trim tx16w u1-fmt u2-fmt u3-fmt u4-fmt ul-fmt util vibro voc vol vox vox-fmt wav wve xa xmalloc soxstdint ${optional_srcs})
+add_library(lib${PROJECT_NAME} 8svx adpcm adpcms aifc-fmt aiff aiff-fmt al-fmt au auto avr biquad biquads cdr chorus compand compandt cvsd cvsd-fmt dat dcshift dither dvms-fmt earwax echo echos effects fade FFT filter flanger g711 g721 g723_24 g723_40 g72x getopt1 getopt gsm handlers hcom ima-fmt ima_rw la-fmt lpc10 lu-fmt maud mcompand misc mixer noiseprof noisered nulfile pad pan phaser pitch polyphas prc rate raw raw-fmt repeat resample reverb reverse s1-fmt s2-fmt s3-fmt s4-fmt sf silence skeleff skelform smp sndrtool soxio speed sphere stat stretch swap synth tremolo trim tx16w u1-fmt u2-fmt u3-fmt u4-fmt ul-fmt util vibro voc vol vox vox-fmt wav wve xa xmalloc soxstdint ${optional_srcs})
 add_executable(${PROJECT_NAME} ${PROJECT_NAME}.c)
 target_link_libraries(${PROJECT_NAME} lib${PROJECT_NAME} lpc10 ${optional_libs})
 add_executable(sox_sample_test sox_sample_test.c)
 add_custom_target(rec ALL ln -sf sox rec DEPENDS sox)
 add_custom_target(play ALL ln -sf sox play DEPENDS sox)
+find_program(CTAGS NAMES exuberant-ctags ctags)
+add_custom_target(tags ${CTAGS} --recurse --extra=fq ${CMAKE_CURRENT_SOURCE_DIR})
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -183,7 +183,7 @@
 	  g711.c g711.h g721.c g723_24.c g723_40.c g72x.c g72x.h vox.c vox.h	\
 	  raw.c raw.h handlers.c misc.c sox_i.h skelform.c soxio.c	\
 	  util.c xmalloc.c xmalloc.h getopt.c getopt1.c getopt.h	\
-	  soxconfig.h
+	  soxconfig.h effects.c
 libsox_la_CFLAGS = @SAMPLERATE_CFLAGS@
 libsox_la_LIBADD = @LIBGSM_LIBADD@ @SAMPLERATE_LIBS@
 
--- /dev/null
+++ b/src/effects.c
@@ -1,0 +1,284 @@
+/*
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library.  If not, write to the Free Software Foundation,
+ * Fifth Floor, 51 Franklin Street, Boston, MA 02111-1301, USA.
+ */
+
+/* SoX Effects chain   (c) 2007 robs@users.sourceforge.net */
+
+#include "sox_i.h"
+
+struct sox_effect * effects[MAX_EFFECTS];
+unsigned neffects;
+
+
+void add_effect(struct sox_effect * e, sox_signalinfo_t * in, sox_signalinfo_t * out, int * effects_mask)
+{
+  unsigned f, flows;
+
+  *effects_mask = sox_updateeffect(e, in, out, *effects_mask);
+
+  flows = (e->h->flags & SOX_EFF_MCHAN)? 1 : e->ininfo.channels;
+
+  effects[neffects] = xcalloc(flows, sizeof(effects[neffects][0]));
+  effects[neffects][0] = *e;
+  effects[neffects][0].flows = flows;
+
+  for (f = 1; f < flows; ++f)
+    effects[neffects][f] = effects[neffects][0];
+
+  ++neffects;
+}
+
+static void stop_effect(unsigned e)
+{
+  unsigned i;
+
+  sox_size_t clips = 0;
+  int (*stop)(eff_t effp) =
+     effects[e][0].h->stop? effects[e][0].h->stop : sox_effect_nothing;
+
+  for (i = 0; i < effects[e][0].flows; ++i) {
+    stop(&effects[e][i]);
+    clips += effects[e][i].clips;
+  }
+  if (clips != 0)
+    sox_warn("'%s' clipped %u samples; decrease volume?",effects[e][0].name,clips);
+}
+
+void stop_effects(void)
+{
+  unsigned e;
+  for (e = 0; e < neffects; ++e)
+    stop_effect(e);
+}
+
+static void kill_effect(unsigned e)
+{
+  int (*kill)(eff_t effp) =
+     effects[e][0].h->kill? effects[e][0].h->kill : sox_effect_nothing;
+
+  kill(&effects[e][0]);  /* One kill for all flows */
+}
+
+void kill_effects(void)
+{
+  unsigned e;
+  for (e = 0; e < neffects; ++e)
+    kill_effect(e);
+}
+
+int start_effects(void)
+{
+  unsigned i, j;
+  int ret = SOX_SUCCESS;
+
+  for (i = 0; i < neffects; ++i) {
+    struct sox_effect * e = &effects[i][0];
+    sox_bool is_always_null = (e->h->flags & SOX_EFF_NULL) != 0;
+    int (*start)(eff_t effp) = e->h->start? e->h->start : sox_effect_nothing;
+
+    if (is_always_null)
+      sox_report("'%s' has no effect (is a proxy effect)", e->name);
+    else {
+      e->clips = 0;
+      ret = start(e);
+      if (ret == SOX_EFF_NULL)
+        sox_warn("'%s' has no effect in this configuration", e->name);
+      else if (ret != SOX_SUCCESS)
+        return SOX_EOF;
+    }
+    if (is_always_null || ret == SOX_EFF_NULL) { /* remove from the chain */
+      kill_effect(i);
+      free(effects[i]);
+      --neffects;
+      for (j = i--; j < neffects; ++j)
+        effects[j] = effects[j + 1];
+    }
+    else for (j = 1; j < effects[i][0].flows; ++j) {
+      effects[i][j].clips = 0;
+      if (start(&effects[i][j]) != SOX_SUCCESS)
+        return SOX_EOF;
+    }
+  }
+  for (i = 0; i < neffects; ++i) {
+    struct sox_effect * e = &effects[i][0];
+    sox_report("Effects chain: %-10s %uHz %u channels %s",
+        e->name, e->ininfo.rate, e->ininfo.channels,
+        (e->h->flags & SOX_EFF_MCHAN)? "(multi)" : "");
+  }
+  return SOX_SUCCESS;
+}
+
+static sox_ssample_t **ibufc, **obufc; /* Channel interleave buffers */
+
+static int flow_effect(unsigned e)
+{
+  sox_size_t i, f, idone, odone;
+  const sox_ssample_t *ibuf;
+  int effstatus = SOX_SUCCESS;
+  int (*flow)(eff_t, sox_ssample_t const*, sox_ssample_t*, sox_size_t*, sox_size_t*) =
+    effects[e][0].h->flow? effects[e][0].h->flow : sox_effect_nothing_flow;
+
+  idone = effects[e - 1][0].olen - effects[e - 1][0].odone;
+  odone = sox_bufsiz - effects[e][0].olen;
+
+  if (effects[e][0].flows == 1)   /* Run effect on all channels at once */
+    effstatus = flow(&effects[e][0],
+      &effects[e - 1][0].obuf[effects[e - 1][0].odone],
+      &effects[e][0].obuf[effects[e][0].olen], &idone, &odone);
+  else {                         /* Run effect on each channel individually */
+    sox_ssample_t *obuf = &effects[e][0].obuf[effects[e][0].olen];
+    sox_size_t idone_last, odone_last;
+
+    ibuf = &effects[e - 1][0].obuf[effects[e - 1][0].odone];
+    for (i = 0; i < idone; i += effects[e][0].flows)
+      for (f = 0; f < effects[e][0].flows; ++f)
+        ibufc[f][i / effects[e][0].flows] = *ibuf++;
+
+    for (f = 0; f < effects[e][0].flows; ++f) {
+      sox_size_t idonec = idone / effects[e][0].flows;
+      sox_size_t odonec = odone / effects[e][0].flows;
+      int eff_status_c =
+        flow(&effects[e][f], ibufc[f], obufc[f], &idonec, &odonec);
+      if (f && (idonec != idone_last || odonec != odone_last)) {
+        sox_fail("'%s' flowed asymmetrically!", effects[e][0].name);
+        effstatus = SOX_EOF;
+      }
+      idone_last = idonec;
+      odone_last = odonec;
+
+      if (eff_status_c != SOX_SUCCESS)
+        effstatus = SOX_EOF;
+    }
+
+    for (i = 0; i < odone_last; ++i)
+      for (f = 0; f < effects[e][0].flows; ++f)
+        *obuf++ = obufc[f][i];
+
+    idone = f * idone_last;
+    odone = f * odone_last;
+  }
+  effects[e - 1][0].odone += idone;
+  if (effects[e - 1][0].odone == effects[e - 1][0].olen) /* Can reuse this buffer? */
+    effects[e - 1][0].odone = effects[e - 1][0].olen = 0;
+
+  effects[e][0].olen += odone;
+
+  return effstatus == SOX_SUCCESS? SOX_SUCCESS : SOX_EOF;
+}
+
+static int drain_effect(unsigned e)
+{
+  sox_size_t i, f, odone;
+  int effstatus = SOX_SUCCESS;
+  int (*drain)(eff_t, sox_ssample_t*, sox_size_t*) =
+    effects[e][0].h->drain? effects[e][0].h->drain : sox_effect_nothing_drain;
+
+  odone = sox_bufsiz - effects[e][0].olen;
+
+  if (effects[e][0].flows == 1)   /* Run effect on all channels at once */
+    effstatus = drain(&effects[e][0],
+      &effects[e][0].obuf[effects[e][0].olen], &odone);
+  else {                         /* Run effect on each channel individually */
+    sox_ssample_t *obuf = &effects[e][0].obuf[effects[e][0].olen];
+    sox_size_t odone_last;
+
+    for (f = 0; f < effects[e][0].flows; ++f) {
+      sox_size_t odonec = odone / effects[e][0].flows;
+      int eff_status_c =
+        drain(&effects[e][f], obufc[f], &odonec);
+      if (f && (odonec != odone_last)) {
+        sox_fail("'%s' drained asymmetrically!", effects[e][0].name);
+        effstatus = SOX_EOF;
+      }
+      odone_last = odonec;
+
+      if (eff_status_c != SOX_SUCCESS)
+        effstatus = SOX_EOF;
+    }
+
+    for (i = 0; i < odone_last; ++i)
+      for (f = 0; f < effects[e][0].flows; ++f)
+        *obuf++ = obufc[f][i];
+    odone = f * odone_last;
+  }
+  if (!odone)
+    effstatus = SOX_EOF;
+
+  effects[e][0].olen += odone;
+
+  return effstatus == SOX_SUCCESS? SOX_SUCCESS : SOX_EOF;
+}
+
+int flow_effects(void (* update_status)(sox_bool), sox_bool * user_abort)
+{
+  int e, source_e = 0;               /* effect indices */
+  int flow_status = SOX_SUCCESS;
+  sox_bool draining = sox_true;
+  sox_size_t f, max_flows = 0;
+
+  for (e = 0; e < (int)neffects; ++e) {
+    effects[e][0].obuf = xmalloc(sox_bufsiz * sizeof(effects[e][0].obuf[0]));
+    effects[e][0].odone = effects[e][0].olen = 0;
+    max_flows = max(max_flows, effects[e][0].flows);
+  }
+
+  ibufc = xcalloc(max_flows, sizeof(*ibufc));
+  obufc = xcalloc(max_flows, sizeof(*obufc));
+  for (f = 0; f < max_flows; ++f) {
+    ibufc[f] = xcalloc(sox_bufsiz / 2, sizeof(ibufc[f][0]));
+    obufc[f] = xcalloc(sox_bufsiz / 2, sizeof(obufc[f][0]));
+  }
+
+  --e;
+  while (source_e < (int)neffects) {
+    if (e == source_e && (draining || effects[e - 1][0].odone == effects[e - 1][0].olen)) {
+      if (drain_effect(e) == SOX_EOF) {
+        ++source_e;
+        draining = sox_false;
+      }
+    } else if (flow_effect(e) == SOX_EOF) {
+      flow_status = SOX_EOF;
+      source_e = e;
+      draining = sox_true;
+    }
+    if (effects[e][0].odone < effects[e][0].olen)
+      ++e;
+    else if (--e < source_e)
+      e = source_e;
+
+    update_status(*user_abort || source_e == (int)neffects);
+    
+    if (*user_abort) /* Don't get stuck in this loop. */
+      return SOX_EOF;
+  }
+
+  for (f = 0; f < max_flows; ++f) {
+    free(ibufc[f]);
+    free(obufc[f]);
+  }
+  free(obufc);
+  free(ibufc);
+
+  for (e = 0; e < (int)neffects; ++e)
+    free(effects[e][0].obuf);
+
+  return flow_status;
+}
+
+void delete_effects(void)
+{
+  while (neffects)
+    free(effects[--neffects]);
+}
--- a/src/sox.c
+++ b/src/sox.c
@@ -72,7 +72,9 @@
 #endif
 
 static sox_bool play = sox_false, rec = sox_false;
+#ifdef HAVE_LTDL_H
 static sox_bool plugins_initted = sox_false;
+#endif
 static enum {sox_sequence, sox_concatenate, sox_mix, sox_merge} combine_method = sox_concatenate;
 static sox_size_t mixing_clips = 0;
 static sox_bool repeatable_random = sox_false;  /* Whether to invoke srand. */
@@ -91,8 +93,6 @@
 static unsigned long read_wide_samples = 0;
 static unsigned long output_samples = 0;
 
-static sox_ssample_t *ibufl, *ibufr, *obufl, *obufr; /* Left/right interleave buffers */
-
 typedef struct file_info
 {
   char *filename;
@@ -112,15 +112,6 @@
 static int process(void);
 static void update_status(sox_bool all_done);
 static void report_file_info(file_t f);
-static void parse_effects(int argc, char **argv);
-static void build_effects_table(void);
-static int start_all_effects(void);
-static int flow_effect_out(void);
-static int flow_effect(unsigned);
-static int drain_effect_out(void);
-static int drain_effect(unsigned);
-static void stop_effects(void);
-static void kill_effects(void);
 
 #define MAX_INPUT_FILES 32
 #define MAX_FILES MAX_INPUT_FILES + 2 /* 1 output file plus record input */
@@ -138,28 +129,11 @@
  * converting a mono file to stereo.  This allows the resample to work
  * on half the data.
  *
- * Real effects table only needs to be 2 entries bigger then the user
- * specified table.  This is because at most we will need to add
- * a resample effect and a channel averaging effect.
+ * User effects table must be 4 entries smaller then the real
+ * effects table.  This is because at most we will need to add
+ * a resample effect, a channel mixing effect, the input, and the output.
  */
-#define MAX_EFF 16
-#define MAX_USER_EFF 14
-
-/*
- * efftab[0] is a dummy entry used only as an input buffer for
- * reading input data into.
- *
- * If one was to support effects for quad-channel files, there would
- * need to be an effect table for each channel to handle effects
- * that don't set SOX_EFF_MCHAN.
- */
-
-static struct sox_effect efftab[MAX_EFF]; /* left/mono channel effects */
-static struct sox_effect efftabR[MAX_EFF];/* right channel effects */
-static unsigned neffects;                     /* # of effects to run on data */
-static unsigned input_eff;                    /* last input effect with data */
-static sox_bool input_eff_eof;                /* has input_eff reached EOF? */
-
+#define MAX_USER_EFF (MAX_EFFECTS - 4)
 static struct sox_effect user_efftab[MAX_USER_EFF];
 static unsigned nuser_effects;
 
@@ -194,7 +168,6 @@
 static void cleanup(void)
 {
   size_t i;
-  int ret;
 
   /* Close the input and output files before exiting. */
   for (i = 0; i < input_count; i++) {
@@ -249,7 +222,7 @@
   return f;
 }
 
-static void set_device(file_t f, sox_bool recording)
+static void set_device(file_t f, sox_bool recording UNUSED)
 {
 #ifdef HAVE_LIBAO
   if (!recording) {
@@ -454,6 +427,37 @@
   }
 }
 
+static void parse_effects(int argc, char **argv)
+{
+  int argc_effect;
+
+  for (nuser_effects = 0; optind < argc; ++nuser_effects) {
+    struct sox_effect * e = &user_efftab[nuser_effects];
+    int (*getopts)(eff_t effp, int argc, char *argv[]);
+
+    if (nuser_effects >= MAX_USER_EFF) {
+      sox_fail("too many effects specified (at most %i allowed)", MAX_USER_EFF);
+      exit(1);
+    }
+
+    argc_effect = sox_geteffect_opt(e, argc - optind, &argv[optind]);
+    if (argc_effect == SOX_EOF) {
+      sox_fail("Effect `%s' does not exist!", argv[optind]);
+      exit(1);
+    }
+    if (e->h->flags & SOX_EFF_DEPRECATED)
+      sox_warn("Effect `%s' is deprecated and may be removed in a future release; please refer to the manual sox(1) for an alternative effect", e->name);
+
+    optind++; /* Skip past effect name */
+    e->global_info = &effects_global_info;
+    getopts = e->h->getopts?  e->h->getopts : sox_effect_nothing_getopts;
+    if (getopts(e, argc_effect, &argv[optind]) == SOX_EOF)
+      exit(2);
+
+    optind += argc_effect; /* Skip past the effect arguments */
+  }
+}
+
 /* FIXME: Use vasprintf */
 #ifdef HAVE_LTDL_H
 #define MAX_NAME_LEN 1024
@@ -521,12 +525,6 @@
      if desired) */
   find_formats();
   
-  /* Allocate buffers, size of which may have been set by --buffer */
-  ibufl = xcalloc(sox_bufsiz / 2, sizeof(sox_ssample_t));
-  obufl = xcalloc(sox_bufsiz / 2, sizeof(sox_ssample_t));
-  ibufr = xcalloc(sox_bufsiz / 2, sizeof(sox_ssample_t));
-  obufr = xcalloc(sox_bufsiz / 2, sizeof(sox_ssample_t));
-
   /* Make sure we got at least the required # of input filenames */
   input_count = file_count ? file_count - 1 : 0;
   if (input_count < (combine_method <= sox_concatenate ? 1 : 2))
@@ -590,7 +588,7 @@
   }
 
   for (i = 0; i < input_count; i++) {
-    int j;
+    unsigned j;
     for (j =0; j < nuser_effects && !files[i]->desc->signal.channels; ++j)
       files[i]->desc->signal.channels = user_efftab[j].ininfo.channels;
     if (!files[i]->desc->signal.channels)
@@ -635,6 +633,8 @@
   if (show_progress) {
     if (user_abort)
       fprintf(stderr, "Aborted.\n");
+    else if (user_skip)
+      fprintf(stderr, "Skipped.\n");
     else
       fprintf(stderr, "Done.\n");
   }
@@ -762,9 +762,9 @@
    * This hack is a huge time savings when trimming
    * gigs of audio data into managable chunks
    */ 
-  if (input_count == 1 && neffects > 1 && strcmp(efftab[1].name, "trim") == 0) {
+  if (input_count == 1 && neffects > 1 && strcmp(effects[1][0].name, "trim") == 0) {
     if ((files[0]->desc->h->flags & SOX_FILE_SEEK) && files[0]->desc->seekable){
-      sox_size_t offset = sox_trim_get_start(&efftab[1]);
+      sox_size_t offset = sox_trim_get_start(&effects[1][0]);
       if (sox_seek(files[0]->desc, offset, SOX_SEEK_SET) != SOX_EOF) { 
         read_wide_samples = offset / files[0]->desc->signal.channels;
         /* Assuming a failed seek stayed where it was.  If the 
@@ -771,7 +771,7 @@
          * seek worked then reset the start location of 
          * trim so that it thinks user didn't request a skip.
          */ 
-        sox_trim_clear_start(&efftab[1]);
+        sox_trim_clear_start(&effects[1][0]);
       }    
     }        
   }    
@@ -1029,6 +1029,10 @@
 
 static void progress_to_file(file_t f)
 {
+  if (user_skip) {
+    user_skip = sox_false;
+    fprintf(stderr, "Skipped.\n");
+  }
   read_wide_samples = 0;
   input_wide_samples = f->desc->length / f->desc->signal.channels;
   if (show_progress && (sox_output_verbosity_level < 3 ||
@@ -1070,9 +1074,9 @@
     files[i]->desc->signal.rate     == files[i - 1]->desc->signal.rate;
 }
 
-static sox_size_t sox_read_wide(ft_t desc, sox_ssample_t * buf)
+static sox_size_t sox_read_wide(ft_t desc, sox_ssample_t * buf, sox_size_t max)
 {
-  sox_size_t len = sox_bufsiz / combiner.channels;
+  sox_size_t len = max / combiner.channels;
   len = sox_read(desc, buf, len * desc->signal.channels) / desc->signal.channels;
   if (!len && desc->sox_errno)
     sox_fail("%s: %s (%s)", desc->filename, desc->sox_errstr, strerror(desc->sox_errno));
@@ -1090,293 +1094,134 @@
     }
 }
 
-/*
- * Process:   Input(s) -> Balancing -> Combiner -> Effects -> Output
- */
-
-static int process(void) {
-  int flowstatus = 0;
-  sox_size_t e, ws, s, i;
-  sox_size_t ilen[MAX_INPUT_FILES];
+typedef struct input_combiner
+{
   sox_ssample_t *ibuf[MAX_INPUT_FILES];
-  sox_bool known_length = combine_method != sox_sequence;
-  sox_size_t olen = 0;
+} * input_combiner_t;
 
-  combiner = files[current_input]->desc->signal;
-  if (combine_method == sox_sequence) {
-    if (!current_input) for (i = 0; i < input_count; i++)
-      report_file_info(files[i]);
-  } else {
-    sox_size_t total_channels = 0;
-    sox_size_t min_channels = SOX_SIZE_MAX;
-    sox_size_t max_channels = 0;
-    sox_size_t min_rate = SOX_SIZE_MAX;
-    sox_size_t max_rate = 0;
+assert_static(sizeof(struct input_combiner) <= SOX_MAX_EFFECT_PRIVSIZE,
+              /* else */ input_combiner_PRIVSIZE_too_big);
 
-    for (i = 0; i < input_count; i++) { /* Report all inputs, then check */
-      report_file_info(files[i]);
-      total_channels += files[i]->desc->signal.channels;
-      min_channels = min(min_channels, files[i]->desc->signal.channels);
-      max_channels = max(max_channels, files[i]->desc->signal.channels);
-      min_rate = min(min_rate, files[i]->desc->signal.rate);
-      max_rate = max(max_rate, files[i]->desc->signal.rate);
-      known_length = known_length && files[i]->desc->length != 0;
-      if (combine_method == sox_concatenate)
-        olen += files[i]->desc->length / files[i]->desc->signal.channels;
-      else
-        olen = max(olen, files[i]->desc->length / files[i]->desc->signal.channels);
-    }
-    if (min_rate != max_rate)
-      sox_fail("Input files must have the same sample-rate");
-    if (min_channels != max_channels) {
-      if (combine_method == sox_concatenate) {
-        sox_fail("Input files must have the same # channels");
-        exit(1);
-      } else if (combine_method == sox_mix)
-        sox_warn("Input files don't have the same # channels");
-    }
-    if (min_rate != max_rate)
-      exit(1);
+static int combiner_start(eff_t effp)
+{
+  input_combiner_t z = (input_combiner_t) effp->priv;
+  sox_size_t ws, i;
 
-    combiner.channels = 
-      combine_method == sox_merge? total_channels : max_channels;
-  }
-
-  ofile->signal = ofile_signal;
-  if (ofile->signal.rate == 0)
-    ofile->signal.rate = combiner.rate;
-  if (ofile->signal.size == -1)
-    ofile->signal.size = combiner.size;
-  if (ofile->signal.encoding == SOX_ENCODING_UNKNOWN)
-    ofile->signal.encoding = combiner.encoding;
-  if (ofile->signal.channels == 0)
-    ofile->signal.channels = combiner.channels;
-
-  combiner.rate = combiner.rate * effects_global_info.speed + .5;
-
-  for (i = 0; i < nuser_effects; i++)
-    known_length = known_length && !(user_efftab[i].h->flags & SOX_EFF_LENGTH);
-
-  if (!known_length)
-    olen = 0;
-
-  {
-    sox_loopinfo_t loops[SOX_MAX_NLOOPS];
-    double factor;
-    int i;
-    char const *comment = NULL;
-
-    if (ofile->comment == NULL)
-      comment = files[0]->desc->comment ? files[0]->desc->comment : "Processed by SoX";
-    else if (*ofile->comment != '\0')
-        comment = ofile->comment;
-
-    /*
-     * copy loop info, resizing appropriately
-     * it's in samples, so # channels don't matter
-     * FIXME: This doesn't work for multi-file processing or
-     * effects that change file length.
-     */
-    factor = (double) ofile->signal.rate / combiner.rate;
-    for (i = 0; i < SOX_MAX_NLOOPS; i++) {
-      loops[i].start = files[0]->desc->loops[i].start * factor;
-      loops[i].length = files[0]->desc->loops[i].length * factor;
-      loops[i].count = files[0]->desc->loops[i].count;
-      loops[i].type = files[0]->desc->loops[i].type;
-    }
-
-    ofile->desc = sox_open_write(overwrite_permitted,
-                          ofile->filename,
-                          &ofile->signal,
-                          ofile->filetype,
-                          comment,
-                          olen,
-                          &files[0]->desc->instr,
-                          loops);
-
-    if (!ofile->desc)
-      /* sox_open_write() will call sox_warn for most errors.
-       * Rely on that printing something. */
-      exit(2);
-
-    /* When writing to an audio device, auto turn on the
-     * progress display to match behavior of ogg123,
-     * unless the user requested us not to display anything. */
-    if (show_progress == SOX_OPTION_DEFAULT)
-      show_progress = (ofile->desc->h->flags & SOX_FILE_DEVICE) != 0 &&
-                      (ofile->desc->h->flags & SOX_FILE_PHONY) == 0;
-
-    report_file_info(ofile);
-  }
-
-  build_effects_table();
-
-  if (start_all_effects() != SOX_SUCCESS)
-    exit(2); /* The failing effect should have displayed an error message */
-
-  /* Allocate output buffers for effects */
-  for (e = 0; e < neffects; e++) {
-    efftab[e].obuf = (sox_ssample_t *)xmalloc(sox_bufsiz * sizeof(sox_ssample_t));
-    if (efftabR[e].name)
-      efftabR[e].obuf = (sox_ssample_t *)xmalloc(sox_bufsiz * sizeof(sox_ssample_t));
-  }
-
   if (combine_method <= sox_concatenate)
     progress_to_file(files[current_input]);
   else {
     ws = 0;
     for (i = 0; i < input_count; i++) {
-      ibuf[i] = (sox_ssample_t *)xmalloc(sox_bufsiz * sizeof(sox_ssample_t));
+      z->ibuf[i] = (sox_ssample_t *)xmalloc(sox_bufsiz * sizeof(sox_ssample_t));
       progress_to_file(files[i]);
       ws = max(ws, input_wide_samples);
     }
     input_wide_samples = ws; /* Output length is that of longest input file. */
   }
+  return SOX_SUCCESS;
+}
 
-  optimize_trim();
+static int combiner_drain(eff_t effp, sox_ssample_t * obuf, sox_size_t * osamp)
+{
+  input_combiner_t z = (input_combiner_t) effp->priv;
+  sox_size_t ws, s, i;
+  sox_size_t ilen[MAX_INPUT_FILES];
+  sox_size_t olen = 0;
 
-  input_eff = 0;
-  input_eff_eof = sox_false;
-
-  /* mark chain as empty */
-  for(e = 1; e < neffects; e++)
-    efftab[e].odone = efftab[e].olen = 0;
-
-  signal(SIGINT, sigint);
-  signal(SIGTERM, sigint);
-  /* Run input data through effects until EOF (olen == 0) or user-abort. */
-  do {
-    efftab[0].olen = 0;
-    if (combine_method <= sox_concatenate) {
-      if (!user_skip)
-        efftab[0].olen = sox_read_wide(files[current_input]->desc, efftab[0].obuf);
-      if (efftab[0].olen == 0) {   /* If EOF, go to the next input file. */
-        update_status(sox_true);
-        if (user_skip) {
-          user_skip = sox_false;
-          fprintf(stderr, "Skipped.\n");
-        }
-        if (++current_input < input_count) {
-          if (combine_method == sox_sequence && !can_segue(current_input))
-            break;
-          progress_to_file(files[current_input]);
-          continue;
-        }
+  if (combine_method <= sox_concatenate) while (sox_true) {
+    if (!user_skip)
+      olen = sox_read_wide(files[current_input]->desc, obuf, *osamp);
+    if (olen == 0) {   /* If EOF, go to the next input file. */
+      if (++current_input < input_count) {
+        if (combine_method == sox_sequence && !can_segue(current_input))
+          break;
+        progress_to_file(files[current_input]);
+        continue;
       }
-      balance_input(efftab[0].obuf, efftab[0].olen, files[current_input]);
-    } else {
-      sox_ssample_t * p = efftab[0].obuf;
-      for (i = 0; i < input_count; ++i) {
-        ilen[i] = sox_read_wide(files[i]->desc, ibuf[i]);
-        balance_input(ibuf[i], ilen[i], files[i]);
-        efftab[0].olen = max(efftab[0].olen, ilen[i]);
-      }
-      for (ws = 0; ws < efftab[0].olen; ++ws) /* wide samples */
-        if (combine_method == sox_mix) {          /* sum samples together */
-          for (s = 0; s < combiner.channels; ++s, ++p) {
-            *p = 0;
-            for (i = 0; i < input_count; ++i)
-              if (ws < ilen[i] && s < files[i]->desc->signal.channels) {
-                /* Cast to double prevents integer overflow */
-                double sample = *p + (double)ibuf[i][ws * files[i]->desc->signal.channels + s];
-                *p = SOX_ROUND_CLIP_COUNT(sample, mixing_clips);
-            }
-          }
-        } else { /* sox_merge: like a multi-track recorder */
+    }
+    balance_input(obuf, olen, files[current_input]);
+    break;
+  } else {
+    sox_ssample_t * p = obuf;
+    for (i = 0; i < input_count; ++i) {
+      ilen[i] = sox_read_wide(files[i]->desc, z->ibuf[i], *osamp);
+      balance_input(z->ibuf[i], ilen[i], files[i]);
+      olen = max(olen, ilen[i]);
+    }
+    for (ws = 0; ws < olen; ++ws) /* wide samples */
+      if (combine_method == sox_mix) {          /* sum samples together */
+        for (s = 0; s < effp->ininfo.channels; ++s, ++p) {
+          *p = 0;
           for (i = 0; i < input_count; ++i)
-            for (s = 0; s < files[i]->desc->signal.channels; ++s)
-              *p++ = (ws < ilen[i]) * ibuf[i][ws * files[i]->desc->signal.channels + s];
-      }
+            if (ws < ilen[i] && s < files[i]->desc->signal.channels) {
+              /* Cast to double prevents integer overflow */
+              double sample = *p + (double)z->ibuf[i][ws * files[i]->desc->signal.channels + s];
+              *p = SOX_ROUND_CLIP_COUNT(sample, mixing_clips);
+          }
+        }
+      } else { /* sox_merge: like a multi-track recorder */
+        for (i = 0; i < input_count; ++i)
+          for (s = 0; s < files[i]->desc->signal.channels; ++s)
+            *p++ = (ws < ilen[i]) * z->ibuf[i][ws * files[i]->desc->signal.channels + s];
     }
-    if (efftab[0].olen == 0)
-      break;
+  }
+  read_wide_samples += olen;
+  olen *= effp->ininfo.channels;
+  *osamp = olen;
+  return olen? SOX_SUCCESS : SOX_EOF;
+}
 
-    efftab[0].odone = 0;
-    read_wide_samples += efftab[0].olen;
-    efftab[0].olen *= combiner.channels;
-    flowstatus = flow_effect_out();
-    update_status(user_abort || ofile->desc->sox_errno || flowstatus);
+static int combiner_stop(eff_t effp)
+{
+  input_combiner_t z = (input_combiner_t) effp->priv;
+  sox_size_t i;
 
-    /* Quit reading/writing on user aborts.  This will close
-     * the files nicely as if an EOF was reached on read. */
-    if (user_abort)
-      break;
-
-    /* If there's an error, don't try to write more. */
-    if (ofile->desc->sox_errno)
-      break;
-  } while (flowstatus == 0);
-
-  /* Drain the effects; don't write if output is indicating errors. */
-  if (ofile->desc->sox_errno == 0)
-    drain_effect_out();
-
   if (combine_method > sox_concatenate)
     /* Free input buffers now that they are not used */
     for (i = 0; i < input_count; i++)
-      free(ibuf[i]);
+      free(z->ibuf[i]);
 
-  /* Free output buffers now that they won't be used */
-  for (e = 0; e < neffects; e++) {
-    free(efftab[e].obuf);
-    free(efftabR[e].obuf);
-  }
-
-  /* N.B. more data may be written during stop_effects */
-  stop_effects();
-  return flowstatus;
+  return SOX_SUCCESS;
 }
 
-static void parse_effects(int argc, char **argv)
+static sox_effect_t const * input_combiner_effect_fn(void)
 {
-  int argc_effect;
+  static sox_effect_t driver = {
+    "input", 0, SOX_EFF_MCHAN,
+    0, combiner_start, 0, combiner_drain, combiner_stop, 0
+  };
+  return &driver;
+}
 
-  for (nuser_effects = 0; optind < argc; ++nuser_effects) {
-    struct sox_effect * e = &user_efftab[nuser_effects];
-    int (*getopts)(eff_t effp, int argc, char *argv[]);
-
-    if (nuser_effects >= MAX_USER_EFF) {
-      sox_fail("too many effects specified (at most %i allowed)", MAX_USER_EFF);
-      exit(1);
+static int output_flow(eff_t effp UNUSED, sox_ssample_t const * ibuf,
+    sox_ssample_t * obuf UNUSED, sox_size_t * isamp, sox_size_t * osamp)
+{
+  size_t len;
+  for (*osamp = *isamp; *osamp; ibuf += len, *osamp -= len) {
+    len = sox_write(ofile->desc, ibuf, *osamp);
+    if (len == 0) {
+      sox_warn("Error writing: %s", ofile->desc->sox_errstr);
+      return SOX_EOF;
     }
-
-    argc_effect = sox_geteffect_opt(e, argc - optind, &argv[optind]);
-    if (argc_effect == SOX_EOF) {
-      sox_fail("Effect `%s' does not exist!", argv[optind]);
-      exit(1);
-    }
-    if (e->h->flags & SOX_EFF_DEPRECATED)
-      sox_warn("Effect `%s' is deprecated and may be removed in a future release; please refer to the manual sox(1) for an alternative effect", e->name);
-
-    optind++; /* Skip past effect name */
-    e->global_info = &effects_global_info;
-    getopts = e->h->getopts?  e->h->getopts : sox_effect_nothing_getopts;
-    if (getopts(e, argc_effect, &argv[optind]) == SOX_EOF)
-      exit(2);
-
-    optind += argc_effect; /* Skip past the effect arguments */
+    if (user_abort) /* Don't get stuck in this loop. */
+      return SOX_EOF;
   }
+  output_samples += *isamp / ofile->desc->signal.channels;
+  return SOX_SUCCESS;
 }
 
-static void add_effect(int * effects_mask)
+static sox_effect_t const * output_effect_fn(void)
 {
-  struct sox_effect * e = &efftab[neffects];
-
-  /* Copy format info to effect table */
-  *effects_mask =
-    sox_updateeffect(e, &combiner, &ofile->desc->signal, *effects_mask);
-
-  /* If this effect can't handle multiple channels then account for this. */
-  if (e->ininfo.channels > 1 && !(e->h->flags & SOX_EFF_MCHAN))
-    memcpy(&efftabR[neffects], e, sizeof(*e));
-  else memset(&efftabR[neffects], 0, sizeof(*e));
-
-  ++neffects;
+  static sox_effect_t driver = {
+    "output", 0, SOX_EFF_MCHAN, 0, 0, output_flow, 0, 0, 0
+  };
+  return &driver;
 }
 
 static void add_default_effect(char const * name, int * effects_mask)
 {
-  struct sox_effect * e = &efftab[neffects];
+  struct sox_effect eff;
+  struct sox_effect * e = &eff;
   int (*getopts)(eff_t effp, int argc, char *argv[]);
 
   /* Find effect and update initial pointers */
@@ -1388,12 +1233,12 @@
   if (getopts(e, 0, NULL) == SOX_EOF)
     exit(2);
 
-  add_effect(effects_mask);
+  add_effect(e, &combiner, &ofile->desc->signal, effects_mask);
 }
 
 /* If needed effects are not given, auto-add at (performance) optimal point.
  */
-static void build_effects_table(void)
+static void add_effects(void)
 {
   unsigned i;
   int effects_mask = 0;
@@ -1401,6 +1246,7 @@
   sox_bool need_chan = combiner.channels != ofile->desc->signal.channels;
   int user_mchan = -1;
   sox_size_t channels = combiner.channels;
+  struct sox_effect eff;
 
   { /* Check if we have to add effects to change rate/chans or if the
        user has specified effects to do this, in which case, check if
@@ -1429,12 +1275,11 @@
     }
   }
 
-  /* --------- add the effects ------------------------ */
+  eff.h = input_combiner_effect_fn();
+  eff.name = eff.h->name;
+  add_effect(&eff, &combiner, &ofile->desc->signal, &effects_mask);
 
-  /* efftab[0] is always the input stream and always exists */
-  neffects = 1;
-
-  /* Copy user specified effects into the real efftab */
+  /* Copy user specified effects into the real effects */
   for (i = 0; i <= nuser_effects; i++) {
     /* If reducing channels, it's faster to do so before all other effects: */
     if ((int)i > user_mchan && need_chan && combiner.channels > ofile->desc->signal.channels) {
@@ -1445,432 +1290,164 @@
     /* If reducing rate, it's faster to do so before all other effects
      * (except reducing channels): */
     if (need_rate)
-      if (i == nuser_effects || (channels <= 2 && combiner.rate > ofile->desc->signal.rate)) {
+      if (i == nuser_effects || combiner.rate > ofile->desc->signal.rate) {
         add_default_effect("resample", &effects_mask);
         need_rate = sox_false;
       }
-    if (i < nuser_effects) {
-      memcpy(&efftab[neffects], &user_efftab[i], sizeof(efftab[0]));
-      add_effect(&effects_mask);
-    }
+    if (i < nuser_effects)
+      add_effect(&user_efftab[i], &combiner, &ofile->desc->signal, &effects_mask);
   }
   if (need_chan)
     add_default_effect("mixer", &effects_mask);
-}
 
-static int start_all_effects(void)
-{
-  unsigned i, j;
-  int ret = SOX_SUCCESS;
-
-  for (i = 1; i < neffects; i++) {
-    struct sox_effect * e = &efftab[i];
-    sox_bool is_always_null = (e->h->flags & SOX_EFF_NULL) != 0;
-    int (*start)(eff_t effp) = e->h->start? e->h->start : sox_effect_nothing;
-
-    if (is_always_null)
-      sox_report("'%s' has no effect (is a proxy effect)", e->name);
-    else {
-      e->clips = 0;
-      ret = start(e);
-      if (ret == SOX_EFF_NULL)
-        sox_warn("'%s' has no effect in this configuration", e->name);
-      else if (ret != SOX_SUCCESS)
-        return SOX_EOF;
-    }
-    if (is_always_null || ret == SOX_EFF_NULL) { /* remove from the chain */
-      int (*kill)(eff_t effp) = e->h->kill? e->h->kill: sox_effect_nothing;
-
-      /* No left & right kill as there is no left & right getopts */
-      kill(e);
-      --neffects;
-      for (j = i--; j < neffects; ++j) {
-        efftab[j] = efftab[j + 1];
-        efftabR[j] = efftabR[j + 1];
-      }
-    }
-    /* No null checks here; the left channel looks after this */
-    else if (efftabR[i].name) {
-      efftabR[i].clips = 0;
-      if (start(&efftabR[i]) != SOX_SUCCESS)
-        return SOX_EOF;
-    }
-  }
-  for (i = 1; i < neffects; ++i) {
-    struct sox_effect * e = &efftab[i];
-    if (e->ininfo.channels > 2 && !(e->h->flags & SOX_EFF_MCHAN)) {
-      sox_fail("Sorry, effect '%s' cannot handle multiple channels, and SoX can only help out in the case of 2 channels", e->name);
-      return SOX_EOF;
-    }
-    sox_report("Effects chain: %-10s %-6s %uHz", e->name,
-        e->ininfo.channels < 2 ? "mono" :
-        (e->h->flags & SOX_EFF_MCHAN)? "multi" : "stereo", e->ininfo.rate);
-  }
-  return SOX_SUCCESS;
+  eff.h = output_effect_fn();
+  eff.name = eff.h->name;
+  add_effect(&eff, &combiner, &ofile->desc->signal, &effects_mask);
 }
 
-static int flow_effect_out(void)
-{
-  int havedata, flowstatus = 0;
-  size_t e, len, total;
+/*
+ * Process:   Input(s) -> Balancing -> Combiner -> Effects -> Output
+ */
 
-  do {
-    /* run entire chain BACKWARDS: pull, don't push.*/
-    /* this is because buffering system isn't a nice queueing system */
-    for (e = neffects - 1; e && (int)e >= (int)input_eff; e--) {
-      /* Do not call flow effect on input if it has reported
-       * EOF already as that's a waste of time and may
-       * do bad things.
-       */
-      if (e == input_eff && input_eff_eof)
-        continue;
+static int process(void) {
+  int flowstatus = 0;
+  sox_size_t i;
+  sox_bool known_length = combine_method != sox_sequence;
+  sox_size_t olen = 0;
 
-      /* flow_effect returns SOX_EOF when it will not process
-       * any more samples.  This is used to bail out early.
-       * Since we are "pulling" data, it is OK that we are not
-       * calling any more previous effects since their output
-       * would not be looked at anyways.
-       */
-      flowstatus = flow_effect(e);
-      if (flowstatus == SOX_EOF) {
-        input_eff = e;
-        /* Assume next effect hasn't reach EOF yet */
-        input_eff_eof = sox_false;
-      }
+  combiner = files[current_input]->desc->signal;
+  if (combine_method == sox_sequence) {
+    if (!current_input) for (i = 0; i < input_count; i++)
+      report_file_info(files[i]);
+  } else {
+    sox_size_t total_channels = 0;
+    sox_size_t min_channels = SOX_SIZE_MAX;
+    sox_size_t max_channels = 0;
+    sox_size_t min_rate = SOX_SIZE_MAX;
+    sox_size_t max_rate = 0;
 
-      /* If this buffer contains more input data then break out
-       * of this loop now.  This will allow us to loop back around
-       * and reprocess the rest of this input buffer: we finish each
-       * effect before moving on to the next, so that each effect
-       * starts with an empty output buffer.
-       */
-      if (efftab[e].odone < efftab[e].olen) {
-        sox_debug_more("Breaking out of loop to flush buffer");
-        break;
-      }
+    for (i = 0; i < input_count; i++) { /* Report all inputs, then check */
+      report_file_info(files[i]);
+      total_channels += files[i]->desc->signal.channels;
+      min_channels = min(min_channels, files[i]->desc->signal.channels);
+      max_channels = max(max_channels, files[i]->desc->signal.channels);
+      min_rate = min(min_rate, files[i]->desc->signal.rate);
+      max_rate = max(max_rate, files[i]->desc->signal.rate);
+      known_length = known_length && files[i]->desc->length != 0;
+      if (combine_method == sox_concatenate)
+        olen += files[i]->desc->length / files[i]->desc->signal.channels;
+      else
+        olen = max(olen, files[i]->desc->length / files[i]->desc->signal.channels);
     }
-
-    /* If outputting and output data was generated then write it */
-    if (efftab[neffects - 1].olen > efftab[neffects - 1].odone) {
-      total = 0;
-      do {
-        /* Do not do any more writing during user aborts as
-         * we may be stuck in an infinite writing loop.
-         */
-        if (user_abort)
-          return SOX_EOF;
-
-        len = sox_write(ofile->desc,
-                       &efftab[neffects - 1].obuf[total],
-                       efftab[neffects - 1].olen - total);
-
-        if (len == 0) {
-          sox_warn("Error writing: %s", ofile->desc->sox_errstr);
-          return SOX_EOF;
-        }
-        total += len;
-      } while (total < efftab[neffects-1].olen);
-      output_samples += (total / ofile->desc->signal.channels);
-    } else {
-      /* Make it look like everything was consumed */
-      output_samples += (efftab[neffects-1].olen /
-                         ofile->desc->signal.channels);
+    if (min_rate != max_rate)
+      sox_fail("Input files must have the same sample-rate");
+    if (min_channels != max_channels) {
+      if (combine_method == sox_concatenate) {
+        sox_fail("Input files must have the same # channels");
+        exit(1);
+      } else if (combine_method == sox_mix)
+        sox_warn("Input files don't have the same # channels");
     }
-    efftab[neffects-1].odone = efftab[neffects-1].olen = 0;
+    if (min_rate != max_rate)
+      exit(1);
 
-    /* if stuff still in pipeline, set up to flow effects again */
-    /* When all effects have reported SOX_EOF then this check will
-     * show no more data.
-     */
-    havedata = 0;
-    for (e = neffects - 1; (int)e >= (int)input_eff; e--) {
-      /* If odone and olen are the same then this buffer
-       * can be reused.
-       */
-      if (efftab[e].odone == efftab[e].olen)
-        efftab[e].odone = efftab[e].olen = 0;
-
-      if (efftab[e].odone < efftab[e].olen) {
-        /* Only mark that we have more data if a full
-         * frame that can be written.
-         * FIXME: If this error case happens for the
-         * input buffer then the data will be lost and
-         * will cause stereo channels to be inversed.
-         */
-        if ((efftab[e].olen - efftab[e].odone) >=
-            ofile->desc->signal.channels)
-          havedata = 1;
-        else
-          sox_warn("Received buffer with incomplete amount of samples.");
-        /* Don't break out because other things are being
-         * done in loop.
-         */
-      }
-    }
-
-    if (!havedata && input_eff > 0) {
-      /* When EOF has been detected, skip to the next input
-       * before looking for more data.
-       */
-      if (input_eff_eof) {
-        input_eff++;
-        input_eff_eof = sox_false;
-      }
-
-      /* If the input file is not returning data then
-       * we must prime the pump using the drain effect.
-       * After it's primed, the loop will suck the data
-       * through.  Once an input_eff stops reporting samples,
-       * we will continue to the next until all are drained.
-       */
-      while (input_eff < neffects) {
-        int rc = drain_effect(input_eff);
-
-        if (efftab[input_eff].olen == 0) {
-          input_eff++;
-          /* Assume next effect hasn't reached EOF yet. */
-          input_eff_eof = sox_false;
-        } else {
-          havedata = 1;
-          input_eff_eof = rc == SOX_EOF;
-          break;
-        }
-      }
-    }
-  } while (havedata);
-
-  /* If input_eff isn't pointing at fake first entry then there
-   * is no need to read any more data from disk.  Return this
-   * fact to caller.
-   */
-  if (input_eff > 0) {
-    sox_debug("Effect return SOX_EOF");
-    return SOX_EOF;
+    combiner.channels = 
+      combine_method == sox_merge? total_channels : max_channels;
   }
 
-  return SOX_SUCCESS;
-}
+  ofile->signal = ofile_signal;
+  if (ofile->signal.rate == 0)
+    ofile->signal.rate = combiner.rate;
+  if (ofile->signal.size == -1)
+    ofile->signal.size = combiner.size;
+  if (ofile->signal.encoding == SOX_ENCODING_UNKNOWN)
+    ofile->signal.encoding = combiner.encoding;
+  if (ofile->signal.channels == 0)
+    ofile->signal.channels = combiner.channels;
 
-static int flow_effect(unsigned e)
-{
-  sox_size_t i, done, idone, odone, idonel, odonel, idoner, odoner;
-  const sox_ssample_t *ibuf;
-  sox_ssample_t *obuf;
-  int effstatus, effstatusl, effstatusr;
-  int (*flow)(eff_t, sox_ssample_t const*, sox_ssample_t*, sox_size_t*, sox_size_t*) =
-    efftab[e].h->flow? efftab[e].h->flow : sox_effect_nothing_flow;
+  combiner.rate = combiner.rate * effects_global_info.speed + .5;
 
-  /* Do not attempt to do any more effect processing during
-   * user aborts as we may be stuck in an infinite flow loop.
-   */
-  if (user_abort)
-    return SOX_EOF;
+  for (i = 0; i < nuser_effects; i++)
+    known_length = known_length && !(user_efftab[i].h->flags & SOX_EFF_LENGTH);
 
-  /* I have no input data ? */
-  if (efftab[e - 1].odone == efftab[e - 1].olen) {
-    sox_debug_more("%s no data to pull to me!", efftab[e].name);
-    return 0;
-  }
+  if (!known_length)
+    olen = 0;
 
-  if (!efftabR[e].name) {
-    /* No stereo data, or effect can handle stereo data so
-     * run effect over entire buffer.
-     */
-    idone = efftab[e - 1].olen - efftab[e - 1].odone;
-    odone = sox_bufsiz - efftab[e].olen;
-    sox_debug_more("pre %s idone=%d, odone=%d", efftab[e].name, idone, odone);
-    sox_debug_more("pre %s odone1=%d, olen1=%d odone=%d olen=%d", efftab[e].name, efftab[e-1].odone, efftab[e-1].olen, efftab[e].odone, efftab[e].olen);
+  {
+    sox_loopinfo_t loops[SOX_MAX_NLOOPS];
+    double factor;
+    int i;
+    char const *comment = NULL;
 
-    effstatus = flow(&efftab[e],
-                     &efftab[e - 1].obuf[efftab[e - 1].odone],
-                     &efftab[e].obuf[efftab[e].olen],
-                     (sox_size_t *)&idone,
-                     (sox_size_t *)&odone);
+    if (ofile->comment == NULL)
+      comment = files[0]->desc->comment ? files[0]->desc->comment : "Processed by SoX";
+    else if (*ofile->comment != '\0')
+        comment = ofile->comment;
 
-    efftab[e - 1].odone += idone;
-    /* Don't update efftab[e].odone as we didn't consume data */
-    efftab[e].olen += odone;
-    sox_debug_more("post %s idone=%d, odone=%d", efftab[e].name, idone, odone);
-    sox_debug_more("post %s odone1=%d, olen1=%d odone=%d olen=%d", efftab[e].name, efftab[e-1].odone, efftab[e-1].olen, efftab[e].odone, efftab[e].olen);
-
-    done = idone + odone;
-  } else {
-    /* Put stereo data in two separate buffers and run effect
-     * on each of them.
+    /*
+     * copy loop info, resizing appropriately
+     * it's in samples, so # channels don't matter
+     * FIXME: This doesn't work for multi-file processing or
+     * effects that change file length.
      */
-    idone = efftab[e - 1].olen - efftab[e - 1].odone;
-    odone = sox_bufsiz - efftab[e].olen;
-
-    ibuf = &efftab[e - 1].obuf[efftab[e - 1].odone];
-    for (i = 0; i < idone; i += 2) {
-      ibufl[i / 2] = *ibuf++;
-      ibufr[i / 2] = *ibuf++;
+    factor = (double) ofile->signal.rate / combiner.rate;
+    for (i = 0; i < SOX_MAX_NLOOPS; i++) {
+      loops[i].start = files[0]->desc->loops[i].start * factor;
+      loops[i].length = files[0]->desc->loops[i].length * factor;
+      loops[i].count = files[0]->desc->loops[i].count;
+      loops[i].type = files[0]->desc->loops[i].type;
     }
 
-    /* left */
-    idonel = (idone + 1) / 2;   /* odd-length logic */
-    odonel = odone / 2;
-    sox_debug_more("pre %s idone=%d, odone=%d", efftab[e].name, idone, odone);
-    sox_debug_more("pre %s odone1=%d, olen1=%d odone=%d olen=%d", efftab[e].name, efftab[e - 1].odone, efftab[e - 1].olen, efftab[e].odone, efftab[e].olen);
+    ofile->desc = sox_open_write(overwrite_permitted,
+                          ofile->filename,
+                          &ofile->signal,
+                          ofile->filetype,
+                          comment,
+                          olen,
+                          &files[0]->desc->instr,
+                          loops);
 
-    effstatusl = flow(&efftab[e],
-                      ibufl, obufl, (sox_size_t *)&idonel,
-                      (sox_size_t *)&odonel);
+    if (!ofile->desc)
+      /* sox_open_write() will call sox_warn for most errors.
+       * Rely on that printing something. */
+      exit(2);
 
-    /* right */
-    idoner = idone / 2;               /* odd-length logic */
-    odoner = odone / 2;
-    effstatusr = flow(&efftabR[e],
-                      ibufr, obufr, (sox_size_t *)&idoner,
-                      (sox_size_t *)&odoner);
+    /* When writing to an audio device, auto turn on the
+     * progress display to match behavior of ogg123,
+     * unless the user requested us not to display anything. */
+    if (show_progress == SOX_OPTION_DEFAULT)
+      show_progress = (ofile->desc->h->flags & SOX_FILE_DEVICE) != 0 &&
+                      (ofile->desc->h->flags & SOX_FILE_PHONY) == 0;
 
-    obuf = &efftab[e].obuf[efftab[e].olen];
-    /* This loop implies left and right effect will always output
-     * the same amount of data.
-     */
-    for (i = 0; i < odoner; i++) {
-      *obuf++ = obufl[i];
-      *obuf++ = obufr[i];
-    }
-    efftab[e-1].odone += idonel + idoner;
-    /* Don't zero efftab[e].odone since nothing has been consumed yet */
-    efftab[e].olen += odonel + odoner;
-    sox_debug_more("post %s idone=%d, odone=%d", efftab[e].name, idone, odone);
-    sox_debug_more("post %s odone1=%d, olen1=%d odone=%d olen=%d", efftab[e].name, efftab[e - 1].odone, efftab[e - 1].olen, efftab[e].odone, efftab[e].olen);
-
-    done = idonel + idoner + odonel + odoner;
-
-    if (effstatusl)
-      effstatus = effstatusl;
-    else
-      effstatus = effstatusr;
+    report_file_info(ofile);
   }
-  if (effstatus == SOX_EOF)
-    return SOX_EOF;
-  if (done == 0) {
-    sox_fail("'%s' effect took & gave no samples!", efftab[e].name);
-    exit(2);
-  }
-  return SOX_SUCCESS;
-}
 
-static int drain_effect_out(void)
-{
-  /* Skip past input effect since we know thats not needed */
-  if (input_eff == 0) {
-    input_eff = 1;
-    /* Assuming next effect hasn't reached EOF yet. */
-    input_eff_eof = sox_false;
-  }
+  add_effects();
+  if (start_effects() != SOX_SUCCESS)
+    exit(2); /* The failing effect should have displayed an error message */
 
-  /* Try to prime the pump with some data */
-  while (input_eff < neffects) {
-    int rc = drain_effect(input_eff);
+  optimize_trim();
 
-    if (efftab[input_eff].olen == 0) {
-      input_eff++;
-      /* Assuming next effect hasn't reached EOF yet. */
-      input_eff_eof = sox_false;
-    } else {
-      input_eff_eof = rc == SOX_EOF;
-      break;
-    }
-  }
+  signal(SIGINT, sigint);
+  signal(SIGTERM, sigint);
 
-  /* Just do standard flow routines after the priming. */
-  return flow_effect_out();
-}
+  flowstatus = flow_effects(update_status, &user_abort);
 
-static int drain_effect(unsigned e)
-{
-  sox_ssize_t i, olen, olenl, olenr;
-  sox_ssample_t *obuf;
-  int rc;
-  int (*drain)(eff_t effp, sox_ssample_t *obuf, sox_size_t *osamp) =
-    efftab[e].h->drain? efftab[e].h->drain : sox_effect_nothing_drain;
-
-  if (! efftabR[e].name) {
-    efftab[e].olen = sox_bufsiz;
-    rc = drain(&efftab[e],efftab[e].obuf, &efftab[e].olen);
-    efftab[e].odone = 0;
-  } else {
-    int rc_l, rc_r;
-
-    olen = sox_bufsiz;
-
-    /* left */
-    olenl = olen/2;
-    rc_l = drain(&efftab[e], obufl, (sox_size_t *)&olenl);
-
-    /* right */
-    olenr = olen/2;
-    rc_r = drain(&efftabR[e], obufr, (sox_size_t *)&olenr);
-
-    if (rc_l == SOX_EOF || rc_r == SOX_EOF)
-      rc = SOX_EOF;
-    else
-      rc = SOX_SUCCESS;
-
-    obuf = efftab[e].obuf;
-    /* This loop implies left and right effect will always output
-     * the same amount of data.
-     */
-    for (i = 0; i < olenr; i++) {
-      *obuf++ = obufl[i];
-      *obuf++ = obufr[i];
-    }
-    efftab[e].olen = olenl + olenr;
-    efftab[e].odone = 0;
-  }
-  return rc;
+  stop_effects();
+  delete_effects();
+  return flowstatus;
 }
 
-static void stop_effects(void)
-{
-  unsigned e;
-
-  for (e = 1; e < neffects; e++) {
-    sox_size_t clips;
-    int (*stop)(eff_t effp) =
-       efftab[e].h->stop? efftab[e].h->stop : sox_effect_nothing;
-
-    stop(&efftab[e]);
-    clips = efftab[e].clips;
-
-    if (efftabR[e].name) {
-      stop(&efftabR[e]);
-      clips += efftab[e].clips;
-    }
-    if (clips != 0)
-      sox_warn("'%s' clipped %u samples; decrease volume?",efftab[e].name,clips);
-  }
-}
-
-static void kill_effects(void)
-{
-  unsigned e;
-
-  for (e = 1; e < neffects; e++) {
-    int (*kill)(eff_t effp) =
-       efftab[e].h->kill? efftab[e].h->kill : sox_effect_nothing;
-
-    /* No left & right kill as there is no left & right getopts */
-    kill(&efftab[e]);
-  }
-}
-
 static sox_size_t total_clips(void)
 {
-  unsigned i;
+  unsigned i, f;
   sox_size_t clips = 0;
   for (i = 0; i < file_count; ++i)
     clips += files[i]->desc->clips + files[i]->volume_clips;
   clips += mixing_clips;
-  for (i = 1; i < neffects; ++i) {
-    clips += efftab[i].clips;
-    if (efftabR[i].name)
-      clips += efftab[i].clips;
-  }
+  for (i = 1; i < neffects - 1; ++i)
+    for (f = 1; f < effects[i][0].flows; ++f)
+      clips += effects[i][f].clips;
   return clips;
 }
 
@@ -1934,6 +1511,53 @@
   size_t i, formats;
   const char **format_list;
   const sox_effect_t *e;
+  static char const * lines[] = {
+"SPECIAL FILENAMES:",
+"-               stdin (infile) or stdout (outfile)",
+"-n              use the null file handler; for use with e.g. synth & stat",
+"",
+"GLOBAL OPTIONS (gopts) (can be specified at any point before the first effect):",
+"--buffer BYTES  set the buffer size (default 8192)",
+"--combine concatenate  concatenate multiple input files (default for sox, rec)",
+"--combine sequence  sequence multiple input files (default for play)",
+"-h, --help      display version number and usage information",
+"--help-effect NAME  display usage of specified effect; use `all' to display all",
+"--interactive   prompt to overwrite output file",
+"-m, --combine mix  mix multiple input files (instead of concatenating)",
+"-M, --combine merge  merge multiple input files (instead of concatenating)",
+"--octave        generate Octave commands to plot response of filter effect",
+"-q, --no-show-progress  run in quiet mode; opposite of -S",
+"--replay-gain track|album|off  default: off (sox, rec), track (play)",
+"-R              use default random numbers (same on each run of SoX)",
+"-S, --show-progress  display progress while processing audio data",
+"--version       display version number of SoX and exit",
+"-V[LEVEL]       increment or set verbosity level (default 2); levels are:",
+"                  1: failure messages",
+"                  2: warnings",
+"                  3: details of processing",
+"                  4-6: increasing levels of debug messages",
+"",
+"FORMAT OPTIONS (fopts):",
+"Format options only need to be supplied for input files that are headerless,",
+"otherwise they are obtained automatically.  Output files will default to the",
+"same format options as the input file unless otherwise specified.",
+"",
+"-c, --channels CHANNELS  number of channels in audio data",
+"-C, --compression FACTOR  compression factor for output format",
+"--comment TEXT  Specify comment text for the output file",
+"--comment-file FILENAME  file containing comment text for the output file",
+"--endian little|big|swap  set endianness; swap means opposite to default",
+"-r, --rate RATE  sample rate of audio",
+"-t, --type FILETYPE  file type of audio",
+"-x              invert auto-detected endianness",
+"-N, --reverse-nibbles  nibble-order",
+"-X, --reverse-bits  bit-order of data",
+"-B/-L           force endianness to big/little",
+"-s/-u/-U/-A/    sample encoding: signed/unsigned/u-law/A-law",
+"  -a/-i/-g/-f   ADPCM/IMA_ADPCM/GSM/floating point",
+"-1/-2/-3/-4/-8  sample size in bytes",
+"-v, --volume FACTOR  volume input file volume adjustment factor (real number)",
+""};
 
   printf("%s: ", myname);
   printf("SoX Version %s\n\n", PACKAGE_VERSION);
@@ -1941,52 +1565,9 @@
     fprintf(stderr, "Failed: %s\n\n", message);
   printf("Usage summary: [gopts] [[fopts] infile]... [fopts]%s [effect [effopts]]...\n\n",
          play? "" : " outfile");
-  printf("SPECIAL FILENAMES:\n"
-         "-               stdin (infile) or stdout (outfile)\n"
-         "-n              use the null file handler; for use with e.g. synth & stat\n"
-         "\n"
-         "GLOBAL OPTIONS (gopts) (can be specified at any point before the first effect):\n"
-         "--buffer BYTES  set the buffer size (default 8192)\n"
-         "--combine concatenate  concatenate multiple input files (default for sox, rec)\n"
-         "--combine sequence  sequence multiple input files (default for play)\n"
-         "-h, --help      display version number and usage information\n"
-         "--help-effect NAME  display usage of specified effect; use `all' to display all\n"
-         "--interactive   prompt to overwrite output file\n"
-         "-m, --combine mix  mix multiple input files (instead of concatenating)\n"
-         "-M, --combine merge  merge multiple input files (instead of concatenating)\n"
-         "--octave        generate Octave commands to plot response of filter effect\n"
-         "-q, --no-show-progress  run in quiet mode; opposite of -S\n"
-         "--replay-gain track|album|off  default: off (sox, rec), track (play)\n"
-         "-R              use default random numbers (same on each run of SoX)\n"
-         "-S, --show-progress  display progress while processing audio data\n"
-         "--version       display version number of SoX and exit\n"
-         "-V[LEVEL]       increment or set verbosity level (default 2); levels are:\n"
-         "                  1: failure messages\n"
-         "                  2: warnings\n"
-         "                  3: details of processing\n"
-         "                  4-6: increasing levels of debug messages\n"
-         "\n"
-         "FORMAT OPTIONS (fopts):\n"
-         "Format options only need to be supplied for input files that are headerless,\n"
-         "otherwise they are obtained automatically.  Output files will default to the\n"
-         "same format options as the input file unless otherwise specified.\n"
-         "\n"
-         "-c, --channels CHANNELS  number of channels in audio data\n"
-         "-C, --compression FACTOR  compression factor for output format\n"
-         "--comment TEXT  Specify comment text for the output file\n"
-         "--comment-file FILENAME  file containing comment text for the output file\n"
-         "--endian little|big|swap  set endianness; swap means opposite to default\n"
-         "-r, --rate RATE  sample rate of audio\n"
-         "-t, --type FILETYPE  file type of audio\n"
-         "-x              invert auto-detected endianness\n"
-         "-N, --reverse-nibbles  nibble-order\n"
-         "-X, --reverse-bits  bit-order of data\n"
-         "-B/-L           force endianness to big/little\n"
-         "-s/-u/-U/-A/    sample encoding: signed/unsigned/u-law/A-law\n"
-         "  -a/-i/-g/-f   ADPCM/IMA_ADPCM/GSM/floating point\n"
-         "-1/-2/-3/-4/-8  sample size in bytes\n"
-         "-v, --volume FACTOR  volume input file volume adjustment factor (real number)\n"
-         "\n");
+
+  for (i = 0; i < array_length(lines); ++i)
+    puts(lines[i]);
 
   printf("SUPPORTED FILE FORMATS:");
   for (i = 0, formats = 0; i < sox_formats; i++) {
--- a/src/sox.h
+++ b/src/sox.h
@@ -392,6 +392,7 @@
 struct sox_effect
 {
     char const *name;               /* effect name */
+    sox_size_t       flows;
     struct sox_effects_global_info * global_info;/* global parameters */
     struct sox_signalinfo ininfo;    /* input signal specifications */
     struct sox_signalinfo outinfo;   /* output signal specifications */
@@ -432,6 +433,17 @@
 int sox_geteffect(eff_t effp, const char *effect_name);
 sox_bool is_effect_name(char const *text);
 int sox_updateeffect(eff_t effp, const sox_signalinfo_t *in, const sox_signalinfo_t *out, int effect_mask);
+
+#define MAX_EFFECTS 20
+extern struct sox_effect * effects[MAX_EFFECTS];
+extern unsigned neffects;                     /* # of effects to run on data */
+void add_effect(struct sox_effect * e, sox_signalinfo_t * in, sox_signalinfo_t * out, int * effects_mask);
+int start_effects(void);
+int flow_effects(void (* update_status)(sox_bool), sox_bool * user_abort);
+void stop_effects(void);
+void kill_effects(void);
+void delete_effects(void);
+
 int sox_gettype(ft_t, sox_bool);
 ft_t sox_initformat(void);
 char const * sox_parsesamples(sox_rate_t rate, const char *str, sox_size_t *samples, int def);
--- a/src/util.c
+++ b/src/util.c
@@ -188,6 +188,8 @@
 {
     int i;
 
+    memset(effp, 0, sizeof(*effp));
+
     for(i = 0; sox_effect_fns[i]; i++) {
         const sox_effect_t *e = sox_effect_fns[i]();