shithub: sox

Download patch

ref: 6fa0f577dab703fc3c682eb8ab604aa6912a4577
parent: 7e27d2ef6a96c866715f1e9ccf505b89991e1bee
author: cbagwell <cbagwell>
date: Mon Sep 1 19:21:41 EDT 2008

Restructure code so that restarting the effects chain can completely
re-create the effects without memory leaks.  In mult-output mode,
only restart effects that do not have samples left.  Framework now
can support using different effects and options for each file processed.
Also, removed attempt to optimatize when rate/mixer were auto-inserted
because it was unpredicatable/confusing at how it affected longer chains.

--- a/ChangeLog
+++ b/ChangeLog
@@ -49,6 +49,7 @@
 Internal improvements:
 
   o Fixed all compiler warnings (with gcc 4.3.1, 64-bit arch.).  (robs)
+  o Updates to internal effects chain API.
 
 
 sox-14.1.0	2008-7-29
--- a/libsox.3
+++ b/libsox.3
@@ -38,6 +38,8 @@
 .P
 .B int sox_format_init(void);
 .P
+.B void sox_format_quit(void);
+.P
 .B sox_format_t sox_open_read(const char *\fIpath\fB, const sox_signalinfo_t *\fIinfo\fB, const char *\fIfiletype\fB);
 .P
 .B sox_format_t sox_open_write(sox_bool (*\fIoverwrite_permitted\fB)(const char *\fIfilename\fB), const char *\fIpath\fB, const sox_signalinfo_t *\fIinfo\fB, const char *\fIfiletype\fB, const char *\fIcomment\fB, sox_size_t \fIlength\fB, const sox_instrinfo_t *\fIinstr\fB, const sox_loopinfo_t *\fIloops\fB);
@@ -52,8 +54,16 @@
 .P
 .B sox_effect_handler_t const *sox_find_effect(char const *\fIname\fB);
 .P
-.B void sox_create_effect(sox_effect_t \fIeffp\fB, sox_effect_handler_t const *\fIe\fB);
+.B sox_effect_t *sox_create_effect(sox_effect_handler_t const *\fIeh\fB);
 .P
+.B int sox_effect_options(sox_effect_t *\fIeffp\fB, int \fIargc\fB, char * const \fIargv[]\fB);
+.P
+.B sox_effects_chain_t *sox_create_effects_chain(sox_encodinginfo_t const *\fIin_enc\fB, sox_encodinginfo_t const *\fIout_enc\fB);
+.P
+.B void sox_delete_effects_chain(sox_effects_chain_t *\fIecp\fB);
+.P
+.B int sox_add_effect(sox_effects_chaint_t *\fIchain\fB, sox_effect_t*\fIeffp\fB, sox_signalinfo_t *\fIin\fB, sox_signalinfo-t const *\fIout\fB);
+.P
 .B cc \fIfile.c\fB -o \fIfile \f-lsox
 .fi
 .SH DESCRIPTION
@@ -68,6 +78,9 @@
 libraries.  This should be called before any other file operations
 are performed.
 .P
+\fBsox_format_quit\fR function performs some required cleanup
+related to all file format handlers.
+.P
 \fBsox_open_input\fR function opens the file for reading whose name is
 the string pointed to by \fIpath\fR and associates an sox_format_t with it. If
 \fIinfo\fR is non-NULL then it will be used to specify the data format
@@ -109,9 +122,6 @@
 underlying file or set of functions. If the format handler was being
 used for output, any buffered data is written first.
 .P
-\fBsox_format_quite\fR function performs some required cleanup
-related to all file format handlers.
-.P
 The function \fBsox_find_effect\fR finds effect \fIname\fR, returning
 a pointer to its \fIsox_effect_handler_t\fR if it exists, and NULL
 otherwise.
@@ -121,16 +131,40 @@
 methods are automatically set to the corresponding \fBnothing\fR
 method.
 .P
-The \fBsox_update_effect\fR function copies input and output signal
-info into effect structures. The \fIeffect_mask\fR parameter is the
-return value of the previous call to this function; for the first
-call, pass 0. The function returns the updated effect mask.
+The function \fBsox_effect_options\fR allows passing options into the effect to control its behavior.  It will return SOX_EOF if there were any invalid options passed in.  On success, the \fIeffp->in_signal\fR will optional contain the rate and channel count it requires input data from and \fIeffp->out_signal\fR will optionally contain the rate and channel count it outputs in.  When present, this information should be used to make sure appropriate effects are placed in the effects chain to handle any needed conversions.
 .P
+Passing in options is currently only supported when they are passed in before the effect is ever started.  The behavior is undefined if its called once the effect is started.
+.P
+\fBsox_create_effects_chain\fR will instantiate an effects chain that
+effects can be added to.  \fIin_enc\fR and \fIout_enc\fR are the 
+signal encoding of the input and output of the chain respectively.
+The pointers to \fIin_enc\fR and \fIout_enc\fR
+are stored internally and so their memory should not be freed.  Also,
+it is OK if their values change over time to reflect new input or
+output encodings as they are referenced only as effects
+start up or are restarted.
+.P
+\fBsox_delete_effects_chain\fR will release any resources reserved during
+the creation of the chain.  This will also call \fBsox_delete_effects\fR
+if any effects are still in the chain.
+.P
+\fBsox_add_effect\fR adds an effect to the chain.  \fIin\fR specifies the input
+signal info for this effect.  \fIout\fR is a suggestion
+as to what the output signal should be but depending on the effects
+given options and on \fIin\fR the effect can choose to do differently.
+Whatever output rate and channels the effect does produce are written
+back to \fIin\fR.  It is meant that \fIin\fR be stored and passed to each
+new call to \fBsox_add_effect\fR so that changes will be propagated to each new effect.
+.P
 SoX includes skeleton C files to assist you in writing new
-formats (skelform.c) and effects (skeleff.c). sox.c itself is a good
-starting point for new programs. Note that new formats can often just
-deal with the header and then use raw.c's routines for reading and
-writing.
+formats (skelform.c) and effects (skeleff.c). Note that new formats 
+can often just deal with the header and then use raw.c's routines 
+for reading and writing.
+
+example0.c and example1.c are a good starting point to see how
+to write applications using libsox.  sox.c itself is also a good
+reference.
+
 .SH RETURN VALUE
 Upon successful completion \fBsox_open_input\fR and
 \fBsox_open_output\fR return an \fIsox_format_t\fR (which is a pointer).
--- a/src/effects.c
+++ b/src/effects.c
@@ -30,7 +30,7 @@
 
 #define DEBUG_EFFECTS_CHAIN 0
 
-
+/* FIXME: Not thread safe using globals */
 sox_effects_globals_t sox_effects_globals =
     {sox_plot_off, 1, &sox_globals};
 
@@ -66,7 +66,9 @@
 sox_effect_t * sox_create_effect(sox_effect_handler_t const * eh)
 {
   sox_effect_t * effp = lsx_calloc(1, sizeof(*effp));
-  assert(eh);
+
+  if (!effp) return NULL;
+
   effp->global_info = &sox_effects_globals;
   effp->handler = *eh;
   if (!effp->handler.getopts) effp->handler.getopts = default_getopts;
@@ -75,17 +77,26 @@
   if (!effp->handler.drain  ) effp->handler.drain   = default_drain;
   if (!effp->handler.stop   ) effp->handler.stop    = default_function;
   if (!effp->handler.kill   ) effp->handler.kill    = default_function;
+
   effp->priv = lsx_calloc(1, effp->handler.priv_size);
+  if (effp->handler.priv_size && !effp->priv)
+  {
+    free(effp);
+    return NULL;
+  }
+
   return effp;
-}
+} /* sox_create_effect */
 
+int sox_effect_options(sox_effect_t *effp, int argc, char * const argv[])
+{
+    return effp->handler.getopts(effp, argc, argv);
+} /* sox_effect_options */
 
-
 /* Effects chain: */
 
 sox_effects_chain_t * sox_create_effects_chain(
-    sox_encodinginfo_t const * in_enc,
-    sox_encodinginfo_t const * out_enc)
+    sox_encodinginfo_t const * in_enc, sox_encodinginfo_t const * out_enc)
 {
   sox_effects_chain_t * result = lsx_calloc(1, sizeof(sox_effects_chain_t));
   result->global_info = sox_effects_globals;
@@ -92,8 +103,15 @@
   result->in_enc = in_enc;
   result->out_enc = out_enc;
   return result;
-}
+} /* sox_create_effects_chain */
 
+void sox_delete_effects_chain(sox_effects_chain_t *ecp)
+{
+    if (ecp && ecp->length)
+        sox_delete_effects(ecp);
+    free(ecp);
+} /* sox_delete_effects_chain */
+
 /* Effect can call in start() or flow() to set minimum input size to flow() */
 int sox_effect_set_imin(sox_effect_t * effp, size_t imin)
 {
@@ -378,7 +396,7 @@
  * Note: This currently closes down the effect which might
  * note be obvious from name.
  */
-static void sox_delete_effect(sox_effect_t *effp)
+void sox_delete_effect(sox_effect_t *effp)
 {
     size_t clips;
     unsigned f;
--- a/src/example0.c
+++ b/src/example0.c
@@ -83,10 +83,9 @@
   sox_flow_effects(chain, NULL);
 
   /* All done; tidy up: */
-  sox_delete_effects(chain);
+  sox_delete_effects_chain(chain);
   sox_close(out);
   sox_close(in);
   sox_format_quit();
-  free(chain);
   return 0;
 }
--- a/src/example1.c
+++ b/src/example1.c
@@ -51,7 +51,7 @@
 }
 
 /* The function that will be called to output samples from the effects chain.
- * In this example, we store the samples in a SoX-openned audio file.
+ * In this example, we store the samples in a SoX-opened audio file.
  * In a different application, they might perhaps be analysed in some way,
  * or displayed as a wave-form */
 static int output_flow(sox_effect_t *effp UNUSED, sox_sample_t const * ibuf,
@@ -153,10 +153,9 @@
   sox_flow_effects(chain, NULL);
 
   /* All done; tidy up: */
-  sox_delete_effects(chain);
+  sox_delete_effects_chain(chain);
   sox_close(out);
   sox_close(in);
   sox_format_quit();
-  free(chain);
   return 0;
 }
--- a/src/sox.c
+++ b/src/sox.c
@@ -130,9 +130,10 @@
  * a resampling effect, a channel mixing effect, the input, and the output.
  */
 #define MAX_USER_EFF (SOX_MAX_EFFECTS - 4)
-static sox_effect_t * user_efftab[MAX_USER_EFF], efftab_options[MAX_USER_EFF];
+static struct { char *name; int argc; char *argv[256]; } user_effargs[MAX_USER_EFF];
+static sox_effect_t *user_efftab[MAX_USER_EFF];
 static unsigned nuser_effects;
-static sox_effects_chain_t ofile_effects_chain;
+static sox_effects_chain_t *effects_chain = NULL;
 
 
 /* Flowing */
@@ -542,14 +543,21 @@
   return &handler;
 }
 
-static void add_auto_effect(sox_effects_chain_t * chain, char const * name, char * arg, sox_signalinfo_t * signal)
+static void add_effect(sox_effects_chain_t *chain, char const *name, 
+                       int argc, char *argv[], sox_signalinfo_t *signal)
 {
   sox_effect_t * effp;
-  char * * argv = & arg;
 
   effp = sox_create_effect(sox_find_effect(name)); /* Should always succeed. */
 
-  if (effp->handler.getopts(effp, arg != NULL, argv) == SOX_EOF)
+  if (!effp)
+    sox_fail("Failed creating effect.  Out of Memory?\n");
+
+  if (effp->handler.flags & SOX_EFF_DEPRECATED)
+    sox_warn("effect `%s' is deprecated; see soxeffect(7) for an alternative", 
+             effp->handler.name);
+
+  if (sox_effect_options(effp, argc, argv) == SOX_EOF)
     exit(1); /* The failing effect should have displayed an error message */
   
   if (sox_add_effect(chain, effp, signal, &ofile->ft->signal) != SOX_SUCCESS)
@@ -556,12 +564,57 @@
     exit(2); /* The effects chain should have displayed an error message */
 }
 
-/* If needed effects are not given, auto-add at (performance) optimal point. */
-static void add_effects(sox_effects_chain_t * chain)
+/* Creates users effects and passes in user specified options.
+ * This is done without putting anything into the effects chain
+ * because an effect may set the effp->in_format and we may want
+ * to copy that back into the input/combiner before opening and 
+ * inserting it.  
+ * Similarly, we may want to use effp->out_format to override the 
+ * default values of output file before we open it.
+ * To keep things simple, we create all user effects.  Later, when
+ * we add them, some may already be in the chain and we will need to free
+ * them.
+ */
+static void create_user_effects(void)
 {
+  unsigned i;
+  sox_effect_t *effp;
+
+  for (i = 0; i < nuser_effects; i++) 
+  {
+      effp = sox_create_effect(sox_find_effect(user_effargs[i].name));
+
+      if (!effp)
+        sox_fail("Failed creating effect.  Out of Memory?\n");
+
+      if (effp->handler.flags & SOX_EFF_DEPRECATED)
+        sox_warn("effect `%s' is deprecated; see soxeffect(7) for an alternative", 
+                 effp->handler.name);
+
+      /* The failing effect should have displayed an error message */
+      if (sox_effect_options(effp, user_effargs[i].argc, 
+                             user_effargs[i].argv) == SOX_EOF)
+        exit(1);
+
+      user_efftab[i] = effp;
+  }
+}
+
+/* Add all user effects to the chain.  If the output effect's rate or
+ * channel count do not match the end of the effects chain then
+ * insert effects to correct this.
+ *
+ * This can also be called on pre-existing effect chains so that it
+ * will only add effects that are missing off the END of the chain.
+ * This is useful if an effect went into drain mode and you
+ * wish to restart it and any effects after it.
+ */
+static void add_effects(sox_effects_chain_t *chain)
+{
   sox_signalinfo_t signal = combiner_signal;
   unsigned i, min_chan = 0, min_rate = 0;
   sox_effect_t * effp;
+  unsigned effects_added = 0;
   char * rate_arg = sox_mode != sox_play? NULL :
     (rate_arg = getenv("PLAY_RATE_ARG"))? rate_arg : "-l";
 
@@ -572,36 +625,55 @@
     if (user_efftab[i]->handler.flags & SOX_EFF_RATE)
       min_rate = i + 1;
   }
-  /* 1st `effect' in the chain is the input combiner_signal */
-  effp = sox_create_effect(input_combiner_effect_fn());
-  sox_add_effect(chain, effp, &signal, &ofile->ft->signal);
 
-  /* Add auto effects if appropriate; add user specified effects */
-  for (i = 0; i <= nuser_effects; i++) {
-    /* If reducing channels, it's faster to do so before all other effects: */
-    if (signal.channels > ofile->ft->signal.channels && i >= min_chan)
-      add_auto_effect(chain, "mixer", NULL, &signal);
+  effects_added++;
+  if (effects_added > chain->length)
+  {
+    /* 1st `effect' in the chain is the input combiner_signal */
+    effp = sox_create_effect(input_combiner_effect_fn());
+    sox_add_effect(chain, effp, &signal, &ofile->ft->signal);
+  }
 
-    /* If reducing rate, it's faster to do so before all other effects
-     * (except reducing channels): */
-    if (signal.rate > ofile->ft->signal.rate && i >= min_rate)
-      add_auto_effect(chain, "rate", rate_arg, &signal);
-
-    if (i < nuser_effects)
-      if (sox_add_effect(chain, user_efftab[i], &signal, &ofile->ft->signal) != SOX_SUCCESS)
+  /* Add auto effects if appropriate; add user specified effects */
+  for (i = 0; i < nuser_effects; i++) {
+    effects_added++;
+    if (effects_added > chain->length)
+    {
+      /* Effects chain should have displayed an error message */
+      if (sox_add_effect(chain, user_efftab[i], 
+                         &signal, &ofile->ft->signal) != SOX_SUCCESS)
         exit(2);
+    }
+    else
+      /* Since already added from previous pass, free the extra copy
+       * created this pass.
+       */
+      sox_delete_effect(user_efftab[i]);
   }
+
   /* Add auto effects if still needed at this point */
   if (signal.rate != ofile->ft->signal.rate)
-    add_auto_effect(chain, "rate", rate_arg, &signal); /* Must be up-sampling */
+  {
+    effects_added++;
+    if (effects_added > chain->length)
+      add_effect(chain, "rate", rate_arg != NULL, &rate_arg, &signal); /* Must be up-sampling */
+  }
   if (signal.channels != ofile->ft->signal.channels)
-    add_auto_effect(chain, "mixer", NULL, &signal); /* Must be increasing channels */
+  {
+    effects_added++;
+    if (effects_added > chain->length)
+      add_effect(chain, "mixer", 0, NULL, &signal); /* Must be increasing channels */
+  }
+    
+  effects_added++;
+  if (effects_added > chain->length)
+  {
+    /* Last `effect' in the chain is the output file */
+    effp = sox_create_effect(output_effect_fn());
+    if (sox_add_effect(chain, effp, &signal, &ofile->ft->signal) != SOX_SUCCESS)
+      exit(2);
+  }
 
-  /* Last `effect' in the chain is the output file */
-  effp = sox_create_effect(output_effect_fn());
-  if (sox_add_effect(chain, effp, &signal, &ofile->ft->signal) != SOX_SUCCESS)
-    exit(2);
-
   for (i = 0; i < chain->length; ++i) {
     sox_effect_t const * effp = &chain->effects[i][0];
     sox_report("effects chain: %-10s %gHz %u channels %u bits %s",
@@ -616,7 +688,7 @@
   size_t clips = 0;
   for (i = 0; i < file_count; ++i)
     clips += files[i]->ft->clips + files[i]->volume_clips;
-  return clips + mixing_clips + sox_effects_clips(&ofile_effects_chain);
+  return clips + mixing_clips + sox_effects_clips(effects_chain);
 }
 
 static sox_bool since(struct timeval * then, double secs, sox_bool always_reset)
@@ -711,15 +783,15 @@
    * to do it for multiple files is complex and problably never used.  This
    * hack is a huge time savings when trimming gigs of audio data into
    * managable chunks.  */
-  if (input_count == 1 && ofile_effects_chain.length > 1 && strcmp(ofile_effects_chain.effects[1][0].handler.name, "trim") == 0) {
+  if (input_count == 1 && effects_chain->length > 1 && strcmp(effects_chain->effects[1][0].handler.name, "trim") == 0) {
     if (files[0]->ft->handler.seek && files[0]->ft->seekable){
-      size_t offset = sox_trim_get_start(&ofile_effects_chain.effects[1][0]);
+      size_t offset = sox_trim_get_start(&effects_chain->effects[1][0]);
       if (offset && sox_seek(files[0]->ft, offset, SOX_SEEK_SET) == SOX_SUCCESS) {
         read_wide_samples = offset / files[0]->ft->signal.channels;
         /* Assuming a failed seek stayed where it was.  If the seek worked then
          * reset the start location of trim so that it thinks user didn't
          * request a skip.  */
-        sox_trim_clear_start(&ofile_effects_chain.effects[1][0]);
+        sox_trim_clear_start(&effects_chain->effects[1][0]);
         sox_debug("optimize_trim successful");
       }
     }
@@ -851,11 +923,25 @@
   else user_abort = sox_true;
 }
 
-static void calculate_combiner_and_output_signal_parameters(void)
+static void calculate_combiner_signal_parameters(void)
 {
-  sox_bool known_length = combine_method != sox_sequence;
-  size_t i, olen = 0;
+  size_t i;
 
+  /* If user didn't specify # of channels then see if an effect
+   * is specifying them.  This is of most use currently with the
+   * synth effect were user can use null input handler and specify
+   * channel counts directly in effect.  Forcing to use -c with
+   * -n isn't as convenient.
+   */
+  for (i = 0; i < input_count; i++) {
+    unsigned j;
+    for (j =0; j < nuser_effects && !files[i]->ft->signal.channels; ++j)
+      files[i]->ft->signal.channels = user_efftab[j]->in_signal.channels;
+    /* For historical reasons, default to one channel if not specified. */
+    if (!files[i]->ft->signal.channels)
+      files[i]->ft->signal.channels = 1;
+  }
+
   /* Set the combiner output signal attributes to those of the 1st/next input
    * file.  If we are in sox_sequence mode then we don't need to check the
    * attributes of the other inputs, otherwise, it is mandatory that all input
@@ -884,11 +970,6 @@
       max_channels = max(max_channels, files[i]->ft->signal.channels);
       min_rate     = min(min_rate    , files[i]->ft->signal.rate);
       max_rate     = max(max_rate    , files[i]->ft->signal.rate);
-      known_length = known_length && files[i]->ft->signal.length != 0;
-      if (combine_method == sox_concatenate)
-        olen += files[i]->ft->signal.length / files[i]->ft->signal.channels;
-      else
-        olen = max(olen, files[i]->ft->signal.length / files[i]->ft->signal.channels);
     }
 
     /* Check for invalid/unusual rate or channel combinations: */
@@ -910,6 +991,27 @@
       combine_method == sox_merge? total_channels : max_channels;
   }
 
+  /* Now take account of any net speed change specified by user effects by
+   * adjusting the nominal sample rate at the output of the combiner: */
+  combiner_signal.rate *= sox_effects_globals.speed;
+
+} /* calculate_combiner_signal_parameters */
+
+static void calculate_output_signal_parameters(void)
+{
+  sox_bool known_length = combine_method != sox_sequence;
+  size_t i, olen = 0;
+
+  /* Report all input files and gather info on differing rates & numbers of
+   * channels, and on the resulting output audio length: */
+  for (i = 0; i < input_count; i++) {
+    known_length = known_length && files[i]->ft->signal.length != 0;
+    if (combine_method == sox_concatenate)
+      olen += files[i]->ft->signal.length / files[i]->ft->signal.channels;
+    else
+      olen = max(olen, files[i]->ft->signal.length / files[i]->ft->signal.channels);
+  }
+
   /* Determine the output file signal attributes; set from user options
    * if given: */
   ofile->signal = ofile_signal_options;
@@ -928,10 +1030,6 @@
   /* FIXME: comment this: */
   ofile->signal.precision = combiner_signal.precision;
 
-  /* Now take account of any net speed change specified by user effects by
-   * adjusting the nominal sample rate at the output of the combiner: */
-  combiner_signal.rate *= sox_effects_globals.speed;
-
   /* If any given user effect modifies the audio length, then we assume that
    * we don't know what the output length will be.  FIXME: in most cases,
    * an effect that modifies length will be able to determine by how much from
@@ -971,28 +1069,26 @@
 
 static int process(void)
 {         /* Input(s) -> Balancing -> Combiner -> Effects -> Output */
-  unsigned i;
   int flow_status;
 
-  for (i = 0; i < nuser_effects; ++i)
-    *user_efftab[i] = efftab_options[i];
+  create_user_effects();
 
-  calculate_combiner_and_output_signal_parameters();
+  calculate_combiner_signal_parameters();
   set_combiner_and_output_encoding_parameters();
+
+  calculate_output_signal_parameters();
   open_output_file();
 
-  ofile_effects_chain.length = 0;
+  if (!effects_chain)
+    effects_chain = sox_create_effects_chain(&combiner_encoding, 
+                                             &ofile->ft->encoding);
+  add_effects(effects_chain);
 
-  ofile_effects_chain.global_info = sox_effects_globals;
-  ofile_effects_chain.in_enc = &combiner_encoding;
-  ofile_effects_chain.out_enc = &ofile->ft->encoding;
-  add_effects(&ofile_effects_chain);
-
   optimize_trim();
 
   signal(SIGTERM, sigint); /* Stop gracefully, as soon as we possibly can. */
   signal(SIGINT , sigint); /* Either skip current input or behave as SIGTERM. */
-  flow_status = sox_flow_effects(&ofile_effects_chain, update_status);
+  flow_status = sox_flow_effects(effects_chain, update_status);
 
   /* When in sox_multiple mode, changing to a new output file is
    * based on effects return ST_EOF.  In that case, don't return
@@ -1674,7 +1770,6 @@
 static void parse_effects(int argc, char **argv)
 {
   for (nuser_effects = 0; optind < argc; ++nuser_effects) {
-    sox_effect_t * e;
     int i;
 
     if (nuser_effects >= MAX_USER_EFF) {
@@ -1683,17 +1778,12 @@
     }
 
     /* Name should always be correct! */
-    e = sox_create_effect(sox_find_effect(argv[optind++]));
+    user_effargs[nuser_effects].name = strdup(argv[optind++]);
+    for (i = 0; i < argc - optind && !sox_find_effect(argv[optind + i]); ++i)
+      user_effargs[nuser_effects].argv[i] = strdup(argv[optind + i]);
+    user_effargs[nuser_effects].argc = i;
 
-    for (i = 0; i < argc - optind && !sox_find_effect(argv[optind + i]); ++i);
-    if (e->handler.getopts(e, i, &argv[optind]) == SOX_EOF)
-      exit(1); /* The failing effect should have displayed an error message */
-
     optind += i; /* Skip past the effect arguments */
-
-    if (e->handler.flags & SOX_EFF_DEPRECATED)
-      sox_warn("effect `%s' is deprecated; see soxeffect(7) for an alternative", e->handler.name);
-    user_efftab[nuser_effects] = e;
   }
 }
 
@@ -1898,15 +1988,6 @@
     exit(0);
   }
 
-  /* Bit of a hack: input files can get # of chans from an effect */
-  for (i = 0; i < input_count; i++) {
-    unsigned j;
-    for (j =0; j < nuser_effects && !files[i]->ft->signal.channels; ++j)
-      files[i]->ft->signal.channels = user_efftab[j]->in_signal.channels;
-    if (!files[i]->ft->signal.channels)
-      ++files[i]->ft->signal.channels;
-  }
-
   if (sox_globals.repeatable)
     sox_debug("Not reseeding PRNG; randomness is repeatable");
   else {
@@ -1920,8 +2001,6 @@
    * block of input files.  Also used by sox_multiple output mode. */
   ofile_signal_options = ofile->signal;
   ofile_encoding_options = ofile->encoding;
-  for (i = 0; i < nuser_effects; ++i)
-    efftab_options[i] = *user_efftab[i];
 
   if (combine_method == sox_sequence || output_method == sox_multiple) do {
     /* If ofile->ft is set then we've called this at least once.
@@ -1935,16 +2014,29 @@
        * If input reached EOF then restart all effects.
        */
       if (output_method == sox_multiple && !input_eof)
-          /* FIXME: Make a function that does that */
-          ;
-      else if (output_method == sox_multiple)
-          sox_delete_effects(&ofile_effects_chain);
+      {
+        size_t e;
+
+        /* Scan backwards until data is found and do not free input effect */
+        for (e = effects_chain->length-1; e > 0; --e)
+        {
+          if (effects_chain->effects[e]->obeg != 
+              effects_chain->effects[e]->oend)
+            break;
+          sox_delete_effect(effects_chain->effects[e]);
+          effects_chain->effects[e] = NULL;
+          effects_chain->length--;
+        }
+      }
+      else 
+        sox_delete_effects(effects_chain);
       sox_close(ofile->ft);
       ofile->ft = NULL;
     }
   } while (process() != SOX_EOF && !user_abort && current_input < input_count);
   else process();
-  sox_delete_effects(&ofile_effects_chain);
+
+  sox_delete_effects_chain(effects_chain);
 
   for (i = 0; i < file_count; ++i)
     if (files[i]->ft->clips != 0)
--- a/src/sox.h
+++ b/src/sox.h
@@ -476,6 +476,7 @@
 
 sox_effect_handler_t const * sox_find_effect(char const * name);
 sox_effect_t * sox_create_effect(sox_effect_handler_t const * eh);
+int sox_effect_options(sox_effect_t *effp, int argc, char * const argv[]);
 
 /* Effects chain */
 
@@ -493,13 +494,14 @@
 };
 typedef struct sox_effects_chain sox_effects_chain_t;
 sox_effects_chain_t * sox_create_effects_chain(
-    sox_encodinginfo_t const * in_enc,
-    sox_encodinginfo_t const * out_enc);
+    sox_encodinginfo_t const * in_enc, sox_encodinginfo_t const * out_enc);
+void sox_delete_effects_chain(sox_effects_chain_t *ecp);
 int sox_add_effect( sox_effects_chain_t * chain, sox_effect_t * effp, sox_signalinfo_t * in, sox_signalinfo_t const * out);
 int sox_flow_effects(sox_effects_chain_t *, int (* callback)(sox_bool all_done));
 size_t sox_effects_clips(sox_effects_chain_t *);
 size_t sox_stop_effect(sox_effect_t *effp);
-void sox_delete_effects(sox_effects_chain_t *);
+void sox_delete_effect(sox_effect_t *effp);
+void sox_delete_effects(sox_effects_chain_t *chain);
 
 /* The following routines are unique to the trim effect.
  * sox_trim_get_start can be used to find what is the start