shithub: sox

Download patch

ref: d198b7490bcdaa714c43bf5852d89c6ceba33aa6
parent: 064994fefb926acf283ac3518af52942d648a7a8
author: cbagwell <cbagwell>
date: Sun Sep 28 22:15:56 EDT 2008

Add new psuedo-effects ":" and "newfile" to allow running more then
1 effects chain over a single input file.  newfile will also create
a new file when an effects chain terminates.  Removed --output
option since newfile duplicates its function.

--- a/ChangeLog
+++ b/ChangeLog
@@ -64,8 +64,10 @@
 
 Other new features:
 
-  o New --output option to write to multiple files in one run.
-    Only useful with certain effects like trim and silence. (cbagwell)
+  o New psuedo-effects "newfile" and ":" to allow running
+    multiple effect chains on a single file.  newfile will
+    create a new output file when an effect chain terminates.
+    Of most use with trim and silence effects. (cbagwell)
   o Display SoX build environment information with -V -V.  (robs)
   o Display (with -V) the detected file-type if it differs from the 
     file extension.  (robs)
--- a/sox.1
+++ b/sox.1
@@ -459,20 +459,13 @@
 .SS Output Files
 SoX's default behavior is to take one or more input files and
 write them to a single output file.  In fact, the command line syntax
-only supports specifying a single filename in all cases.
+only supports specifying a single filename.
 
 SoX's output routine can be configured to output multiple files in
-certain cases.  If the output method 'multiple' is specified
-then SoX will create a new file each time the specified effects
-terminate the output early.  Each time a new file is created, the effects
-chain is restarted.  It is important to place the effect that
-stops processing at the front of the chain because all samples
-in the pipeline before it will be discarded.
+certain cases.  If the psuedo-effect 'newfile' is specified
+then SoX will create a new file once the specified effects
+chain stops the output early.
 
-There are only a few effects that can stop processing input early such
-as the trim and silence effects so the application for multiple files
-is very limited.
-
 In multiple output mode, a unique number will automatically be appended
 to the end of the filename.  If the filename has an extension 
 then the number is inserted before the extension.  This behavior can
@@ -613,11 +606,6 @@
 .SP
 See \fBInput File Combining\fR above for a description of the different
 combining methods.
-.TP
-\fB\-\-output single\fR\^|\^\fBmultiple\fR
-Select single or multiple output file mode.
-See \fBOutput Files\fR above for a description of the different
-output modes.
 .TP
 \fB\-\-plot gnuplot\fR\^|\^\fBoctave\fR\^|\^\fBoff\fR
 If not set to
--- a/src/effects.c
+++ b/src/effects.c
@@ -393,6 +393,25 @@
   return clips;
 }
 
+void sox_push_effect_last(sox_effects_chain_t *chain, sox_effect_t *effp)
+{
+  chain->effects[chain->length++] = effp;;
+} /* sox_push_effect_last */
+
+sox_effect_t *sox_pop_effect_last(sox_effects_chain_t *chain)
+{
+  if (chain->length > 0)
+  {
+    sox_effect_t *effp;
+    chain->length--;
+    effp = chain->effects[chain->length];
+    chain->effects[chain->length] = NULL;
+    return effp;
+  }
+  else
+    return NULL;
+} /* sox_pop_effect_last */
+
 /* Free resources related to effect.
  * Note: This currently closes down the effect which might
  * note be obvious from name.
--- a/src/sox.c
+++ b/src/sox.c
@@ -130,12 +130,13 @@
 #define MAX_USER_EFF (SOX_MAX_EFFECTS - 4)
 static sox_effect_t *user_efftab[MAX_USER_EFF];
 static sox_effects_chain_t *effects_chain = NULL;
+static sox_effect_t *save_output_eff = NULL;
 
-#define MAX_USER_EFF_LOOPS 256
-static struct { char *name; int argc; char *argv[FILENAME_MAX]; } user_effargs[MAX_USER_EFF_LOOPS][MAX_USER_EFF];
-static unsigned nuser_effects[MAX_USER_EFF_LOOPS];
-static int current_eff_loop = 0;
-static int eff_loop_count = 0;
+#define MAX_USER_EFF_CHAINS 256
+static struct { char *name; int argc; char *argv[FILENAME_MAX]; } user_effargs[MAX_USER_EFF_CHAINS][MAX_USER_EFF];
+static unsigned nuser_effects[MAX_USER_EFF_CHAINS];
+static int current_eff_chain = 0;
+static int eff_chain_count = 0;
 static char *effects_filename = NULL;
 
 /* Flowing */
@@ -579,7 +580,7 @@
   unsigned j;
   int i, k;
 
-  for (i = 0; i < eff_loop_count; i++) 
+  for (i = 0; i < eff_chain_count; i++) 
   {
     for (j = 0; j < nuser_effects[i]; j++) 
     {
@@ -596,32 +597,69 @@
     }
     nuser_effects[i] = 0;
   }
-  eff_loop_count = 0;
+  eff_chain_count = 0;
 } /* delete_user_effargs */
 
 static void parse_effects(int argc, char **argv)
 {
-  unsigned i;
-
-  nuser_effects[eff_loop_count] = 0;
-  for (i = 0; optind < argc; i++) {
+  nuser_effects[eff_chain_count] = 0;
+  while (optind < argc) {
     unsigned eff_offset;
     int j;
+    int newfile_mode = 0;
 
-    eff_offset = nuser_effects[eff_loop_count];
+    if (eff_chain_count >= MAX_USER_EFF_CHAINS) {
+      sox_fail("too many effects chains specified (at most %i allowed)", MAX_USER_EFF_CHAINS);
+      exit(1);
+    }
+
+    eff_offset = nuser_effects[eff_chain_count];
     if (eff_offset >= MAX_USER_EFF) {
       sox_fail("too many effects specified (at most %i allowed)", MAX_USER_EFF);
       exit(1);
     }
 
+    /* psuedo-effect ":" is used to create a new effects chain */
+    if (strcmp(argv[optind], ":") == 0)
+    {
+      /* Only create a new chain if current one has effects.
+       * Error checking will be done when loop is restarted.
+       */
+      if (nuser_effects[eff_chain_count] != 0)
+        nuser_effects[++eff_chain_count] = 0;
+      optind++;
+      continue;
+    }
+    
+    if (strcmp(argv[optind], "newfile") == 0)
+    {
+      /* Start a new effect chain for newfile if user doesn't
+       * manually do it.  Restart loop without advancing
+       * optind to do error checking.
+       */
+      if (nuser_effects[eff_chain_count] != 0)
+      {
+        nuser_effects[++eff_chain_count] = 0;
+        continue;
+      }
+      newfile_mode = 1;
+    }
+
     /* Name should always be correct! */
-    user_effargs[eff_loop_count][eff_offset].name = strdup(argv[optind++]);
-    for (j = 0; j < argc - optind && !sox_find_effect(argv[optind + j]); ++j)
-      user_effargs[eff_loop_count][eff_offset].argv[j] = strdup(argv[optind + j]);
-    user_effargs[eff_loop_count][eff_offset].argc = j;
+    user_effargs[eff_chain_count][eff_offset].name = strdup(argv[optind++]);
+    for (j = 0; j < argc - optind && !sox_find_effect(argv[optind + j]) &&
+         strcmp(":", argv[optind + j]) && strcmp("newfile", argv[optind + j]); 
+         ++j)
+      user_effargs[eff_chain_count][eff_offset].argv[j] = strdup(argv[optind + j]);
+    user_effargs[eff_chain_count][eff_offset].argc = j;
 
     optind += j; /* Skip past the effect arguments */
-    nuser_effects[eff_loop_count]++;
+    nuser_effects[eff_chain_count]++;
+    if (newfile_mode) 
+    {
+      output_method = sox_multiple;
+      nuser_effects[++eff_chain_count] = 0;
+    }
   }
 } /* parse_effects */
 
@@ -689,7 +727,7 @@
     int len;
 
     delete_user_effargs();
-    current_eff_loop = 0;
+    current_eff_chain = 0;
 
     if (file == NULL)
     {
@@ -707,6 +745,14 @@
 
       argc = strtoargv(s, &argv);
 
+      /* Make sure first option is an effect name. */
+      if (!sox_find_effect(argv[0]) && !strcmp("newfile", argv[0]) &&
+          !strcmp(":", argv[0]))
+      {
+        printf("Cannot find an effect called `%s'.\n", argv[0]);
+        exit(1);
+      }
+
       /* parse_effects normally parses options from command line.
        * Reset opt index so it thinks its back at beginning of
        * main()'s argv[].
@@ -713,16 +759,13 @@
        */
       optind = 0;
       parse_effects(argc, argv);
-      eff_loop_count++;
+      /* Advance to next effect but only if current chain has been
+       * filled in.  This recovers from side affects of psuedo-effects.
+       */
+      if (nuser_effects[eff_chain_count] > 0)
+        eff_chain_count++;
     }
 
-    /* More then 1 user effect in loop currently means multiple
-     * output mode or else it will simply keep overwriting the
-     * same file each loop.
-     */
-    if (eff_loop_count > 1)
-      output_method = sox_multiple;
-
     fclose(file);
 
 } /* read_user_effects */
@@ -743,9 +786,9 @@
   unsigned i;
   sox_effect_t *effp;
 
-  for (i = 0; i < nuser_effects[current_eff_loop]; i++) 
+  for (i = 0; i < nuser_effects[current_eff_chain]; i++) 
   {
-      effp = sox_create_effect(sox_find_effect(user_effargs[current_eff_loop][i].name));
+      effp = sox_create_effect(sox_find_effect(user_effargs[current_eff_chain][i].name));
 
       if (!effp)
         sox_fail("Failed creating effect.  Out of Memory?\n");
@@ -755,8 +798,8 @@
                  effp->handler.name);
 
       /* The failing effect should have displayed an error message */
-      if (sox_effect_options(effp, user_effargs[current_eff_loop][i].argc, 
-                             user_effargs[current_eff_loop][i].argv) == SOX_EOF)
+      if (sox_effect_options(effp, user_effargs[current_eff_chain][i].argc, 
+                             user_effargs[current_eff_chain][i].argv) == SOX_EOF)
         exit(1);
 
       user_efftab[i] = effp;
@@ -767,10 +810,9 @@
  * 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.
+ * This can be called with the input effect already in the effects
+ * chain from a previous run.  Also, it use a pre-existing
+ * output effect if its been saved into save_output_eff.
  */
 static void add_effects(sox_effects_chain_t *chain)
 {
@@ -777,51 +819,37 @@
   sox_signalinfo_t signal = combiner_signal;
   unsigned i;
   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";
 
-  effects_added++;
-  if (effects_added > chain->length)
+  /* 1st `effect' in the chain is the input combiner_signal.
+   * add it only if its not there from a previous run.
+   */
+  if (chain->length == 0)
   {
-    /* 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[current_eff_loop]; 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]);
+  for (i = 0; i < nuser_effects[current_eff_chain]; i++) {
+    /* Effects chain should have displayed an error message */
+    if (sox_add_effect(chain, user_efftab[i], 
+                       &signal, &ofile->ft->signal) != SOX_SUCCESS)
+      exit(2);
   }
 
   /* Add auto effects if still needed at this point */
   if (signal.rate != ofile->ft->signal.rate)
   {
-    effects_added++;
-    if (effects_added > chain->length)
-      add_effect(chain, "rate", rate_arg != NULL, &rate_arg, &signal); /* Must be up-sampling */
+    add_effect(chain, "rate", rate_arg != NULL, &rate_arg, &signal); /* Must be up-sampling */
   }
   if (signal.channels != ofile->ft->signal.channels)
   {
-    effects_added++;
-    if (effects_added > chain->length)
-      add_effect(chain, "mixer", 0, NULL, &signal); /* Must be increasing channels */
+    add_effect(chain, "mixer", 0, NULL, &signal); /* Must be increasing channels */
   }
     
-  effects_added++;
-  if (effects_added > chain->length)
+  if (!save_output_eff)
   {
     /* Last `effect' in the chain is the output file */
     effp = sox_create_effect(output_effect_fn());
@@ -828,6 +856,11 @@
     if (sox_add_effect(chain, effp, &signal, &ofile->ft->signal) != SOX_SUCCESS)
       exit(2);
   }
+  else
+  {
+    sox_push_effect_last(chain, save_output_eff);
+    save_output_eff = NULL;
+  }
 
   for (i = 0; i < chain->length; ++i) {
     sox_effect_t const * effp = &chain->effects[i][0];
@@ -837,6 +870,47 @@
   }
 }
 
+static int advance_eff_chain(void)
+{
+  /* If input file reached EOF then delete all effects in current
+   * chain and restart the current chain.
+   *
+   * This is only used with sox_sequence combine mode even though
+   * we do not specifically check for that method.
+   */
+  if (input_eof)
+    sox_delete_effects(effects_chain);
+  else
+  {
+    /* Effect chain stopped so advance to next effect chain but
+     * quite if no more chains exist.
+     */
+    if (++current_eff_chain >= eff_chain_count) 
+      return SOX_EOF;
+
+    /* Save off the output effect handler to be reused except
+     * when pseudo-effect "newfile" is spcified.
+     */
+    if (nuser_effects[current_eff_chain] == 1 &&
+        strcmp("newfile", user_effargs[current_eff_chain][0].name) == 0)
+    {
+      if (++current_eff_chain >= eff_chain_count) 
+        return SOX_EOF;
+    }
+    else
+      save_output_eff = sox_pop_effect_last(effects_chain);
+
+    /* TODO: Warn user when an effect is deleted that still
+     * had unprocessed samples.
+     */
+    while (effects_chain->length > 1)
+    {
+      sox_delete_effect_last(effects_chain);
+    }
+  }
+  return SOX_SUCCESS;
+} /* advance_eff_chain */
+
 static size_t total_clips(void)
 {
   unsigned i;
@@ -1061,6 +1135,10 @@
   sox_oob_t oob = files[0]->ft->oob;
   char *expand_fn;
 
+  /* Skip opening file if we are not recreating output effect */
+  if (save_output_eff)
+    return;
+
   oob.comments = sox_copy_comments(files[0]->ft->oob.comments);
 
   if (!oob.comments && !p)
@@ -1128,7 +1206,7 @@
    */
   for (i = 0; i < input_count; i++) {
     unsigned j;
-    for (j =0; j < nuser_effects[current_eff_loop] && 
+    for (j =0; j < nuser_effects[current_eff_chain] && 
                !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. */
@@ -1212,10 +1290,10 @@
 
   /* If no user option for output rate or # of channels, set from the last
    * effect that sets these, or from the input combiner if there is none such */
-  for (i = 0; i < nuser_effects[current_eff_loop] && !ofile->signal.rate; ++i)
-    ofile->signal.rate = user_efftab[nuser_effects[current_eff_loop] - 1 - i]->out_signal.rate;
-  for (i = 0; i < nuser_effects[current_eff_loop] && !ofile->signal.channels; ++i)
-    ofile->signal.channels = user_efftab[nuser_effects[current_eff_loop] - 1 - i]->out_signal.channels;
+  for (i = 0; i < nuser_effects[current_eff_chain] && !ofile->signal.rate; ++i)
+    ofile->signal.rate = user_efftab[nuser_effects[current_eff_chain] - 1 - i]->out_signal.rate;
+  for (i = 0; i < nuser_effects[current_eff_chain] && !ofile->signal.channels; ++i)
+    ofile->signal.channels = user_efftab[nuser_effects[current_eff_chain] - 1 - i]->out_signal.channels;
   if (!ofile->signal.rate)
     ofile->signal.rate = combiner_signal.rate;
   if (!ofile->signal.channels)
@@ -1228,7 +1306,7 @@
    * 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
    * its getopts parameters, so olen should be calculable. */
-  for (i = 0; i < nuser_effects[current_eff_loop]; i++)
+  for (i = 0; i < nuser_effects[current_eff_chain]; i++)
     known_length = known_length && !(user_efftab[i]->handler.flags & SOX_EFF_LENGTH);
 
   if (!known_length)
@@ -1284,13 +1362,14 @@
   signal(SIGINT , sigint); /* Either skip current input or behave as SIGTERM. */
   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
-   * SOX_EOF.  Treat input EOF or errors writing to output file as
-   * real error case.
+  /* Don't return SOX_EOF if
+   * 1) input reach EOF and there are more input files to process or
+   * 2) output didn't return EOF (disk full?) there are more
+   *    effect chains.
+   * For case #2, something else must decide when to stop processing.
    */
-  if (output_method == sox_multiple && current_input < input_count && 
-      !(input_eof || output_eof))
+  if ((input_eof && current_input < input_count) || 
+      (!output_eof && current_eff_chain < eff_chain_count))
     flow_status = SOX_SUCCESS;
 
   return flow_status;
@@ -1411,8 +1490,6 @@
 "--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)",
-"--output single          Write to single output file (default)",
-"--output multiple        Write to multiple output file",
 "--plot gnuplot|octave    Generate script 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)",
@@ -1648,12 +1725,6 @@
   ENUM_ITEM(sox_,multiply)
   {0, 0}};
 
-static enum_item const output_methods[] = {
-  ENUM_ITEM(sox_,single)
-  ENUM_ITEM(sox_,multiple)
-  {0, 0}};
-
-
 enum {ENDIAN_little, ENDIAN_big, ENDIAN_swap};
 static enum_item const endian_options[] = {
   ENUM_ITEM(ENDIAN_,little)
@@ -1789,10 +1860,6 @@
         exit(0);
         break;
 
-      case 13:
-        output_method = enum_option(option_index, output_methods);
-        break;
-
       case 14:
         effects_filename = strdup(optarg);
         break;
@@ -2216,10 +2283,11 @@
 
   /* Loop through the rest of the arguments looking for effects */
   parse_effects(argc, argv);
-  eff_loop_count++;
+  if (eff_chain_count == 0 || nuser_effects[eff_chain_count] > 0)
+    eff_chain_count++;
 
   /* Not the best way for users to do this; now deprecated in favour of soxi. */
-  if (!show_progress && !nuser_effects[current_eff_loop] && 
+  if (!show_progress && !nuser_effects[current_eff_chain] && 
       ofile->filetype && !strcmp(ofile->filetype, "null")) {
     for (i = 0; i < input_count; i++)
       report_file_info(files[i]);
@@ -2236,7 +2304,7 @@
   }
 
   /* Save things that sox_sequence needs to be reinitialised for each segued
-   * block of input files.  Also used by sox_multiple output mode. */
+   * block of input files.*/
   ofile_signal_options = ofile->signal;
   ofile_encoding_options = ofile->encoding;
 
@@ -2251,27 +2319,14 @@
 
   while (process() != SOX_EOF && !user_abort && current_input < input_count)
   {
-    if (input_eof)
-      sox_delete_effects(effects_chain);
-    else
+    if (advance_eff_chain() == SOX_EOF)
+      break;
+
+    if (!save_output_eff)
     {
-      /* if input hasn't reached EOF, delete all effects except for
-       * input effect.
-       */
-      /* TODO: Warn user when an effect is deleted that still
-       * had unprocessed samples.
-       */
-      while (effects_chain->length > 1)
-      {
-        sox_delete_effect_last(effects_chain);
-      }
+      sox_close(ofile->ft);
+      ofile->ft = NULL;
     }
-
-    /* Advance to next effect loop; with rollover */
-    current_eff_loop++;
-    if (current_eff_loop >= eff_loop_count) current_eff_loop = 0;
-    sox_close(ofile->ft);
-    ofile->ft = NULL;
   }
 
   sox_delete_effects_chain(effects_chain);
--- a/src/sox.h
+++ b/src/sox.h
@@ -501,6 +501,8 @@
 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_push_effect_last(sox_effects_chain_t *chain, sox_effect_t *effp);
+sox_effect_t *sox_pop_effect_last(sox_effects_chain_t *chain);
 void sox_delete_effect(sox_effect_t *effp);
 void sox_delete_effect_last(sox_effects_chain_t *chain);
 void sox_delete_effects(sox_effects_chain_t *chain);