shithub: sox

Download patch

ref: 2423ba7e9e5941c6e4549e27f2d7c9a6d2e7da23
parent: b106f2ad26fda2a76299020097a8cb50fda01dd2
author: robs <robs>
date: Sun Feb 17 13:04:00 EST 2008

try to order functions from least to most significant (ish)

--- a/src/sox.c
+++ b/src/sox.c
@@ -137,193 +137,6 @@
 static sox_sample_t omax[2], omin[2];
 
 
-/* local forward declarations */
-
-static sox_bool parse_gopts_and_fopts(file_t, int, char **);
-static int process(void);
-static void display_status(sox_bool all_done);
-
-
-static void display_SoX_version(FILE * file)
-{
-  fprintf(file, "%s: SoX v%s\n", myname, PACKAGE_VERSION);
-}
-
-static int strcmp_p(const void *p1, const void *p2)
-{
-  return strcmp(*(const char **)p1, *(const char **)p2);
-}
-
-static void display_supported_formats(void)
-{
-  size_t i, formats;
-  char const * * format_list;
-  char const * const * names;
-
-  for (i = 0, formats = 0; i < sox_formats; i++) {
-    char const * const *names = sox_format_fns[i].fn()->names;
-    while (*names++)
-      formats++;
-  }
-  format_list = (const char **)xmalloc(formats * sizeof(char *));
-
-  printf("AUDIO FILE FORMATS:");
-  for (i = 0, formats = 0; i < sox_formats; i++) {
-    sox_format_handler_t const * handler = sox_format_fns[i].fn();
-    if (!(handler->flags & SOX_FILE_DEVICE))
-      for (names = handler->names; *names; ++names)
-        format_list[formats++] = *names;
-  }
-  qsort(format_list, formats, sizeof(char *), strcmp_p);
-  for (i = 0; i < formats; i++)
-    printf(" %s", format_list[i]);
-  putchar('\n');
-
-  printf("PLAYLIST FORMATS: m3u pls\nAUDIO DEVICES:");
-  for (i = 0, formats = 0; i < sox_formats; i++) {
-    sox_format_handler_t const * handler = sox_format_fns[i].fn();
-    if ((handler->flags & SOX_FILE_DEVICE) && !(handler->flags & SOX_FILE_PHONY))
-      for (names = handler->names; *names; ++names)
-        format_list[formats++] = *names;
-  }
-  qsort(format_list, formats, sizeof(char *), strcmp_p);
-  for (i = 0; i < formats; i++)
-    printf(" %s", format_list[i]);
-  puts("\n");
-
-  free(format_list);
-}
-
-static void display_supported_effects(void)
-{
-  size_t i;
-  const sox_effect_handler_t *e;
-
-  printf("EFFECTS:");
-  for (i = 0; sox_effect_fns[i]; i++) {
-    e = sox_effect_fns[i]();
-    if (e && e->name && !(e->flags & SOX_EFF_DEPRECATED))
-      printf(" %s", e->name);
-  }
-  puts("\n");
-}
-
-static void usage(char const * message)
-{
-  size_t i;
-  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)",
-"--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)",
-"-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",
-"--add-comment TEXT  Append output file comment",
-"--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)",
-""};
-
-  display_SoX_version(stdout);
-  putchar('\n');
-
-  if (message)
-    fprintf(stderr, "Failed: %s\n\n", message);  /* N.B. stderr */
-
-  printf("Usage summary: [gopts] [[fopts] infile]... [fopts]%s [effect [effopts]]...\n\n",
-         sox_mode == sox_play? "" : " outfile");
-  for (i = 0; i < array_length(lines); ++i)
-    puts(lines[i]);
-  display_supported_formats();
-  display_supported_effects();
-  printf("effopts: depends on effect\n");
-  exit(message != NULL);
-}
-
-static void usage_effect(char const * name)
-{
-  int i;
-
-  display_SoX_version(stdout);
-  putchar('\n');
-
-  if (strcmp("all", name) && !sox_find_effect(name)) {
-    printf("Cannot find an effect called `%s'.", name);
-    display_supported_effects();
-  }
-  else {
-    printf("Effect usage:\n\n");
-
-    for (i = 0; sox_effect_fns[i]; i++) {
-      const sox_effect_handler_t *e = sox_effect_fns[i]();
-      if (e && e->name && (!strcmp("all", name) || !strcmp(e->name, name)))
-        printf("%s %s\n\n", e->name, e->usage? e->usage : "");
-    }
-  }
-  exit(1);
-}
-
-static void output_message(unsigned level, const char *filename, const char *fmt, va_list ap)
-{
-  if (sox_globals.verbosity >= level) {
-    fprintf(stderr, "%s ", myname);
-    sox_output_message(stderr, filename, fmt, ap);
-    fprintf(stderr, "\n");
-  }
-}
-
-static sox_bool overwrite_permitted(char const * filename)
-{
-  char c;
-
-  if (!interactive) {
-    sox_report("Overwriting `%s'", filename);
-    return sox_true;
-  }
-  sox_warn("Output file `%s' already exists", filename);
-  if (!isatty(fileno(stdin)))
-    return sox_false;
-  do fprintf(stderr, "%s sox: overwrite `%s' (y/n)? ", myname, filename);
-  while (scanf(" %c%*[^\n]", &c) != 1 || !strchr("yYnN", c));
-  return c == 'y' || c == 'Y';
-}
-
 /* Cleanup atexit() function, hence always called. */
 static void cleanup(void)
 {
@@ -436,6 +249,23 @@
     display_file_info(f->ft, f, sox_true);
 }
 
+static void display_error(sox_format_t * ft)
+{
+  static char const * const sox_strerror[] = {
+    "Invalid Audio Header",
+    "Unsupported data format",
+    "Unsupported rate for format",
+    "Can't alloc memory",
+    "Operation not permitted",
+    "Operation not supported",
+    "Invalid argument",
+    "Unsupported file format",
+  };
+  sox_fail("%s: %s (%s)", ft->filename, ft->sox_errstr,
+      ft->sox_errno < SOX_EHDR?
+      strerror(ft->sox_errno) : sox_strerror[ft->sox_errno - SOX_EHDR]);
+}
+
 static void progress_to_file(file_t f)
 {
   if (user_skip) {
@@ -454,331 +284,687 @@
   f->ft->sox_errno = errno = 0;
 }
 
-static sox_bool since(struct timeval * then, double secs, sox_bool always_reset)
+static sox_size_t sox_read_wide(sox_format_t * ft, sox_sample_t * buf, sox_size_t max)
 {
-  sox_bool ret;
-  struct timeval now;
-  time_t d;
-  gettimeofday(&now, NULL);
-  d = now.tv_sec - then->tv_sec;
-  ret = d > ceil(secs) || now.tv_usec - then->tv_usec + d * TIME_FRAC >= secs * TIME_FRAC;
-  if (ret || always_reset)
-    *then = now;
-  return ret;
+  sox_size_t len = max / combiner.channels;
+  len = sox_read(ft, buf, len * ft->signal.channels) / ft->signal.channels;
+  if (!len && ft->sox_errno)
+    display_error(ft);
+  return len;
 }
 
-static void init_file(file_t f)
+static void balance_input(sox_sample_t * buf, sox_size_t ws, file_t f)
 {
-  memset(f, 0, sizeof(*f));
-  f->signal.reverse_bytes = SOX_OPTION_DEFAULT;
-  f->signal.reverse_nibbles = SOX_OPTION_DEFAULT;
-  f->signal.reverse_bits = SOX_OPTION_DEFAULT;
-  f->signal.compression = HUGE_VAL;
-  f->volume = HUGE_VAL;
-  f->replay_gain = HUGE_VAL;
+  sox_size_t s = ws * f->ft->signal.channels;
+
+  if (f->volume != 1)
+    while (s--) {
+      double d = f->volume * *buf;
+      *buf++ = SOX_ROUND_CLIP_COUNT(d, f->volume_clips);
+    }
 }
 
-static void set_replay_gain(comments_t comments, file_t f)
+typedef struct input_combiner
 {
-  rg_mode rg = replay_gain_mode;
-  int try = 2; /* Will try to find the other GAIN if preferred one not found */
-  size_t i, n = num_comments(comments);
+  sox_sample_t *ibuf[MAX_INPUT_FILES];
+} * input_combiner_t;
 
-  if (rg != RG_off) while (try--) {
-    char const * target =
-      rg == RG_track? "REPLAYGAIN_TRACK_GAIN=" : "REPLAYGAIN_ALBUM_GAIN=";
-    for (i = 0; i < n; ++i) {
-      if (strncasecmp(comments[i], target, strlen(target)) == 0) {
-        f->replay_gain = atof(comments[i] + strlen(target));
-        return;
-      }
+assert_static(sizeof(struct input_combiner) <= SOX_MAX_EFFECT_PRIVSIZE,
+              /* else */ input_combiner_PRIVSIZE_too_big);
+
+static int combiner_start(sox_effect_t *effp)
+{
+  input_combiner_t z = (input_combiner_t) effp->priv;
+  sox_size_t ws, i;
+
+  if (combine_method <= sox_concatenate)
+    progress_to_file(files[current_input]);
+  else {
+    ws = 0;
+    for (i = 0; i < input_count; i++) {
+      z->ibuf[i] = (sox_sample_t *)xmalloc(sox_globals.bufsiz * sizeof(sox_sample_t));
+      progress_to_file(files[i]);
+      ws = max(ws, input_wide_samples);
     }
-    rg ^= RG_track ^ RG_album;
+    input_wide_samples = ws; /* Output length is that of longest input file. */
   }
+  return SOX_SUCCESS;
 }
 
-typedef enum {
-  full, rate, channels, samples, duration, bits, encoding, annotation} soxi_t;
+static sox_bool can_segue(sox_size_t i)
+{
+  return
+    files[i]->ft->signal.channels == files[i - 1]->ft->signal.channels &&
+    files[i]->ft->signal.rate     == files[i - 1]->ft->signal.rate;
+}
 
-static int soxi1(soxi_t * type, char * filename)
+static int combiner_drain(sox_effect_t *effp, sox_sample_t * obuf, sox_size_t * osamp)
 {
-  sox_size_t ws;
-  sox_format_t * ft = sox_open_read(filename, NULL, NULL);
+  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;
 
-  if (!ft)
-    return 1;
-  ws = ft->length / max(ft->signal.channels, 1);
-  switch (*type) {
-    case rate: printf("%g\n", ft->signal.rate); break;
-    case channels: printf("%u\n", ft->signal.channels); break;
-    case samples: printf("%u\n", ws); break;
-    case duration: printf("%s\n", str_time((double)ws / max(ft->signal.rate, 1))); break;
-    case bits: printf("%s\n", sox_size_bits_str[ft->signal.size]); break;
-    case encoding: printf("%s\n", sox_encodings_str[ft->signal.encoding]); break;
-    case annotation: if (ft->comments) {
-      comments_t p = ft->comments;
-      do printf("%s\n", *p); while (*++p);
+  if (combine_method <= sox_concatenate) while (sox_true) {
+    if (!user_skip)
+      olen = sox_read_wide(files[current_input]->ft, 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(obuf, olen, files[current_input]);
     break;
-    case full: display_file_info(ft, NULL, sox_false); break;
+  } else {
+    sox_sample_t * p = obuf;
+    for (i = 0; i < input_count; ++i) {
+      ilen[i] = sox_read_wide(files[i]->ft, 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)
+            if (ws < ilen[i] && s < files[i]->ft->signal.channels) {
+              /* Cast to double prevents integer overflow */
+              double sample = *p + (double)z->ibuf[i][ws * files[i]->ft->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]->ft->signal.channels; ++s)
+            *p++ = (ws < ilen[i]) * z->ibuf[i][ws * files[i]->ft->signal.channels + s];
+    }
   }
-  return !!sox_close(ft);
+  read_wide_samples += olen;
+  olen *= effp->ininfo.channels;
+  *osamp = olen;
+  return olen? SOX_SUCCESS : SOX_EOF;
 }
 
-static void soxi(int argc, char * const * argv)
+static int combiner_stop(sox_effect_t *effp)
 {
-  static char const opts[] = "rcsdbea?";
-  soxi_t type = full;
-  int opt, num_errors = 0;
+  input_combiner_t z = (input_combiner_t) effp->priv;
+  sox_size_t i;
 
-  while ((opt = getopt(argc, argv, opts)) > 0) /* act only on last option */
-    type = 1 + (strchr(opts, opt) - opts);
-  if (type > annotation)
-    printf("Usage: soxi [-r|-c|-s|-d|-b|-e|-a] infile1 ...\n");
-  else for (; optind < argc; ++optind) {
-    if (sox_is_playlist(argv[optind]))
-      num_errors += (sox_parse_playlist((sox_playlist_callback_t)soxi1, &type, argv[optind]) != SOX_SUCCESS);
-    else num_errors += soxi1(&type, argv[optind]);
+  if (combine_method > sox_concatenate)
+    /* Free input buffers now that they are not used */
+    for (i = 0; i < input_count; i++)
+      free(z->ibuf[i]);
+
+  return SOX_SUCCESS;
+}
+
+static sox_effect_handler_t const * input_combiner_effect_fn(void)
+{
+  static sox_effect_handler_t handler = {
+    "input", 0, SOX_EFF_MCHAN,
+    0, combiner_start, 0, combiner_drain, combiner_stop, 0
+  };
+  return &handler;
+}
+
+static int output_flow(sox_effect_t *effp UNUSED, sox_sample_t const * ibuf,
+    sox_sample_t * obuf UNUSED, sox_size_t * isamp, sox_size_t * osamp)
+{
+  size_t len;
+
+  for (len = 0; len < *isamp; len += effp->ininfo.channels) {
+    omax[0] = max(omax[0], ibuf[len]);
+    omin[0] = min(omin[0], ibuf[len]);
+    if (effp->ininfo.channels > 1) {
+      omax[1] = max(omax[1], ibuf[len + 1]);
+      omin[1] = min(omin[1], ibuf[len + 1]);
+    }
+    else {
+      omax[1] = omax[0];
+      omin[1] = omin[0];
+    }
   }
-  exit(num_errors);
+  for (*osamp = *isamp; *osamp; ibuf += len, *osamp -= len) {
+    len = sox_write(ofile->ft, ibuf, *osamp);
+    if (len == 0) {
+      sox_warn("Error writing: %s", ofile->ft->sox_errstr);
+      return SOX_EOF;
+    }
+    if (user_abort) /* Don't get stuck in this loop. */
+      return SOX_EOF;
+  }
+  output_samples += *isamp / ofile->ft->signal.channels;
+  return SOX_SUCCESS;
 }
 
-static char const * device_name(char const * const type)
+static sox_effect_handler_t const * output_effect_fn(void)
 {
-  char * name = NULL, * from_env = getenv("AUDIODEV");
+  static sox_effect_handler_t handler = {
+    "output", 0, SOX_EFF_MCHAN, NULL, NULL, output_flow, NULL, NULL, NULL
+  };
+  return &handler;
+}
 
-  if (!type)
-    return NULL;
-  if (!strcmp(type, "sunau")) name = "/dev/audio";
-  else if (!strcmp(type, "oss" ) || !strcmp(type, "ossdsp")) name = "/dev/dsp";
-  else if (!strcmp(type, "alsa") || !strcmp(type, "ao"))     name = "default";
-  return name? from_env? from_env : name : NULL;
+static void add_auto_effect(sox_effects_chain_t * chain, char const * name, sox_signalinfo_t * signal)
+{
+  sox_effect_t eff;
+
+  /* Auto effect should always succeed here */
+  sox_create_effect(&eff, sox_find_effect(name));
+  eff.handler.getopts(&eff, 0, NULL);          /* Set up with default opts */
+
+  /* But could fail here */
+  if (sox_add_effect(chain, &eff, signal, &ofile->ft->signal) != SOX_SUCCESS)
+    exit(2);
 }
 
-static char const * set_default_device(file_t f)
+/* If needed effects are not given, auto-add at (performance) optimal point. */
+static void add_effects(sox_effects_chain_t * chain)
 {
-  /* Default audio driver type in order of preference: */
-  if (!f->filetype) f->filetype = getenv("AUDIODRIVER");
-  if (!f->filetype && sox_find_format("alsa", sox_false)) f->filetype = "alsa";
-  if (!f->filetype && sox_find_format("oss" , sox_false)) f->filetype = "oss";
-  if (!f->filetype && sox_find_format("sunau",sox_false)) f->filetype = "sunau";
-  if (!f->filetype && sox_find_format("ao"  , sox_false) && file_count) /*!rec*/
-    f->filetype = "ao";
+  sox_signalinfo_t signal = combiner;
+  unsigned i, min_chan = 0, min_rate = 0;
+  sox_effect_t eff;
 
-  if (!f->filetype) {
-    sox_fail("Sorry, there is no default audio device configured");
-    exit(1);
+  /* Find points after which we might add effects to change rate/chans */
+  for (i = 0; i < nuser_effects; i++) {
+    if (user_efftab[i].handler.flags & (SOX_EFF_CHAN|SOX_EFF_MCHAN))
+      min_chan = i + 1;
+    if (user_efftab[i].handler.flags & SOX_EFF_RATE)
+      min_rate = i + 1;
   }
-  return device_name(f->filetype);
+  /* 1st `effect' in the chain is the input combiner */
+  sox_create_effect(&eff, input_combiner_effect_fn());
+  sox_add_effect(chain, &eff, &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", &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, "resample", &signal);
+
+    if (i < nuser_effects)
+      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)
+    add_auto_effect(chain, "resample", &signal);  /* Must be up-sampling */
+  if (signal.channels != ofile->ft->signal.channels)
+    add_auto_effect(chain, "mixer", &signal);     /* Must be increasing channels */
+
+  /* Last `effect' in the chain is the output file */
+  sox_create_effect(&eff, output_effect_fn());
+  if (sox_add_effect(chain, &eff, &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",
+        effp->handler.name, effp->ininfo.rate, effp->ininfo.channels, effp->ininfo.size * 8,
+        (effp->handler.flags & SOX_EFF_MCHAN)? "(multi)" : "");
+  }
 }
 
-static int add_file(struct file_info const * const opts, char const * const filename)
+static sox_size_t total_clips(void)
 {
-  file_t f = xmalloc(sizeof(*f));
+  unsigned i;
+  sox_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);
+}
 
-  if (file_count >= MAX_FILES) {
-    sox_fail("too many files; maximum is %d input files (and 1 output file)", MAX_INPUT_FILES);
-    exit(1);
+static char const * sigfigs3(sox_size_t number)
+{
+  static char string[16][10];
+  static unsigned n;
+  unsigned a, b, c = 2;
+  sprintf(string[n = (n+1) & 15], "%#.3g", (double)number);
+  if (sscanf(string[n], "%u.%ue%u", &a, &b, &c) == 3)
+    a = 100*a + b;
+  switch (c%3) {
+    case 0: sprintf(string[n], "%u.%02u%c", a/100,a%100, " kMGTPE"[c/3]); break;
+    case 1: sprintf(string[n], "%u.%u%c"  , a/10 ,a%10 , " kMGTPE"[c/3]); break;
+    case 2: sprintf(string[n], "%u%c"     , a          , " kMGTPE"[c/3]); break;
   }
-  *f = *opts;
-  if (!filename)
-    usage("missing filename"); /* No return */
-  f->filename = xstrdup(filename);
-  files[file_count++] = f;
-  return 0;
+  return string[n];
 }
 
-static void parse_options_and_filenames(int argc, char **argv)
+static char const * sigfigs3p(double percentage)
 {
-  struct file_info opts, opts_none;
-  init_file(&opts), init_file(&opts_none);
+  static char string[16][10];
+  static unsigned n;
+  sprintf(string[n = (n+1) & 15], "%.1f%%", percentage);
+  if (strlen(string[n]) < 5)
+    sprintf(string[n], "%.2f%%", percentage);
+  else if (strlen(string[n]) > 5)
+    sprintf(string[n], "%.0f%%", percentage);
+  return string[n];
+}
 
-  if (sox_mode == sox_rec)
-    add_file(&opts, set_default_device(&opts)), init_file(&opts);
+static char const * vu(unsigned channel)
+{
+  static char const * const text[][2] = {
+    {"", ""}, {"-", "-"}, {"=", "="}, {"-=", "=-"},
+    {"==", "=="}, {"-==", "==-"}, {"===", "==="}, {"-===", "===-"},
+    {"====", "===="}, {"-====", "====-"}, {"=====", "====="},
+    {"-=====", "=====-"}, {"======", "======"},
+    {"!=====", "=====!"}, {"!!====", "====!!"}, /* 2 `red' levels */
+  };
+  int const red = 2, white = array_length(text) - red;
+  double const MAX = SOX_SAMPLE_MAX, MIN = SOX_SAMPLE_MIN;
+  double linear = max(omax[channel] / MAX, omin[channel] / MIN);
+  int vu_dB = linear? floor(2 * white + red - .5 + linear_to_dB(linear)) : 0;
+  int index = vu_dB < 2 * white? max(vu_dB / 2, 0) : vu_dB - white;
+  omax[channel] = omin[channel] = 0;
+  return text[index][channel];
+}
 
-  for (; optind < argc && !sox_find_effect(argv[optind]); init_file(&opts)) {
-    if (parse_gopts_and_fopts(&opts, argc, argv)) { /* is null file? */
-      if (opts.filetype != NULL && strcmp(opts.filetype, "null") != 0)
-        sox_warn("Ignoring `-t %s'.", opts.filetype);
-      opts.filetype = "null";
-      add_file(&opts, "");
-    }
-    else if (optind >= argc || sox_find_effect(argv[optind]))
-      break;
-    else if (!sox_is_playlist(argv[optind]))
-      add_file(&opts, argv[optind++]);
-    else if (sox_parse_playlist((sox_playlist_callback_t)add_file, &opts, argv[optind++]) != SOX_SUCCESS)
-      exit(1);
-  }
-  if (sox_mode == sox_play)
-    add_file(&opts, set_default_device(&opts));
-  else if (memcmp(&opts, &opts_none, sizeof(opts))) /* fopts but no file */
-    add_file(&opts, device_name(opts.filetype));
+static sox_bool since(struct timeval * then, double secs, sox_bool always_reset)
+{
+  sox_bool ret;
+  struct timeval now;
+  time_t d;
+  gettimeofday(&now, NULL);
+  d = now.tv_sec - then->tv_sec;
+  ret = d > ceil(secs) || now.tv_usec - then->tv_usec + d * TIME_FRAC >= secs * TIME_FRAC;
+  if (ret || always_reset)
+    *then = now;
+  return ret;
 }
 
-static void parse_effects(int argc, char **argv)
+static void display_status(sox_bool all_done)
 {
-  for (nuser_effects = 0; optind < argc; ++nuser_effects) {
-    sox_effect_t *e = &user_efftab[nuser_effects];
-    int i;
+  static struct timeval then;
+  if (!show_progress)
+    return;
+  if (all_done || since(&then, .15, sox_false)) {
+    double read_time = (double)read_wide_samples / combiner.rate;
+    double left_time = 0, in_time = 0, percentage = 0;
 
-    if (nuser_effects >= MAX_USER_EFF) {
-      sox_fail("too many effects specified (at most %i allowed)", MAX_USER_EFF);
-      exit(1);
+    if (input_wide_samples) {
+      in_time = (double)input_wide_samples / combiner.rate;
+      left_time = max(in_time - read_time, 0);
+      percentage = max(100. * read_wide_samples / input_wide_samples, 0);
     }
+    fprintf(stderr, "\r%s [%s] of %s (%-5s) Samps out:%-5s%6s|%-6sClips:%-5s",
+      str_time(read_time), str_time(left_time), str_time(in_time),
+      sigfigs3p(percentage), sigfigs3(output_samples),
+      vu(0), vu(1), sigfigs3(total_clips()));
+  }
+  if (all_done)
+    fputc('\n', stderr);
+}
 
-    /* Name should always be correct! */
-    sox_create_effect(e, sox_find_effect(argv[optind++]));
+static int update_status(sox_bool all_done)
+{
+  display_status(all_done || user_abort);
+  return user_abort? SOX_EOF : SOX_SUCCESS;
+}
 
-    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 */
+static void optimize_trim(void)          
+{
+  /* Speed hack.  If the "trim" effect is the first effect then
+   * peek inside its "effect descriptor" and see what the
+   * start location is.  This has to be done after its start()
+   * is called to have the correct location.
+   * Also, only do this when only working with one input file.
+   * This is because the logic 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 (files[0]->ft->handler->seek && files[0]->ft->seekable){
+      sox_size_t offset = sox_trim_get_start(&ofile_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]);
+      }    
+    }        
+  }    
+}
 
-    optind += i; /* Skip past the effect arguments */
+static sox_bool overwrite_permitted(char const * filename)
+{
+  char c;
 
-    if (e->handler.flags & SOX_EFF_DEPRECATED)
-      sox_warn("Effect `%s' is deprecated; see sox(1) for an alternative", e->handler.name);
+  if (!interactive) {
+    sox_report("Overwriting `%s'", filename);
+    return sox_true;
   }
+  sox_warn("Output file `%s' already exists", filename);
+  if (!isatty(fileno(stdin)))
+    return sox_false;
+  do fprintf(stderr, "%s sox: overwrite `%s' (y/n)? ", myname, filename);
+  while (scanf(" %c%*[^\n]", &c) != 1 || !strchr("yYnN", c));
+  return c == 'y' || c == 'Y';
 }
 
-int main(int argc, char **argv)
+static void open_output_file(sox_size_t olen)
 {
-  size_t i;
+  sox_loopinfo_t loops[SOX_MAX_NLOOPS];
+  double factor;
+  int i;
+  comments_t comments = copy_comments(files[0]->ft->comments);
+  comments_t p = ofile->comments;
 
-  myname = argv[0];
-  atexit(cleanup);
-  sox_globals.output_message_handler = output_message;
+  if (!comments && !p)
+    append_comment(&comments, "Processed by SoX");
+  else if (p) {
+    if (!(*p)[0]) {
+      delete_comments(&comments);
+      ++p;
+    }
+    while (*p)
+      append_comment(&comments, *p++);
+  }
 
-  if (strends(myname, "play")) {
-    sox_mode = sox_play;
-    replay_gain_mode = RG_track;
-    combine_method = sox_sequence;
+  /*
+   * 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]->ft->loops[i].start * factor;
+    loops[i].length = files[0]->ft->loops[i].length * factor;
+    loops[i].count = files[0]->ft->loops[i].count;
+    loops[i].type = files[0]->ft->loops[i].type;
   }
-  else if (strends(myname, "rec"))
-    sox_mode = sox_rec;
-  else if (strends(myname, "soxi"))
-    sox_mode = sox_soxi;
 
-  if (sox_format_init() != SOX_SUCCESS)
-    exit(1);
- 
-  if (sox_mode == sox_soxi)
-    soxi(argc, argv);
+  ofile->ft = sox_open_write(overwrite_permitted,
+                        ofile->filename,
+                        &ofile->signal,
+                        ofile->filetype,
+                        comments,
+                        olen,
+                        &files[0]->ft->instr,
+                        loops);
+  delete_comments(&comments);
 
-  parse_options_and_filenames(argc, argv);
+  if (!ofile->ft)
+    /* sox_open_write() will call sox_warn for most errors.
+     * Rely on that printing something. */
+    exit(2);
 
-  if (sox_globals.verbosity > 2)
-    display_SoX_version(stderr);
- 
-  /* 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))
-    usage("Not enough input filenames specified");
+  /* 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->ft->handler->flags & SOX_FILE_DEVICE) != 0 &&
+                    (ofile->ft->handler->flags & SOX_FILE_PHONY) == 0;
 
-  /* Check for misplaced input/output-specific options */
-  for (i = 0; i < input_count; ++i) {
-    if (files[i]->signal.compression != HUGE_VAL)
-      usage("A compression factor can be given only for an output file");
-    if (files[i]->comments != NULL)
-      usage("Comments can be given only for an output file");
-  }
-  if (ofile->volume != HUGE_VAL)
-    usage("-v can be given only for an input file;\n"
-            "\tuse `vol' to set the output file volume");
+  report_file_info(ofile);
+}
 
-  signal(SIGINT, SIG_IGN); /* So child pipes aren't killed by track skip */
-  for (i = 0; i < input_count; i++) {
-    int j = input_count - 1 - i; /* Open in reverse order 'cos of rec (below) */
-    file_t f = files[j];
+static void sigint(int s)
+{
+  static struct timeval then;
+  if (input_count > 1 && show_progress && s == SIGINT &&
+      combine_method <= sox_concatenate && since(&then, 1.0, sox_true))
+    user_skip = sox_true;
+  else user_abort = sox_true;
+}
 
-    /* When mixing audio, default to input side volume adjustments that will
-     * make sure no clipping will occur.  Users probably won't be happy with
-     * this, and will override it, possibly causing clipping to occur. */
-    if (combine_method == sox_mix && !uservolume)
-      f->volume = 1.0 / input_count;
+/*
+ * Process:   Input(s) -> Balancing -> Combiner -> Effects -> Output
+ */
 
-    if (sox_mode == sox_rec && !j) {       /* Set the recording parameters: */
-      if (input_count > 1)                 /* from the (just openned) next */
-        f->signal = files[1]->ft->signal;  /* input file, or from the output */
-      else f->signal = files[1]->signal;   /* file (which is not open yet). */
-    }
-    files[j]->ft = sox_open_read(f->filename, &f->signal, f->filetype);
-    if (!files[j]->ft)
-      /* sox_open_read() will call sox_warn for most errors.
-       * Rely on that printing something. */
-      exit(2);
-    if (show_progress == SOX_OPTION_DEFAULT &&
-        (files[j]->ft->handler->flags & SOX_FILE_DEVICE) != 0 &&
-        (files[j]->ft->handler->flags & SOX_FILE_PHONY) == 0)
-      show_progress = SOX_OPTION_YES;
-    set_replay_gain(files[j]->ft->comments, f);
-  }
-  signal(SIGINT, SIG_DFL);
+static int process(void) {
+  int flowstatus = 0;
+  sox_size_t i;
+  sox_bool known_length = combine_method != sox_sequence;
+  sox_size_t olen = 0;
 
-  /* Loop through the rest of the arguments looking for effects */
-  parse_effects(argc, argv);
+  combiner = files[current_input]->ft->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;
 
-  /* Not the best way for users to do this; now deprecated in favour of soxi. */
-  if (!show_progress && !nuser_effects && ofile->filetype && !strcmp(ofile->filetype, "null")) {
-    for (i = 0; i < input_count; i++)
+    for (i = 0; i < input_count; i++) { /* Report all inputs, then check */
       report_file_info(files[i]);
-    exit(0);
+      total_channels += files[i]->ft->signal.channels;
+      min_channels = min(min_channels, files[i]->ft->signal.channels);
+      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->length != 0;
+      if (combine_method == sox_concatenate)
+        olen += files[i]->ft->length / files[i]->ft->signal.channels;
+      else
+        olen = max(olen, files[i]->ft->length / files[i]->ft->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);
+
+    combiner.channels = 
+      combine_method == sox_merge? total_channels : max_channels;
   }
 
-  /* Bit of a hack: input files can get # of chans from an effect */
-  for (i = 0; i < input_count; i++) {
+  ofile->signal = ofile_signal;
+  if (ofile->signal.rate == 0)
+    ofile->signal.rate = combiner.rate;
+  if (ofile->signal.size == 0)
+    ofile->signal.size = combiner.size;
+  if (ofile->signal.encoding == SOX_ENCODING_UNKNOWN)
+    ofile->signal.encoding = combiner.encoding;
+  if (ofile->signal.channels == 0) {
     unsigned j;
-    for (j =0; j < nuser_effects && !files[i]->ft->signal.channels; ++j)
-      files[i]->ft->signal.channels = user_efftab[j].ininfo.channels;
-    if (!files[i]->ft->signal.channels)
-      ++files[i]->ft->signal.channels;
+    for (j = 0; j < nuser_effects && !ofile->signal.channels; ++j)
+      ofile->signal.channels = user_efftab[nuser_effects - 1 - j].outinfo.channels;
+    if (ofile->signal.channels == 0)
+      ofile->signal.channels = combiner.channels;
   }
 
-  if (repeatable_random)
-    sox_debug("Not reseeding PRNG; randomness is repeatable");
-  else {
-    time_t t;
+  combiner.rate *= sox_effects_globals.speed;
 
-    time(&t);
-    srand((unsigned)t);
+  for (i = 0; i < nuser_effects; i++)
+    known_length = known_length && !(user_efftab[i].handler.flags & SOX_EFF_LENGTH);
+
+  if (!known_length)
+    olen = 0;
+
+  open_output_file((sox_size_t)(olen * ofile->signal.channels * ofile->signal.rate / combiner.rate + .5));
+
+  ofile_effects_chain.global_info = sox_effects_globals;
+  add_effects(&ofile_effects_chain);
+
+  optimize_trim();
+
+  signal(SIGINT, sigint);
+  /* FIXME: For SIGTERM at least we really should guarantee to stop quickly */
+  signal(SIGTERM, sigint); /* Stop gracefully even in extremis */
+  
+  flowstatus = sox_flow_effects(&ofile_effects_chain, update_status);
+
+  sox_delete_effects(&ofile_effects_chain);
+  return flowstatus;
+}
+
+static void display_SoX_version(FILE * file)
+{
+  fprintf(file, "%s: SoX v%s\n", myname, PACKAGE_VERSION);
+}
+
+static int strcmp_p(const void *p1, const void *p2)
+{
+  return strcmp(*(const char **)p1, *(const char **)p2);
+}
+
+static void display_supported_formats(void)
+{
+  size_t i, formats;
+  char const * * format_list;
+  char const * const * names;
+
+  for (i = 0, formats = 0; i < sox_formats; i++) {
+    char const * const *names = sox_format_fns[i].fn()->names;
+    while (*names++)
+      formats++;
   }
+  format_list = (const char **)xmalloc(formats * sizeof(char *));
 
-  ofile_signal = ofile->signal;
-  if (combine_method == sox_sequence) do {
-    if (ofile->ft)
-      sox_close(ofile->ft);
-  } while (process() != SOX_EOF && !user_abort && current_input < input_count);
-  else process();
+  printf("AUDIO FILE FORMATS:");
+  for (i = 0, formats = 0; i < sox_formats; i++) {
+    sox_format_handler_t const * handler = sox_format_fns[i].fn();
+    if (!(handler->flags & SOX_FILE_DEVICE))
+      for (names = handler->names; *names; ++names)
+        format_list[formats++] = *names;
+  }
+  qsort(format_list, formats, sizeof(char *), strcmp_p);
+  for (i = 0; i < formats; i++)
+    printf(" %s", format_list[i]);
+  putchar('\n');
 
-  for (i = 0; i < file_count; ++i)
-    if (files[i]->ft->clips != 0)
-      sox_warn(i < input_count?"%s: input clipped %u samples" :
-                              "%s: output clipped %u samples; decrease volume?",
-          (files[i]->ft->handler->flags & SOX_FILE_DEVICE)?
-                       files[i]->ft->handler->names[0] : files[i]->ft->filename,
-          files[i]->ft->clips);
+  printf("PLAYLIST FORMATS: m3u pls\nAUDIO DEVICES:");
+  for (i = 0, formats = 0; i < sox_formats; i++) {
+    sox_format_handler_t const * handler = sox_format_fns[i].fn();
+    if ((handler->flags & SOX_FILE_DEVICE) && !(handler->flags & SOX_FILE_PHONY))
+      for (names = handler->names; *names; ++names)
+        format_list[formats++] = *names;
+  }
+  qsort(format_list, formats, sizeof(char *), strcmp_p);
+  for (i = 0; i < formats; i++)
+    printf(" %s", format_list[i]);
+  puts("\n");
 
-  if (mixing_clips > 0)
-    sox_warn("mix-combining clipped %u samples; decrease volume?", mixing_clips);
+  free(format_list);
+}
 
-  for (i = 0; i < file_count; i++)
-    if (files[i]->volume_clips > 0)
-      sox_warn("%s: balancing clipped %u samples; decrease volume?", files[i]->filename,
-              files[i]->volume_clips);
+static void display_supported_effects(void)
+{
+  size_t i;
+  const sox_effect_handler_t *e;
 
-  if (show_progress) {
-    if (user_abort)
-      fprintf(stderr, "Aborted.\n");
-    else if (user_skip && sox_mode != sox_rec)
-      fprintf(stderr, "Skipped.\n");
-    else
-      fprintf(stderr, "Done.\n");
+  printf("EFFECTS:");
+  for (i = 0; sox_effect_fns[i]; i++) {
+    e = sox_effect_fns[i]();
+    if (e && e->name && !(e->flags & SOX_EFF_DEPRECATED))
+      printf(" %s", e->name);
   }
+  puts("\n");
+}
 
-  success = 1; /* Signal success to cleanup so the output file isn't removed. */
-  return 0;
+static void usage(char const * message)
+{
+  size_t i;
+  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)",
+"--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)",
+"-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",
+"--add-comment TEXT  Append output file comment",
+"--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)",
+""};
+
+  display_SoX_version(stdout);
+  putchar('\n');
+
+  if (message)
+    fprintf(stderr, "Failed: %s\n\n", message);  /* N.B. stderr */
+
+  printf("Usage summary: [gopts] [[fopts] infile]... [fopts]%s [effect [effopts]]...\n\n",
+         sox_mode == sox_play? "" : " outfile");
+  for (i = 0; i < array_length(lines); ++i)
+    puts(lines[i]);
+  display_supported_formats();
+  display_supported_effects();
+  printf("effopts: depends on effect\n");
+  exit(message != NULL);
 }
 
+static void usage_effect(char const * name)
+{
+  int i;
+
+  display_SoX_version(stdout);
+  putchar('\n');
+
+  if (strcmp("all", name) && !sox_find_effect(name)) {
+    printf("Cannot find an effect called `%s'.", name);
+    display_supported_effects();
+  }
+  else {
+    printf("Effect usage:\n\n");
+
+    for (i = 0; sox_effect_fns[i]; i++) {
+      const sox_effect_handler_t *e = sox_effect_fns[i]();
+      if (e && e->name && (!strcmp("all", name) || !strcmp(e->name, name)))
+        printf("%s %s\n\n", e->name, e->usage? e->usage : "");
+    }
+  }
+  exit(1);
+}
+
 static void read_comment_file(comments_t * comments, char const * const filename)
 {
   int c;
@@ -1069,516 +1255,323 @@
   }
 }
 
-static void sigint(int s)
+static char const * device_name(char const * const type)
 {
-  static struct timeval then;
-  if (input_count > 1 && show_progress && s == SIGINT &&
-      combine_method <= sox_concatenate && since(&then, 1.0, sox_true))
-    user_skip = sox_true;
-  else user_abort = sox_true;
+  char * name = NULL, * from_env = getenv("AUDIODEV");
+
+  if (!type)
+    return NULL;
+  if (!strcmp(type, "sunau")) name = "/dev/audio";
+  else if (!strcmp(type, "oss" ) || !strcmp(type, "ossdsp")) name = "/dev/dsp";
+  else if (!strcmp(type, "alsa") || !strcmp(type, "ao"))     name = "default";
+  return name? from_env? from_env : name : NULL;
 }
 
-static sox_bool can_segue(sox_size_t i)
+static char const * set_default_device(file_t f)
 {
-  return
-    files[i]->ft->signal.channels == files[i - 1]->ft->signal.channels &&
-    files[i]->ft->signal.rate     == files[i - 1]->ft->signal.rate;
+  /* Default audio driver type in order of preference: */
+  if (!f->filetype) f->filetype = getenv("AUDIODRIVER");
+  if (!f->filetype && sox_find_format("alsa", sox_false)) f->filetype = "alsa";
+  if (!f->filetype && sox_find_format("oss" , sox_false)) f->filetype = "oss";
+  if (!f->filetype && sox_find_format("sunau",sox_false)) f->filetype = "sunau";
+  if (!f->filetype && sox_find_format("ao"  , sox_false) && file_count) /*!rec*/
+    f->filetype = "ao";
+
+  if (!f->filetype) {
+    sox_fail("Sorry, there is no default audio device configured");
+    exit(1);
+  }
+  return device_name(f->filetype);
 }
 
-static void display_error(sox_format_t * ft)
+static int add_file(struct file_info const * const opts, char const * const filename)
 {
-  static char const * const sox_strerror[] = {
-    "Invalid Audio Header",
-    "Unsupported data format",
-    "Unsupported rate for format",
-    "Can't alloc memory",
-    "Operation not permitted",
-    "Operation not supported",
-    "Invalid argument",
-    "Unsupported file format",
-  };
-  sox_fail("%s: %s (%s)", ft->filename, ft->sox_errstr,
-      ft->sox_errno < SOX_EHDR?
-      strerror(ft->sox_errno) : sox_strerror[ft->sox_errno - SOX_EHDR]);
+  file_t f = xmalloc(sizeof(*f));
+
+  if (file_count >= MAX_FILES) {
+    sox_fail("too many files; maximum is %d input files (and 1 output file)", MAX_INPUT_FILES);
+    exit(1);
+  }
+  *f = *opts;
+  if (!filename)
+    usage("missing filename"); /* No return */
+  f->filename = xstrdup(filename);
+  files[file_count++] = f;
+  return 0;
 }
 
-static sox_size_t sox_read_wide(sox_format_t * ft, sox_sample_t * buf, sox_size_t max)
+static void init_file(file_t f)
 {
-  sox_size_t len = max / combiner.channels;
-  len = sox_read(ft, buf, len * ft->signal.channels) / ft->signal.channels;
-  if (!len && ft->sox_errno)
-    display_error(ft);
-  return len;
+  memset(f, 0, sizeof(*f));
+  f->signal.reverse_bytes = SOX_OPTION_DEFAULT;
+  f->signal.reverse_nibbles = SOX_OPTION_DEFAULT;
+  f->signal.reverse_bits = SOX_OPTION_DEFAULT;
+  f->signal.compression = HUGE_VAL;
+  f->volume = HUGE_VAL;
+  f->replay_gain = HUGE_VAL;
 }
 
-static void balance_input(sox_sample_t * buf, sox_size_t ws, file_t f)
+static void parse_options_and_filenames(int argc, char **argv)
 {
-  sox_size_t s = ws * f->ft->signal.channels;
+  struct file_info opts, opts_none;
+  init_file(&opts), init_file(&opts_none);
 
-  if (f->volume != 1)
-    while (s--) {
-      double d = f->volume * *buf;
-      *buf++ = SOX_ROUND_CLIP_COUNT(d, f->volume_clips);
+  if (sox_mode == sox_rec)
+    add_file(&opts, set_default_device(&opts)), init_file(&opts);
+
+  for (; optind < argc && !sox_find_effect(argv[optind]); init_file(&opts)) {
+    if (parse_gopts_and_fopts(&opts, argc, argv)) { /* is null file? */
+      if (opts.filetype != NULL && strcmp(opts.filetype, "null") != 0)
+        sox_warn("Ignoring `-t %s'.", opts.filetype);
+      opts.filetype = "null";
+      add_file(&opts, "");
     }
+    else if (optind >= argc || sox_find_effect(argv[optind]))
+      break;
+    else if (!sox_is_playlist(argv[optind]))
+      add_file(&opts, argv[optind++]);
+    else if (sox_parse_playlist((sox_playlist_callback_t)add_file, &opts, argv[optind++]) != SOX_SUCCESS)
+      exit(1);
+  }
+  if (sox_mode == sox_play)
+    add_file(&opts, set_default_device(&opts));
+  else if (memcmp(&opts, &opts_none, sizeof(opts))) /* fopts but no file */
+    add_file(&opts, device_name(opts.filetype));
 }
 
-typedef struct input_combiner
+static void parse_effects(int argc, char **argv)
 {
-  sox_sample_t *ibuf[MAX_INPUT_FILES];
-} * input_combiner_t;
+  for (nuser_effects = 0; optind < argc; ++nuser_effects) {
+    sox_effect_t *e = &user_efftab[nuser_effects];
+    int i;
 
-assert_static(sizeof(struct input_combiner) <= SOX_MAX_EFFECT_PRIVSIZE,
-              /* else */ input_combiner_PRIVSIZE_too_big);
+    if (nuser_effects >= MAX_USER_EFF) {
+      sox_fail("too many effects specified (at most %i allowed)", MAX_USER_EFF);
+      exit(1);
+    }
 
-static int combiner_start(sox_effect_t *effp)
-{
-  input_combiner_t z = (input_combiner_t) effp->priv;
-  sox_size_t ws, i;
+    /* Name should always be correct! */
+    sox_create_effect(e, sox_find_effect(argv[optind++]));
 
-  if (combine_method <= sox_concatenate)
-    progress_to_file(files[current_input]);
-  else {
-    ws = 0;
-    for (i = 0; i < input_count; i++) {
-      z->ibuf[i] = (sox_sample_t *)xmalloc(sox_globals.bufsiz * sizeof(sox_sample_t));
-      progress_to_file(files[i]);
-      ws = max(ws, input_wide_samples);
-    }
-    input_wide_samples = ws; /* Output length is that of longest input file. */
+    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 sox(1) for an alternative", e->handler.name);
   }
-  return SOX_SUCCESS;
 }
 
-static int combiner_drain(sox_effect_t *effp, sox_sample_t * obuf, sox_size_t * osamp)
+typedef enum {
+  full, rate, channels, samples, duration, bits, encoding, annotation} soxi_t;
+
+static int soxi1(soxi_t * type, char * filename)
 {
-  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;
+  sox_size_t ws;
+  sox_format_t * ft = sox_open_read(filename, NULL, NULL);
 
-  if (combine_method <= sox_concatenate) while (sox_true) {
-    if (!user_skip)
-      olen = sox_read_wide(files[current_input]->ft, 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;
-      }
+  if (!ft)
+    return 1;
+  ws = ft->length / max(ft->signal.channels, 1);
+  switch (*type) {
+    case rate: printf("%g\n", ft->signal.rate); break;
+    case channels: printf("%u\n", ft->signal.channels); break;
+    case samples: printf("%u\n", ws); break;
+    case duration: printf("%s\n", str_time((double)ws / max(ft->signal.rate, 1))); break;
+    case bits: printf("%s\n", sox_size_bits_str[ft->signal.size]); break;
+    case encoding: printf("%s\n", sox_encodings_str[ft->signal.encoding]); break;
+    case annotation: if (ft->comments) {
+      comments_t p = ft->comments;
+      do printf("%s\n", *p); while (*++p);
     }
-    balance_input(obuf, olen, files[current_input]);
     break;
-  } else {
-    sox_sample_t * p = obuf;
-    for (i = 0; i < input_count; ++i) {
-      ilen[i] = sox_read_wide(files[i]->ft, 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)
-            if (ws < ilen[i] && s < files[i]->ft->signal.channels) {
-              /* Cast to double prevents integer overflow */
-              double sample = *p + (double)z->ibuf[i][ws * files[i]->ft->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]->ft->signal.channels; ++s)
-            *p++ = (ws < ilen[i]) * z->ibuf[i][ws * files[i]->ft->signal.channels + s];
-    }
+    case full: display_file_info(ft, NULL, sox_false); break;
   }
-  read_wide_samples += olen;
-  olen *= effp->ininfo.channels;
-  *osamp = olen;
-  return olen? SOX_SUCCESS : SOX_EOF;
+  return !!sox_close(ft);
 }
 
-static int combiner_stop(sox_effect_t *effp)
+static int soxi(int argc, char * const * argv)
 {
-  input_combiner_t z = (input_combiner_t) effp->priv;
-  sox_size_t i;
+  static char const opts[] = "rcsdbea?";
+  soxi_t type = full;
+  int opt, num_errors = 0;
 
-  if (combine_method > sox_concatenate)
-    /* Free input buffers now that they are not used */
-    for (i = 0; i < input_count; i++)
-      free(z->ibuf[i]);
-
-  return SOX_SUCCESS;
+  while ((opt = getopt(argc, argv, opts)) > 0) /* act only on last option */
+    type = 1 + (strchr(opts, opt) - opts);
+  if (type > annotation)
+    printf("Usage: soxi [-r|-c|-s|-d|-b|-e|-a] infile1 ...\n");
+  else for (; optind < argc; ++optind) {
+    if (sox_is_playlist(argv[optind]))
+      num_errors += (sox_parse_playlist((sox_playlist_callback_t)soxi1, &type, argv[optind]) != SOX_SUCCESS);
+    else num_errors += soxi1(&type, argv[optind]);
+  }
+  return num_errors;
 }
 
-static sox_effect_handler_t const * input_combiner_effect_fn(void)
+static void set_replay_gain(comments_t comments, file_t f)
 {
-  static sox_effect_handler_t handler = {
-    "input", 0, SOX_EFF_MCHAN,
-    0, combiner_start, 0, combiner_drain, combiner_stop, 0
-  };
-  return &handler;
-}
+  rg_mode rg = replay_gain_mode;
+  int try = 2; /* Will try to find the other GAIN if preferred one not found */
+  size_t i, n = num_comments(comments);
 
-static int output_flow(sox_effect_t *effp UNUSED, sox_sample_t const * ibuf,
-    sox_sample_t * obuf UNUSED, sox_size_t * isamp, sox_size_t * osamp)
-{
-  size_t len;
-
-  for (len = 0; len < *isamp; len += effp->ininfo.channels) {
-    omax[0] = max(omax[0], ibuf[len]);
-    omin[0] = min(omin[0], ibuf[len]);
-    if (effp->ininfo.channels > 1) {
-      omax[1] = max(omax[1], ibuf[len + 1]);
-      omin[1] = min(omin[1], ibuf[len + 1]);
+  if (rg != RG_off) while (try--) {
+    char const * target =
+      rg == RG_track? "REPLAYGAIN_TRACK_GAIN=" : "REPLAYGAIN_ALBUM_GAIN=";
+    for (i = 0; i < n; ++i) {
+      if (strncasecmp(comments[i], target, strlen(target)) == 0) {
+        f->replay_gain = atof(comments[i] + strlen(target));
+        return;
+      }
     }
-    else {
-      omax[1] = omax[0];
-      omin[1] = omin[0];
-    }
+    rg ^= RG_track ^ RG_album;
   }
-  for (*osamp = *isamp; *osamp; ibuf += len, *osamp -= len) {
-    len = sox_write(ofile->ft, ibuf, *osamp);
-    if (len == 0) {
-      sox_warn("Error writing: %s", ofile->ft->sox_errstr);
-      return SOX_EOF;
-    }
-    if (user_abort) /* Don't get stuck in this loop. */
-      return SOX_EOF;
-  }
-  output_samples += *isamp / ofile->ft->signal.channels;
-  return SOX_SUCCESS;
 }
 
-static sox_effect_handler_t const * output_effect_fn(void)
+static void output_message(unsigned level, const char *filename, const char *fmt, va_list ap)
 {
-  static sox_effect_handler_t handler = {
-    "output", 0, SOX_EFF_MCHAN, NULL, NULL, output_flow, NULL, NULL, NULL
-  };
-  return &handler;
+  if (sox_globals.verbosity >= level) {
+    fprintf(stderr, "%s ", myname);
+    sox_output_message(stderr, filename, fmt, ap);
+    fprintf(stderr, "\n");
+  }
 }
 
-static void add_auto_effect(sox_effects_chain_t * chain, char const * name, sox_signalinfo_t * signal)
+int main(int argc, char **argv)
 {
-  sox_effect_t eff;
+  size_t i;
 
-  /* Auto effect should always succeed here */
-  sox_create_effect(&eff, sox_find_effect(name));
-  eff.handler.getopts(&eff, 0, NULL);          /* Set up with default opts */
+  myname = argv[0];
+  atexit(cleanup);
+  sox_globals.output_message_handler = output_message;
 
-  /* But could fail here */
-  if (sox_add_effect(chain, &eff, signal, &ofile->ft->signal) != SOX_SUCCESS)
-    exit(2);
-}
-
-/* If needed effects are not given, auto-add at (performance) optimal point. */
-static void add_effects(sox_effects_chain_t * chain)
-{
-  sox_signalinfo_t signal = combiner;
-  unsigned i, min_chan = 0, min_rate = 0;
-  sox_effect_t eff;
-
-  /* Find points after which we might add effects to change rate/chans */
-  for (i = 0; i < nuser_effects; i++) {
-    if (user_efftab[i].handler.flags & (SOX_EFF_CHAN|SOX_EFF_MCHAN))
-      min_chan = i + 1;
-    if (user_efftab[i].handler.flags & SOX_EFF_RATE)
-      min_rate = i + 1;
+  if (strends(myname, "play")) {
+    sox_mode = sox_play;
+    replay_gain_mode = RG_track;
+    combine_method = sox_sequence;
   }
-  /* 1st `effect' in the chain is the input combiner */
-  sox_create_effect(&eff, input_combiner_effect_fn());
-  sox_add_effect(chain, &eff, &signal, &ofile->ft->signal);
+  else if (strends(myname, "rec"))
+    sox_mode = sox_rec;
+  else if (strends(myname, "soxi"))
+    sox_mode = sox_soxi;
 
-  /* 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", &signal);
+  if (sox_format_init() != SOX_SUCCESS)
+    exit(1);
+ 
+  if (sox_mode == sox_soxi)
+    exit(soxi(argc, argv));
 
-    /* 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, "resample", &signal);
+  parse_options_and_filenames(argc, argv);
 
-    if (i < nuser_effects)
-      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)
-    add_auto_effect(chain, "resample", &signal);  /* Must be up-sampling */
-  if (signal.channels != ofile->ft->signal.channels)
-    add_auto_effect(chain, "mixer", &signal);     /* Must be increasing channels */
+  if (sox_globals.verbosity > 2)
+    display_SoX_version(stderr);
+ 
+  /* 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))
+    usage("Not enough input filenames specified");
 
-  /* Last `effect' in the chain is the output file */
-  sox_create_effect(&eff, output_effect_fn());
-  if (sox_add_effect(chain, &eff, &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",
-        effp->handler.name, effp->ininfo.rate, effp->ininfo.channels, effp->ininfo.size * 8,
-        (effp->handler.flags & SOX_EFF_MCHAN)? "(multi)" : "");
+  /* Check for misplaced input/output-specific options */
+  for (i = 0; i < input_count; ++i) {
+    if (files[i]->signal.compression != HUGE_VAL)
+      usage("A compression factor can be given only for an output file");
+    if (files[i]->comments != NULL)
+      usage("Comments can be given only for an output file");
   }
-}
+  if (ofile->volume != HUGE_VAL)
+    usage("-v can be given only for an input file;\n"
+            "\tuse `vol' to set the output file volume");
 
-static void optimize_trim(void)          
-{
-  /* Speed hack.  If the "trim" effect is the first effect then
-   * peek inside its "effect descriptor" and see what the
-   * start location is.  This has to be done after its start()
-   * is called to have the correct location.
-   * Also, only do this when only working with one input file.
-   * This is because the logic 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 (files[0]->ft->handler->seek && files[0]->ft->seekable){
-      sox_size_t offset = sox_trim_get_start(&ofile_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]);
-      }    
-    }        
-  }    
-}
+  signal(SIGINT, SIG_IGN); /* So child pipes aren't killed by track skip */
+  for (i = 0; i < input_count; i++) {
+    int j = input_count - 1 - i; /* Open in reverse order 'cos of rec (below) */
+    file_t f = files[j];
 
-static void open_output_file(sox_size_t olen)
-{
-  sox_loopinfo_t loops[SOX_MAX_NLOOPS];
-  double factor;
-  int i;
-  comments_t comments = copy_comments(files[0]->ft->comments);
-  comments_t p = ofile->comments;
+    /* When mixing audio, default to input side volume adjustments that will
+     * make sure no clipping will occur.  Users probably won't be happy with
+     * this, and will override it, possibly causing clipping to occur. */
+    if (combine_method == sox_mix && !uservolume)
+      f->volume = 1.0 / input_count;
 
-  if (!comments && !p)
-    append_comment(&comments, "Processed by SoX");
-  else if (p) {
-    if (!(*p)[0]) {
-      delete_comments(&comments);
-      ++p;
+    if (sox_mode == sox_rec && !j) {       /* Set the recording parameters: */
+      if (input_count > 1)                 /* from the (just openned) next */
+        f->signal = files[1]->ft->signal;  /* input file, or from the output */
+      else f->signal = files[1]->signal;   /* file (which is not open yet). */
     }
-    while (*p)
-      append_comment(&comments, *p++);
+    files[j]->ft = sox_open_read(f->filename, &f->signal, f->filetype);
+    if (!files[j]->ft)
+      /* sox_open_read() will call sox_warn for most errors.
+       * Rely on that printing something. */
+      exit(2);
+    if (show_progress == SOX_OPTION_DEFAULT &&
+        (files[j]->ft->handler->flags & SOX_FILE_DEVICE) != 0 &&
+        (files[j]->ft->handler->flags & SOX_FILE_PHONY) == 0)
+      show_progress = SOX_OPTION_YES;
+    set_replay_gain(files[j]->ft->comments, f);
   }
+  signal(SIGINT, SIG_DFL);
 
-  /*
-   * 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]->ft->loops[i].start * factor;
-    loops[i].length = files[0]->ft->loops[i].length * factor;
-    loops[i].count = files[0]->ft->loops[i].count;
-    loops[i].type = files[0]->ft->loops[i].type;
-  }
+  /* Loop through the rest of the arguments looking for effects */
+  parse_effects(argc, argv);
 
-  ofile->ft = sox_open_write(overwrite_permitted,
-                        ofile->filename,
-                        &ofile->signal,
-                        ofile->filetype,
-                        comments,
-                        olen,
-                        &files[0]->ft->instr,
-                        loops);
-  delete_comments(&comments);
-
-  if (!ofile->ft)
-    /* 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->ft->handler->flags & SOX_FILE_DEVICE) != 0 &&
-                    (ofile->ft->handler->flags & SOX_FILE_PHONY) == 0;
-
-  report_file_info(ofile);
-}
-
-static int update_status(sox_bool all_done)
-{
-  display_status(all_done || user_abort);
-  return user_abort? SOX_EOF : SOX_SUCCESS;
-}
-
-/*
- * Process:   Input(s) -> Balancing -> Combiner -> Effects -> Output
- */
-
-static int process(void) {
-  int flowstatus = 0;
-  sox_size_t i;
-  sox_bool known_length = combine_method != sox_sequence;
-  sox_size_t olen = 0;
-
-  combiner = files[current_input]->ft->signal;
-  if (combine_method == sox_sequence) {
-    if (!current_input) for (i = 0; i < input_count; i++)
+  /* Not the best way for users to do this; now deprecated in favour of soxi. */
+  if (!show_progress && !nuser_effects && ofile->filetype && !strcmp(ofile->filetype, "null")) {
+    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;
-
-    for (i = 0; i < input_count; i++) { /* Report all inputs, then check */
-      report_file_info(files[i]);
-      total_channels += files[i]->ft->signal.channels;
-      min_channels = min(min_channels, files[i]->ft->signal.channels);
-      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->length != 0;
-      if (combine_method == sox_concatenate)
-        olen += files[i]->ft->length / files[i]->ft->signal.channels;
-      else
-        olen = max(olen, files[i]->ft->length / files[i]->ft->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);
-
-    combiner.channels = 
-      combine_method == sox_merge? total_channels : max_channels;
+    exit(0);
   }
 
-  ofile->signal = ofile_signal;
-  if (ofile->signal.rate == 0)
-    ofile->signal.rate = combiner.rate;
-  if (ofile->signal.size == 0)
-    ofile->signal.size = combiner.size;
-  if (ofile->signal.encoding == SOX_ENCODING_UNKNOWN)
-    ofile->signal.encoding = combiner.encoding;
-  if (ofile->signal.channels == 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 && !ofile->signal.channels; ++j)
-      ofile->signal.channels = user_efftab[nuser_effects - 1 - j].outinfo.channels;
-    if (ofile->signal.channels == 0)
-      ofile->signal.channels = combiner.channels;
+    for (j =0; j < nuser_effects && !files[i]->ft->signal.channels; ++j)
+      files[i]->ft->signal.channels = user_efftab[j].ininfo.channels;
+    if (!files[i]->ft->signal.channels)
+      ++files[i]->ft->signal.channels;
   }
 
-  combiner.rate *= sox_effects_globals.speed;
+  if (repeatable_random)
+    sox_debug("Not reseeding PRNG; randomness is repeatable");
+  else {
+    time_t t;
 
-  for (i = 0; i < nuser_effects; i++)
-    known_length = known_length && !(user_efftab[i].handler.flags & SOX_EFF_LENGTH);
+    time(&t);
+    srand((unsigned)t);
+  }
 
-  if (!known_length)
-    olen = 0;
+  ofile_signal = ofile->signal;
+  if (combine_method == sox_sequence) do {
+    if (ofile->ft)
+      sox_close(ofile->ft);
+  } while (process() != SOX_EOF && !user_abort && current_input < input_count);
+  else process();
 
-  open_output_file((sox_size_t)(olen * ofile->signal.channels * ofile->signal.rate / combiner.rate + .5));
-
-  ofile_effects_chain.global_info = sox_effects_globals;
-  add_effects(&ofile_effects_chain);
-
-  optimize_trim();
-
-  signal(SIGINT, sigint);
-  /* FIXME: For SIGTERM at least we really should guarantee to stop quickly */
-  signal(SIGTERM, sigint); /* Stop gracefully even in extremis */
-  
-  flowstatus = sox_flow_effects(&ofile_effects_chain, update_status);
-
-  sox_delete_effects(&ofile_effects_chain);
-  return flowstatus;
-}
-
-static sox_size_t total_clips(void)
-{
-  unsigned i;
-  sox_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);
-}
+    if (files[i]->ft->clips != 0)
+      sox_warn(i < input_count?"%s: input clipped %u samples" :
+                              "%s: output clipped %u samples; decrease volume?",
+          (files[i]->ft->handler->flags & SOX_FILE_DEVICE)?
+                       files[i]->ft->handler->names[0] : files[i]->ft->filename,
+          files[i]->ft->clips);
 
-static char const * sigfigs3(sox_size_t number)
-{
-  static char string[16][10];
-  static unsigned n;
-  unsigned a, b, c = 2;
-  sprintf(string[n = (n+1) & 15], "%#.3g", (double)number);
-  if (sscanf(string[n], "%u.%ue%u", &a, &b, &c) == 3)
-    a = 100*a + b;
-  switch (c%3) {
-    case 0: sprintf(string[n], "%u.%02u%c", a/100,a%100, " kMGTPE"[c/3]); break;
-    case 1: sprintf(string[n], "%u.%u%c"  , a/10 ,a%10 , " kMGTPE"[c/3]); break;
-    case 2: sprintf(string[n], "%u%c"     , a          , " kMGTPE"[c/3]); break;
-  }
-  return string[n];
-}
-
-static char const * sigfigs3p(double percentage)
-{
-  static char string[16][10];
-  static unsigned n;
-  sprintf(string[n = (n+1) & 15], "%.1f%%", percentage);
-  if (strlen(string[n]) < 5)
-    sprintf(string[n], "%.2f%%", percentage);
-  else if (strlen(string[n]) > 5)
-    sprintf(string[n], "%.0f%%", percentage);
-  return string[n];
-}
+  if (mixing_clips > 0)
+    sox_warn("mix-combining clipped %u samples; decrease volume?", mixing_clips);
 
-static char const * vu(unsigned channel)
-{
-  static char const * const text[][2] = {
-    {"", ""}, {"-", "-"}, {"=", "="}, {"-=", "=-"},
-    {"==", "=="}, {"-==", "==-"}, {"===", "==="}, {"-===", "===-"},
-    {"====", "===="}, {"-====", "====-"}, {"=====", "====="},
-    {"-=====", "=====-"}, {"======", "======"},
-    {"!=====", "=====!"}, {"!!====", "====!!"}, /* 2 `red' levels */
-  };
-  int const red = 2, white = array_length(text) - red;
-  double const MAX = SOX_SAMPLE_MAX, MIN = SOX_SAMPLE_MIN;
-  double linear = max(omax[channel] / MAX, omin[channel] / MIN);
-  int vu_dB = linear? floor(2 * white + red - .5 + linear_to_dB(linear)) : 0;
-  int index = vu_dB < 2 * white? max(vu_dB / 2, 0) : vu_dB - white;
-  omax[channel] = omin[channel] = 0;
-  return text[index][channel];
-}
+  for (i = 0; i < file_count; i++)
+    if (files[i]->volume_clips > 0)
+      sox_warn("%s: balancing clipped %u samples; decrease volume?",
+          files[i]->filename, files[i]->volume_clips);
 
-static void display_status(sox_bool all_done)
-{
-  static struct timeval then;
-  if (!show_progress)
-    return;
-  if (all_done || since(&then, .15, sox_false)) {
-    double read_time = (double)read_wide_samples / combiner.rate;
-    double left_time = 0, in_time = 0, percentage = 0;
-
-    if (input_wide_samples) {
-      in_time = (double)input_wide_samples / combiner.rate;
-      left_time = max(in_time - read_time, 0);
-      percentage = max(100. * read_wide_samples / input_wide_samples, 0);
-    }
-    fprintf(stderr, "\r%s [%s] of %s (%-5s) Samps out:%-5s%6s|%-6sClips:%-5s",
-      str_time(read_time), str_time(left_time), str_time(in_time),
-      sigfigs3p(percentage), sigfigs3(output_samples),
-      vu(0), vu(1), sigfigs3(total_clips()));
+  if (show_progress) {
+    if (user_abort)
+      fprintf(stderr, "Aborted.\n");
+    else if (user_skip && sox_mode != sox_rec)
+      fprintf(stderr, "Skipped.\n");
+    else
+      fprintf(stderr, "Done.\n");
   }
-  if (all_done)
-    fputc('\n', stderr);
+
+  success = 1; /* Signal success to cleanup so the output file isn't removed. */
+  return 0;
 }