shithub: sox

Download patch

ref: 14df40d05820c94be635afed15b6de34dcd9a666
parent: b4e5bf240085d7fa7078556a1842b47a1cd1bdf1
author: cbagwell <cbagwell>
date: Wed Sep 14 18:44:37 EDT 2005

Added status option (-S) to display progress.

--- a/Changelog
+++ b/Changelog
@@ -24,6 +24,12 @@
   o Added support for -V option to play/rec scripts.
   o Fix to silence effect to allow negative periods to be specified
     (to remove silence from middle of sound file).
+  o Fix swap option handling so that special case of "swap 1 1" will
+    work.
+  o Track length of Ogg Vorbis files on read.
+  o Add support for displaying a status line that tracks progress
+    of read/write routines.  Part of information requires read
+    file handlers to be able to determine file length.
 
 sox-12.17.8
 -----------
--- a/sox.1
+++ b/sox.1
@@ -37,7 +37,7 @@
 .P
 .B General options:
 .br
-    [ -h ] [ -p ] [ -V ]
+    [ -h ] [ -p ] [ -q ] [ -S ] [ -V ]
 .P
 .B Format options:
 .br
@@ -225,29 +225,56 @@
 filenames of "-".  If specified as an input name, it will read data
 from stdin.  If specified as an output name, it will send data
 to stdout.
+.PP
+\fBGeneral options:\fR
+.TP 10
+\fB-h\fR
+Print version number and usage information.
+.TP 10
+\fB-p\fR
+Run in preview mode and run fast.  This will somewhat speed up
+SoX when the output format has a different number of channels and
+a different rate than the input file.  Currently, this defaults to
+using the \fBrate\fR effect instead of the \fBresample\fR effect for sample
+rate changes.
+.TP 10
+\fB-q\fR
+Run in quite mode when SoX wouldn't otherwise do that.  Inverse of \fB-S\fR
+option.
+.TP
+\fB-S\fR
+Print status while processing audio data.  Tells how much of audio data has been
+processed in terms of audio running time instead of samples.
+.TP 10
+\fB-V\fR
+Print a description of processing phases.
+Useful for figuring out exactly how
+.I SoX
+.PP
+is mangling your sound samples.
+.PP
 \fBFormat options:\fR
 .PP
-Format options effect the audio samples that they immediately precede.  If
-they are placed before the input file name then they effect the input
-data.  If they are placed before the output file name then they will
-effect the output data.  By taking advantage of this, you can override
-a input file's corrupted header or produce an output file that is totally
-different style then the input file.  It is also how SoX is informed about
-the format of raw input data.
+Format options effect the input or output file that they immediately precede.
+.PP
+Self describing input files can obtain all the format information directly from the header and so don't generally need format options.  Headerless input files lack this information and so format options must be used to inform SoX of the file's data type, sample rate, and number of channels.
+.PP
+By default, SoX attempts to write audio data using the same data type, sample rate, and channel count as the input data.  If the user wants the output file to be of a different format then format options can be used to specify the differences.
+.PP
+If an output file format doesn't support the same data type, sample rate, or channel count as the input file format, then SoX will auto select the closest values it does support so that the user does not have to specify these format change options manually.
 .TP 10
 \fB-t \fIfiletype\fR
 gives the type of the sound sample file.  Useful when file extension is
-not standard or for specifying the .auto file type.
+not standard or can not be determeind by looking at the header of the file.
 .TP 10
 \fB-r \fIrate\fR
 Gives the sample rate in Hertz of the file.  To cause the output file to have
 a different sample rate than the input file, include this option as a part
-of the output options.
+of the output format options.
 .br
 If the input and output files have
-different rates then a sample rate change effect must be ran.  If a
-sample rate changing effect is not specified then a default one will internally
-be ran by SoX using its default parameters.
+different rates then a sample rate change effect must be ran.  Since SoX has
+multiple rate changing effects, the user can specify which to use as an effect.  If no rate change effect is specified then a default one will be chosen.
 .TP 10
 \fB-v \fIvolume\fR
 Change amplitude (floating point); 
@@ -330,29 +357,12 @@
 command line it will be invoked internally with default parameters.
 .TP 10
 \fB-e\fR
-When used after the input filename (so that it applies to the output file)
+When specified after the last input filename (so that it applies
+to the output file)
 it allows you to avoid giving an output filename and will not
 produce an output file.  It will apply any specified effects
 to the input file.  This is mainly useful with the \fBstat\fR effect
-but can be used with others.
-.PP
-\fBGeneral options:\fR
-.TP 10
-\fB-h\fR
-Print version number and usage information.
-.TP 10
-\fB-p\fR
-Run in preview mode and run fast.  This will somewhat speed up
-SoX when the output format has a different number of channels and
-a different rate than the input file.  Currently, this defaults to
-using the \fBrate\fR effect instead of the \fBresample\fR effect for sample
-rate changes.
-.TP 10
-\fB-V\fR
-Print a description of processing phases.
-Useful for figuring out exactly how
-.I SoX
-is mangling your sound samples.
+but can be used.
 .SH FILE TYPES
 .I SoX
 attempts to determine the file type of input files automatically by looking 
@@ -618,7 +628,7 @@
 .B .ogg
 format.
 .TP 10
-.B vox
+.B .vox
 A headerless file of Dialogic/OKI ADPCM audio data commonly comes with the
 extension .vox.  This ADPCM data has 12-bit precision packed into only 4-bits.
 .TP 10
@@ -677,9 +687,7 @@
 a sample rate of 11025 or 22050 hz.
 .TP 10
 .B .auto
-This is a ``meta-type'': specifying this type for an input file
-triggers some code that tries to guess the real type by looking for
-magic words in the header.  If the type can't be guessed, the program
+This is a ``meta-type'' and is the default file type if the user does not specify one. This file type attempts to guess the real type by looking for magic words in the header. If the type can't be guessed, the program
 exits with an error message.  The input must be a plain file, not a
 pipe.  This type can't be used for output files.
 .SH EFFECTS
--- a/src/aiff.c
+++ b/src/aiff.c
@@ -91,7 +91,8 @@
     ft->st_errno = st_seek(ft, new_offset, SEEK_SET);
 
     if (ft->st_errno == ST_SUCCESS)
-        aiff->nsamples = ft->length - (new_offset / ft->info.size);
+        aiff->nsamples = ft->length - 
+                          (new_offset / ft->info.size / ft->info.channels);
 
     return(ft->st_errno);
 }
@@ -415,12 +416,12 @@
 
         }
 
-        aiff->nsamples = ssndsize / ft->info.size;      /* leave out channels */
+        aiff->nsamples = ssndsize / ft->info.size / ft->info.channels;
 
         /* Cope with 'sowt' CD tracks as read on Macs */
         if (is_sowt)
         {
-                aiff->nsamples -= 4;
+                aiff->nsamples -= (4 / ft->info.channels);
                 ft->swap = ft->swap ? 0 : 1;
         }
         
@@ -611,12 +612,12 @@
 
         if (len < 0)
             return ST_EOF;
-        else if ((st_size_t)len > aiff->nsamples)
-                len = aiff->nsamples;
+        else if ((st_size_t)len > aiff->nsamples*ft->info.channels)
+                len = (aiff->nsamples*ft->info.channels);
         done = st_rawread(ft, buf, len);
         if (done == 0 && aiff->nsamples != 0)
                 st_warn("Premature EOF on AIFF input file");
-        aiff->nsamples -= done;
+        aiff->nsamples -= (done / ft->info.channels);
         return done;
 }
 
@@ -704,7 +705,7 @@
 st_ssize_t st_aiffwrite(ft_t ft, st_sample_t *buf, st_ssize_t len)
 {
         aiff_t aiff = (aiff_t ) ft->priv;
-        aiff->nsamples += len;
+        aiff->nsamples += (len / ft->info.channels);
         st_rawwrite(ft, buf, len);
         return(len);
 }
@@ -731,7 +732,7 @@
                 st_fail_errno(ft,errno,"can't rewind output file to rewrite AIFF header");
                 return(ST_EOF);
         }
-        return(aiffwriteheader(ft, aiff->nsamples / ft->info.channels));
+        return(aiffwriteheader(ft, aiff->nsamples));
 }
 
 static int aiffwriteheader(ft_t ft, st_size_t nframes)
--- a/src/cdr.c
+++ b/src/cdr.c
@@ -24,51 +24,51 @@
 
 #include "st_i.h"
 
-#define SECTORSIZE	(2352 / 2)
+#define SECTORSIZE      (2352 / 2)
 
 /* Private data for SKEL file */
 typedef struct cdrstuff {
-	st_size_t samples;	/* number of samples written */
+        st_size_t samples;      /* number of samples written */
 } *cdr_t;
 
 /*
  * Do anything required before you start reading samples.
  * Read file header. 
- *	Find out sampling rate, 
- *	size and encoding of samples, 
- *	mono/stereo/quad.
+ *      Find out sampling rate, 
+ *      size and encoding of samples, 
+ *      mono/stereo/quad.
  */
 
 int st_cdrstartread(ft_t ft) 
 {
-	int rc;
+        int rc;
 
-	/* Needed because of rawread() */
-	rc = st_rawstartread(ft);
-	if (rc)
-	    return rc;
+        /* Needed because of rawread() */
+        rc = st_rawstartread(ft);
+        if (rc)
+            return rc;
 
-	/* CDR is in Big Endian format.  Swap whats read in on */
+        /* CDR is in Big Endian format.  Swap whats read in on */
         /* Little Endian machines.                             */
-	if (ST_IS_LITTLEENDIAN)
-	{ 
-	    ft->swap = ft->swap ? 0 : 1;
-	}
+        if (ST_IS_LITTLEENDIAN)
+        { 
+            ft->swap = ft->swap ? 0 : 1;
+        }
 
-	ft->info.rate = 44100L;
-	ft->info.size = ST_SIZE_WORD;
-	ft->info.encoding = ST_ENCODING_SIGN2;
-	ft->info.channels = 2;
-	ft->comment = NULL;
+        ft->info.rate = 44100L;
+        ft->info.size = ST_SIZE_WORD;
+        ft->info.encoding = ST_ENCODING_SIGN2;
+        ft->info.channels = 2;
+        ft->comment = NULL;
 
 /* Need length for seeking */
-	if(ft->seekable){
-		ft->length = st_filelength(ft)/2;
-	} else {
-		ft->length = 0;
-	}
-	
-	return(ST_SUCCESS);
+        if(ft->seekable){
+                ft->length = st_filelength(ft)/2/2;
+        } else {
+                ft->length = 0;
+        }
+        
+        return(ST_SUCCESS);
 }
 
 /*
@@ -81,7 +81,7 @@
 st_ssize_t st_cdrread(ft_t ft, st_sample_t *buf, st_ssize_t len) 
 {
 
-	return st_rawread(ft, buf, len);
+        return st_rawread(ft, buf, len);
 }
 
 /*
@@ -90,44 +90,44 @@
  */
 int st_cdrstopread(ft_t ft) 
 {
-	/* Needed because of rawread() */
-	return st_rawstopread(ft);
+        /* Needed because of rawread() */
+        return st_rawstopread(ft);
 }
 
 int st_cdrstartwrite(ft_t ft) 
 {
-	cdr_t cdr = (cdr_t) ft->priv;
-	int rc;
+        cdr_t cdr = (cdr_t) ft->priv;
+        int rc;
 
-	/* CDR is in Big Endian format.  Swap whats written out on */
-	/* Little Endian Machines.                                 */
-	if (ST_IS_LITTLEENDIAN)
-	{
-	    ft->swap = ft->swap ? 0 : 1;
-	}
+        /* CDR is in Big Endian format.  Swap whats written out on */
+        /* Little Endian Machines.                                 */
+        if (ST_IS_LITTLEENDIAN)
+        {
+            ft->swap = ft->swap ? 0 : 1;
+        }
 
-	/* Needed because of rawwrite() */
-	rc = st_rawstartwrite(ft);
-	if (rc)
-	    return rc;
+        /* Needed because of rawwrite() */
+        rc = st_rawstartwrite(ft);
+        if (rc)
+            return rc;
 
-	cdr->samples = 0;
+        cdr->samples = 0;
 
-	ft->info.rate = 44100L;
-	ft->info.size = ST_SIZE_WORD;
-	ft->info.encoding = ST_ENCODING_SIGN2;
-	ft->info.channels = 2;
+        ft->info.rate = 44100L;
+        ft->info.size = ST_SIZE_WORD;
+        ft->info.encoding = ST_ENCODING_SIGN2;
+        ft->info.channels = 2;
 
-	return(ST_SUCCESS);
+        return(ST_SUCCESS);
 }
 
 st_ssize_t st_cdrwrite(ft_t ft, st_sample_t *buf, st_ssize_t len) 
 {
-	cdr_t cdr = (cdr_t) ft->priv;
+        cdr_t cdr = (cdr_t) ft->priv;
 
-	cdr->samples += len;
+        cdr->samples += len;
 
-	return st_rawwrite(ft, buf, len);
+        return st_rawwrite(ft, buf, len);
 }
 
 /*
@@ -137,25 +137,25 @@
 
 int st_cdrstopwrite(ft_t ft) 
 {
-	cdr_t cdr = (cdr_t) ft->priv;
-	int padsamps = SECTORSIZE - (cdr->samples % SECTORSIZE);
-	short zero;
-	int rc;
+        cdr_t cdr = (cdr_t) ft->priv;
+        int padsamps = SECTORSIZE - (cdr->samples % SECTORSIZE);
+        short zero;
+        int rc;
 
-	/* Flush buffer before writing anything else */
-	rc = st_rawstopwrite(ft);
+        /* Flush buffer before writing anything else */
+        rc = st_rawstopwrite(ft);
 
-	if (rc)
-	    return rc;
+        if (rc)
+            return rc;
 
-	zero = 0;
+        zero = 0;
 
-	if (padsamps != SECTORSIZE) 
-	{
-		while (padsamps > 0) {
-			st_writew(ft, zero);
-			padsamps--;
-		}
-	}
-	return(ST_SUCCESS);
+        if (padsamps != SECTORSIZE) 
+        {
+                while (padsamps > 0) {
+                        st_writew(ft, zero);
+                        padsamps--;
+                }
+        }
+        return(ST_SUCCESS);
 }
--- a/src/misc.c
+++ b/src/misc.c
@@ -43,6 +43,18 @@
         "long longs"
 };
 
+const char *st_size_bits_str[] = {
+        "NONSENSE!",
+        "8-bits",
+        "16-bits",
+        "24-bits",
+        "32-bits",
+        "NONSENSE",
+        "NONSENSE",
+        "NONSENSE",
+        "64-bits"
+};
+
 const char *st_encodings_str[] = {
         "NONSENSE!",
         "unsigned",
@@ -55,7 +67,8 @@
         "gsm",
         "inversed u-law",
         "inversed A-law",
-        "MPEG audio (layer I, II or III)"
+        "MPEG audio (layer I, II or III)",
+        "Vorbis"
 };
 
 static const char readerr[] = "Premature EOF while reading sample file.";
--- a/src/mp3.c
+++ b/src/mp3.c
@@ -203,6 +203,13 @@
             mad_stream_skip(p->Stream, tagsize);
         }
 
+        /* TODO: It would be nice to look for Xing VBR headers
+         * or TLE fields in ID3 to detect length of file
+         * and set ft->length.
+         * For CBR, we should fstat the file and divided
+         * by bitrate to find length.
+         */
+
         switch(p->Frame->header.mode)
         {
                 case MAD_MODE_SINGLE_CHANNEL:
--- a/src/prc.c
+++ b/src/prc.c
@@ -113,7 +113,7 @@
         ft->info.channels = 1;
 
         p->dataStart = st_tell(ft);
-        ft->length = p->length/ft->info.size;
+        ft->length = p->length/ft->info.size/ft->info.channels;
 
         return (ST_SUCCESS);
 }
--- a/src/sf.c
+++ b/src/sf.c
@@ -154,7 +154,7 @@
 
 /* Need length for seeking */
         if(ft->seekable){
-                ft->length = st_filelength(ft)/samplesize;
+                ft->length = st_filelength(ft)/samplesize/ft->info.channels;
                 sf->dataStart = st_tell(ft);
         } else {
                 ft->length = 0;
--- a/src/smp.c
+++ b/src/smp.c
@@ -191,7 +191,8 @@
     ft->st_errno = st_seek(ft, new_offset, SEEK_SET);
 
     if( ft->st_errno == ST_SUCCESS )
-        smp->NoOfSamps = ft->length - (new_offset / ft->info.size);
+        smp->NoOfSamps = (ft->length*ft->info.channels) - 
+                         (new_offset / ft->info.size);
 
     return(ft->st_errno);
 }
--- a/src/sox.c
+++ b/src/sox.c
@@ -72,6 +72,12 @@
 static int writing = 1;         /* are we writing to a file? assume yes. */
 static int soxpreview = 0;      /* preview mode */
 
+static int quite = 0;
+static int status = 0;
+static unsigned long input_samples = 0;
+static unsigned long read_samples = 0;
+static unsigned long output_samples = 0;
+
 static st_sample_t ibufl[ST_BUFSIZ/2];    /* Left/right interleave buffers */
 static st_sample_t ibufr[ST_BUFSIZ/2];
 static st_sample_t obufl[ST_BUFSIZ/2];
@@ -97,13 +103,20 @@
 static void usage(char *) NORET;
 static int filetype(int);
 static void process(void);
+static void print_input_status(int input);
+static void update_status(void);
 static void statistics(void);
 static st_sample_t volumechange(st_sample_t *buf, st_ssize_t ct, double vol);
-static void checkeffect(void);
+void parse_effects(int argc, char **argv);
+void check_effects(void);
+void start_effects(void);
+void reserve_effect_buf(void);
 static int flow_effect_out(void);
 static int flow_effect(int);
-static int drain_effect_out(void);
-static int drain_effect(int);
+int drain_effect_out(void);
+int drain_effect(int);
+void release_effect_buf(void);
+void stop_effects(void);
 
 #define MAX_INPUT_FILES 32
 #define MAX_FILES MAX_INPUT_FILES + 1
@@ -151,7 +164,6 @@
 
 int main(int argc, char **argv)
 {
-    int argc_effect;
     file_options_t *fo;
     int i;
 
@@ -216,7 +228,7 @@
     else
         input_count = file_count;
 
-    /* Make sure we got at least the required # of input filename */
+    /* Make sure we got at least the required # of input filenames */
     if (input_count < REQUIRED_INPUT_FILES)
         usage("Not enough input or output filenames specified");
 
@@ -248,41 +260,8 @@
     }
 
     /* Loop through the reset of the arguments looking for effects */
-    nuser_effects = 0;
+    parse_effects(argc, argv);
 
-    while (optind < argc)
-    {
-        if (nuser_effects >= MAX_USER_EFF)
-        {
-            st_fail("To many effects specified.\n");
-        }
-
-        argc_effect = st_geteffect_opt(&user_efftab[nuser_effects],
-                                       argc - optind, &argv[optind]);
-
-        if (argc_effect == ST_EOF)
-        {
-            int i1;
-            fprintf(stderr, "%s: Known effects: ",myname);
-            for (i1 = 0; st_effects[i1].name; i1++)
-                fprintf(stderr, "%s ", st_effects[i1].name);
-            fprintf(stderr, "\n\n");
-            st_fail("Effect '%s' is not known!", argv[optind]);
-        }
-
-
-        /* Skip past effect name */
-        optind++;
-
-        (*user_efftab[nuser_effects].h->getopts)(&user_efftab[nuser_effects],
-                                                 argc_effect,
-                                                 &argv[optind]);
-
-        /* Skip past the effect arguments */
-        optind += argc_effect;
-        nuser_effects++;
-    }
-
     process();
     statistics();
 
@@ -289,6 +268,9 @@
     for (i = 0; i < file_count; i++)
         free(file_desc[i]);
 
+    if (status)
+        fprintf(stderr, "\n\nDone.\n");
+
     return(0);
 }
 
@@ -372,6 +354,18 @@
                     file_desc[offset]->filename, 
                     file_desc[offset]->st_errstr);
 
+        /* When writing to an audio device, auto turn on the
+         * status display to match behavior of ogg123/mpg123
+         * utils.  That is unless user requested us not to display]
+         * anything.
+         */
+        if (strcmp(file_desc[offset]->filetype, "alsa") == 0 ||
+            strcmp(file_desc[offset]->filetype, "ossdsp") == 0 ||
+            strcmp(file_desc[offset]->filetype, "sunau") == 0)
+        {
+            if (!quite)
+                status = 1;
+        }
     }
 }
 
@@ -419,9 +413,9 @@
 }
 
 #ifdef HAVE_GETOPT_H
-static char *getoptstr = "+r:v:t:c:phsuUAaigbwlfdxV";
+static char *getoptstr = "+r:v:t:c:phsuUAaigbwlfdxVSq";
 #else
-static char *getoptstr = "r:v:t:c:phsuUAaigbwlfdxV";
+static char *getoptstr = "r:v:t:c:phsuUAaigbwlfdxVSq";
 #endif
 
 static void doopts(file_options_t *fo, int argc, char **argv)
@@ -522,6 +516,16 @@
             case 'V':
                 verbose = 1;
                 break;
+
+            case 'S':
+                status = 1;
+                quite = 0;
+                break;
+
+            case 'q':
+                status = 0;
+                quite = 1;
+                break;
         }
     }
 }
@@ -574,7 +578,7 @@
  */
 
 static void process(void) {
-    int e, f, flowstatus;
+    int e, f, flowstatus = ST_SUCCESS;
 #ifndef SOXMIX
     int current_input;
     st_ssize_t ilen;
@@ -654,47 +658,39 @@
     }
 
     /* build efftab */
-    checkeffect();
+    check_effects();
 
     /* Start all effects */
-    for(e = 1; e < neffects; e++) {
-        (* efftab[e].h->start)(&efftab[e]);
-        if (efftabR[e].name)
-            (* efftabR[e].h->start)(&efftabR[e]);
-    }
+    start_effects();
 
     /* Reserve an output buffer for all effects */
-    for(e = 0; e < neffects; e++)
-    {
-        efftab[e].obuf = (st_sample_t *) malloc(ST_BUFSIZ * 
-                                                sizeof(st_sample_t));
-        if (efftab[e].obuf == NULL)
-        {
-            st_fail("could not allocate memory");
-        }
-        if (efftabR[e].name)
-        {
-            efftabR[e].obuf = (st_sample_t *) malloc(ST_BUFSIZ * 
-                                                     sizeof(st_sample_t));
-            if (efftabR[e].obuf == NULL)
-            {
-                st_fail("could not allocate memory");
-            }
-        }
-    }
+    reserve_effect_buf();
 
     /* Try to save some time if first effect is "trim" by seeking */
     optimize_trim();
 
 #ifdef SOXMIX
-    for (f = 0; f < MAX_INPUT_FILES; f++)
+    for (f = 0; f < input_count; f++)
     {
+        /* Treat overall length the same as longest input file. */
+        if (file_desc[f]->length > input_samples)
+            input_samples = file_desc[f]->length;
+
         ibuf[f] = (st_sample_t *)malloc(ST_BUFSIZ * sizeof(st_sample_t));
         if (!ibuf[f])
         {
             st_fail("could not allocate memory");
         }
+
+        if (status)
+            print_input_status(f);
     }
+#else
+    current_input = 0;
+    input_samples = file_desc[current_input]->length;
+
+    if (status)
+        print_input_status(current_input);
 #endif
 
     /*
@@ -704,10 +700,6 @@
     for (f = 0; f < file_count; f++)
         file_desc[f]->st_errno = 0;
 
-#ifndef SOXMIX
-    current_input = 0;
-#endif
-
     input_eff = 0;
 
     /* Run input data through effects and get more until olen == 0 
@@ -738,6 +730,12 @@
             if (current_input < input_count-1)
             {
                 current_input++;
+                input_samples = file_desc[current_input]->length;
+                read_samples = 0;
+
+                if (status)
+                    print_input_status(current_input);
+
                 continue;
             }
         }
@@ -832,6 +830,9 @@
 
         flowstatus = flow_effect_out();
 
+        if (status)
+            update_status();
+
         /* Negative flowstatus says no more output will ever be generated. */
         if (flowstatus == ST_EOF || 
             (writing && file_desc[file_count-1]->st_errno))
@@ -844,7 +845,7 @@
 
 #ifdef SOXMIX
     /* Free input buffers now that they are not used */
-    for (f = 0; f < MAX_INPUT_FILES; f++)
+    for (f = 0; f < input_count; f++)
     {
         free(ibuf[f]);
     }
@@ -851,24 +852,14 @@
 #endif
 
     /* Free output buffers now that they won't be used */
-    for(e = 0; e < neffects; e++)
-    {
-        free(efftab[e].obuf);
-        if (efftabR[e].obuf)
-            free(efftabR[e].obuf);
-    }
+    release_effect_buf();
 
     /* Very Important:
      * Effect stop is called BEFORE files close.
      * Effect may write out more data after.
      */
+    stop_effects();
 
-    for (e = 1; e < neffects; e++) {
-        (*efftab[e].h->stop)(&efftab[e]);
-        if (efftabR[e].name)
-            (* efftabR[e].h->stop)(&efftabR[e]);
-    }
-
     for (f = 0; f < input_count; f++)
     {
         /* If problems closing input file, just warn user since
@@ -893,6 +884,269 @@
     }
 }
 
+void parse_effects(int argc, char **argv)
+{
+    int argc_effect;
+
+    nuser_effects = 0;
+
+    while (optind < argc)
+    {
+        if (nuser_effects >= MAX_USER_EFF)
+        {
+            st_fail("To many effects specified.\n");
+        }
+
+        argc_effect = st_geteffect_opt(&user_efftab[nuser_effects],
+                                       argc - optind, &argv[optind]);
+
+        if (argc_effect == ST_EOF)
+        {
+            int i1;
+            fprintf(stderr, "%s: Known effects: ",myname);
+            for (i1 = 0; st_effects[i1].name; i1++)
+                fprintf(stderr, "%s ", st_effects[i1].name);
+            fprintf(stderr, "\n\n");
+            st_fail("Effect '%s' is not known!", argv[optind]);
+        }
+
+
+        /* Skip past effect name */
+        optind++;
+
+        (*user_efftab[nuser_effects].h->getopts)
+            (&user_efftab[nuser_effects],
+             argc_effect,
+             &argv[optind]);
+
+        /* Skip past the effect arguments */
+        optind += argc_effect;
+        nuser_effects++;
+    }
+}
+
+/*
+ * If no effect given, decide what it should be.
+ * Smart ruleset for multiple effects in sequence.
+ *      Puts user-specified effect in right place.
+ */
+void check_effects(void)
+{
+    int i;
+    int needchan = 0, needrate = 0, haschan = 0, hasrate = 0;
+    int effects_mask = 0;
+
+    if (writing)
+    {
+        needrate = (file_desc[0]->info.rate != file_desc[file_count-1]->info.rate);
+        needchan = (file_desc[0]->info.channels != file_desc[file_count-1]->info.channels);
+    }
+
+    for (i = 0; i < nuser_effects; i++)
+    {
+        if (user_efftab[i].h->flags & ST_EFF_CHAN)
+        {
+            haschan++;
+        }
+        if (user_efftab[i].h->flags & ST_EFF_RATE)
+        {
+            hasrate++;
+        }
+    }
+
+    if (haschan > 1)
+        st_fail("Can not specify multiple effects that modify channel #");
+    if (hasrate > 1)
+        st_report("Can not specify multiple effects that change sample rate");
+
+    /* If not writing output then do not worry about adding
+     * channel and rate effects.  This is just to speed things
+     * up.
+     */
+    if (!writing)
+    {
+        needchan = 0;
+        needrate = 0;
+    }
+
+    /* --------- add the effects ------------------------ */
+
+    /* efftab[0] is always the input stream and always exists */
+    neffects = 1;
+
+    /* If reducing channels then its faster to run all effects
+     * after the avg effect.
+     */
+    if (needchan && !(haschan) &&
+        (file_desc[0]->info.channels > file_desc[file_count-1]->info.channels))
+    {
+        /* Find effect and update initial pointers */
+        st_geteffect(&efftab[neffects], "avg");
+
+        /* give default opts for added effects */
+        /* FIXME: Should look at return code and abort on ST_EOF */
+        (* efftab[neffects].h->getopts)(&efftab[neffects],(int)0,
+                                        (char **)0);
+
+        /* Copy format info to effect table */
+        effects_mask = st_updateeffect(&efftab[neffects], 
+                                       &file_desc[0]->info,
+                                       &file_desc[file_count-1]->info, 
+                                       effects_mask);
+
+        neffects++;
+    }
+
+    /* If reducing the number of samples, its faster to run all effects
+     * after the resample effect.
+     */
+    if (needrate && !(hasrate) &&
+        (file_desc[0]->info.rate > file_desc[file_count-1]->info.rate))
+    {
+        if (soxpreview)
+            st_geteffect(&efftab[neffects], "rate");
+        else
+            st_geteffect(&efftab[neffects], "resample");
+
+        /* set up & give default opts for added effects */
+        /* FIXME: Should look at return code and abort on ST_EOF */
+        (* efftab[neffects].h->getopts)(&efftab[neffects],(int)0,
+                                        (char **)0);
+
+        /* Copy format info to effect table */
+        effects_mask = st_updateeffect(&efftab[neffects], 
+                                       &file_desc[0]->info,
+                                       &file_desc[file_count-1]->info, 
+                                       effects_mask);
+
+        /* Rate can't handle multiple channels so be sure and
+         * account for that.
+         */
+        if (efftab[neffects].ininfo.channels > 1)
+        {
+            memcpy(&efftabR[neffects], &efftab[neffects],
+                   sizeof(struct st_effect));
+        }
+
+        neffects++;
+    }
+
+    /* Copy over user specified effects into real efftab */
+    for(i = 0; i < nuser_effects; i++)
+    {
+        memcpy(&efftab[neffects], &user_efftab[i],
+               sizeof(struct st_effect));
+
+        /* Copy format info to effect table */
+        effects_mask = st_updateeffect(&efftab[neffects], 
+                                       &file_desc[0]->info,
+                                       &file_desc[file_count-1]->info, 
+                                       effects_mask);
+
+        /* If this effect can't handle multiple channels then
+         * account for this.
+         */
+        if ((efftab[neffects].ininfo.channels > 1) &&
+            !(efftab[neffects].h->flags & ST_EFF_MCHAN))
+        {
+            memcpy(&efftabR[neffects], &efftab[neffects],
+                   sizeof(struct st_effect));
+        }
+
+        neffects++;
+    }
+
+    /* If rate effect hasn't been added by now then add it here.
+     * Check adding rate before avg because its faster to run
+     * rate on less channels then more.
+     */
+    if (needrate && !(effects_mask & ST_EFF_RATE))
+    {
+        if (soxpreview)
+            st_geteffect(&efftab[neffects], "rate");
+        else
+            st_geteffect(&efftab[neffects], "resample");
+
+        /* set up & give default opts for added effects */
+        /* FIXME: Should look at return code and abort on ST_EOF */
+        (* efftab[neffects].h->getopts)(&efftab[neffects],(int)0,
+                                        (char **)0);
+
+        /* Copy format info to effect table */
+        effects_mask = st_updateeffect(&efftab[neffects], 
+                                       &file_desc[0]->info,
+                                       &file_desc[file_count-1]->info, 
+                                       effects_mask);
+
+        /* Rate can't handle multiple channels so be sure and
+         * account for that.
+         */
+        if (efftab[neffects].ininfo.channels > 1)
+        {
+            memcpy(&efftabR[neffects], &efftab[neffects],
+                   sizeof(struct st_effect));
+        }
+
+        neffects++;
+    }
+
+    /* If code up until now still hasn't added avg effect then
+     * do it now.
+     */
+    if (needchan && !(effects_mask & ST_EFF_CHAN))
+    {
+        st_geteffect(&efftab[neffects], "avg");
+
+        /* set up & give default opts for added effects */
+        /* FIXME: Should look at return code and abort on ST_EOF */
+        (* efftab[neffects].h->getopts)(&efftab[neffects],(int)0,
+                                        (char **)0);
+
+        /* Copy format info to effect table */
+        effects_mask = st_updateeffect(&efftab[neffects], 
+                                       &file_desc[0]->info,
+                                       &file_desc[file_count-1]->info, 
+                                       effects_mask);
+
+        neffects++;
+    }
+}
+
+void start_effects(void)
+{
+    int e;
+
+    for(e = 1; e < neffects; e++) {
+        (*efftab[e].h->start)(&efftab[e]);
+        if (efftabR[e].name)
+            (*efftabR[e].h->start)(&efftabR[e]);
+    }
+}
+
+void reserve_effect_buf(void)
+{
+    int e;
+
+    for(e = 0; e < neffects; e++)
+    {
+        efftab[e].obuf = (st_sample_t *)malloc(ST_BUFSIZ * 
+                                                sizeof(st_sample_t));
+        if (efftab[e].obuf == NULL)
+        {
+            st_fail("could not allocate memory");
+        }
+        if (efftabR[e].name)
+        {
+            efftabR[e].obuf = (st_sample_t *)malloc(ST_BUFSIZ * 
+                                                     sizeof(st_sample_t));
+            if (efftabR[e].obuf == NULL)
+            {
+                st_fail("could not allocate memory");
+            }
+        }
+    }
+}
+
 static int flow_effect_out(void)
 {
     int e, havedata, flowstatus = 0;
@@ -948,6 +1202,10 @@
           /* Currently, assuming all bytes were written and resetting
            * buffer pointers accordingly.
            */
+          read_samples += (efftab[neffects-1].olen / 
+                           file_desc[file_count-1]->info.channels);
+          output_samples += (efftab[neffects-1].olen / 
+                             file_desc[file_count-1]->info.channels);
           efftab[neffects-1].odone = efftab[neffects-1].olen = 0;
 
           if (file_desc[file_count-1]->st_errno)
@@ -957,8 +1215,14 @@
           }
       }
       else
+      {
           /* Make it look like everything was consumed */
+          read_samples += (efftab[neffects-1].olen / 
+                           file_desc[file_count-1]->info.channels);
+          output_samples += (efftab[neffects-1].olen / 
+                             file_desc[file_count-1]->info.channels);
           efftab[neffects-1].odone = efftab[neffects-1].olen = 0;
+      }
 
       /* if stuff still in pipeline, set up to flow effects again */
       /* When all effects have reported ST_EOF then this check will
@@ -1086,7 +1350,7 @@
     return ST_SUCCESS;
 }
 
-static int drain_effect_out(void)
+int drain_effect_out(void)
 {
     /* Skip past input effect since we know thats not needed */
     if (input_eff == 0)
@@ -1109,7 +1373,7 @@
     return flow_effect_out();
 }
 
-static int drain_effect(int e)
+int drain_effect(int e)
 {
     st_ssize_t i, olen, olenl, olenr;
     st_sample_t *obuf;
@@ -1155,198 +1419,112 @@
     }
     return rc;
 }
- 
-/*
- * If no effect given, decide what it should be.
- * Smart ruleset for multiple effects in sequence.
- *      Puts user-specified effect in right place.
- */
-static void
-checkeffect()
+
+void release_effect_buf(void)
 {
-        int i;
-        int needchan = 0, needrate = 0, haschan = 0, hasrate = 0;
-        int effects_mask = 0;
+    int e;
+    
+    for(e = 0; e < neffects; e++)
+    {
+        free(efftab[e].obuf);
+        if (efftabR[e].obuf)
+            free(efftabR[e].obuf);
+    }
+}
 
-        if (writing)
-        {
-            needrate = (file_desc[0]->info.rate != file_desc[file_count-1]->info.rate);
-            needchan = (file_desc[0]->info.channels != file_desc[file_count-1]->info.channels);
-        }
+void stop_effects(void)
+{
+    int e;
 
-        for (i = 0; i < nuser_effects; i++)
-        {
-            if (user_efftab[i].h->flags & ST_EFF_CHAN)
-            {
-                haschan++;
-            }
-            if (user_efftab[i].h->flags & ST_EFF_RATE)
-            {
-                hasrate++;
-            }
-        }
+    for (e = 1; e < neffects; e++) {
+        (*efftab[e].h->stop)(&efftab[e]);
+        if (efftabR[e].name)
+            (* efftabR[e].h->stop)(&efftabR[e]);
+    }
+}
 
-        if (haschan > 1)
-            st_fail("Can not specify multiple effects that modify channel #");
-        if (hasrate > 1)
-            st_report("Can not specify multiple effects that change sample rate");
+static void print_input_status(int input)
+{
+    fprintf(stderr, "\nInput Filename : %s\n", file_desc[input]->filename);
+    fprintf(stderr, "Sample Size    : %s\n", 
+            st_size_bits_str[file_desc[input]->info.size]);
+    fprintf(stderr, "Sample Encoding: %s\n", 
+            st_encodings_str[file_desc[input]->info.encoding]);
+    fprintf(stderr, "Channels       : %d\n", file_desc[input]->info.channels);
+    fprintf(stderr, "Sample Rate    : %d\n", file_desc[input]->info.rate);
 
-        /* If not writing output then do not worry about adding
-         * channel and rate effects.  This is just to speed things
-         * up.
-         */
-        if (!writing)
-        {
-            needchan = 0;
-            needrate = 0;
-        }
+    if (file_desc[input]->comment && *file_desc[input]->comment)
+        fprintf(stderr, "Comments       :\n%s\n", file_desc[input]->comment);
+    fprintf(stderr, "\n");
+}
+ 
+static void update_status(void)
+{
+    int read_min, left_min, in_min;
+    double read_sec, left_sec, in_sec;
+    double read_time, left_time, in_time;
+    float completed;
+    unsigned long out_size;
+    char unit;
 
-        /* --------- add the effects ------------------------ */
+    /* Currently, for both sox and soxmix, all input files must have
+     * the same sample rate.  So we can always just use the rate
+     * of the first input file to compute time.
+     */
+    read_time = (double)read_samples / (double)file_desc[0]->info.rate;
 
-        /* efftab[0] is always the input stream and always exists */
-        neffects = 1;
+    read_min = read_time / 60;
+    read_sec = (double)read_time - 60.0f * (double)read_min;
 
-        /* If reducing channels then its faster to run all effects
-         * after the avg effect.
-         */
-        if (needchan && !(haschan) &&
-            (file_desc[0]->info.channels > file_desc[file_count-1]->info.channels))
+    out_size = output_samples / 1073741824;
+    if (out_size)
+        unit = 'G';
+    else
+    {
+        out_size = output_samples / 1048675;
+        if (out_size)
+            unit = 'M';
+        else
         {
-            /* Find effect and update initial pointers */
-            st_geteffect(&efftab[neffects], "avg");
-
-            /* give default opts for added effects */
-            /* FIXME: Should look at return code and abort on ST_EOF */
-            (* efftab[neffects].h->getopts)(&efftab[neffects],(int)0,
-                                            (char **)0);
-
-            /* Copy format info to effect table */
-            effects_mask = st_updateeffect(&efftab[neffects], 
-                                           &file_desc[0]->info,
-                                           &file_desc[file_count-1]->info, 
-                                           effects_mask);
-
-            neffects++;
-        }
-
-        /* If reducing the number of samples, its faster to run all effects
-         * after the resample effect.
-         */
-        if (needrate && !(hasrate) &&
-            (file_desc[0]->info.rate > file_desc[file_count-1]->info.rate))
-        {
-            if (soxpreview)
-                st_geteffect(&efftab[neffects], "rate");
+            out_size = output_samples / 1024;
+            if (out_size)
+                unit = 'K';
             else
-                st_geteffect(&efftab[neffects], "resample");
-
-            /* set up & give default opts for added effects */
-            /* FIXME: Should look at return code and abort on ST_EOF */
-            (* efftab[neffects].h->getopts)(&efftab[neffects],(int)0,
-                                            (char **)0);
-
-            /* Copy format info to effect table */
-            effects_mask = st_updateeffect(&efftab[neffects], 
-                                           &file_desc[0]->info,
-                                           &file_desc[file_count-1]->info, 
-                                           effects_mask);
-
-            /* Rate can't handle multiple channels so be sure and
-             * account for that.
-             */
-            if (efftab[neffects].ininfo.channels > 1)
-            {
-                memcpy(&efftabR[neffects], &efftab[neffects],
-                       sizeof(struct st_effect));
-            }
-
-            neffects++;
+                unit = ' ';
         }
+    }
 
-        /* Copy over user specified effects into real efftab */
-        for(i = 0; i < nuser_effects; i++)
-        {
-            memcpy(&efftab[neffects], &user_efftab[i],
-                   sizeof(struct st_effect));
+    if (input_samples)
+    {
+        in_time = (double)input_samples / (double)file_desc[0]->info.rate;
+        left_time = in_time - read_time;
+        if (left_time < 0)
+            left_time = 0;
 
-            /* Copy format info to effect table */
-            effects_mask = st_updateeffect(&efftab[neffects], 
-                                           &file_desc[0]->info,
-                                           &file_desc[file_count-1]->info, 
-                                           effects_mask);
+        completed = ((double)read_samples / (double)input_samples) * 100;
+        if (completed < 0)
+            completed = 0;
+    }
+    else
+    {
+        in_time = 0;
+        left_time = 0;
+        completed = 0;
+    }
 
-            /* If this effect can't handle multiple channels then
-             * account for this.
-             */
-            if ((efftab[neffects].ininfo.channels > 1) &&
-                !(efftab[neffects].h->flags & ST_EFF_MCHAN))
-            {
-                memcpy(&efftabR[neffects], &efftab[neffects],
-                       sizeof(struct st_effect));
-            }
+    left_min = left_time / 60;
+    left_sec = (double)left_time - 60.0f * (double)left_min;
 
-            neffects++;
-        }
+    in_min = in_time / 60;
+    in_sec = (double)in_time - 60.0f * (double)in_min;
 
-        /* If rate effect hasn't been added by now then add it here.
-         * Check adding rate before avg because its faster to run
-         * rate on less channels then more.
-         */
-        if (needrate && !(effects_mask & ST_EFF_RATE))
-        {
-            if (soxpreview)
-                st_geteffect(&efftab[neffects], "rate");
-            else
-                st_geteffect(&efftab[neffects], "resample");
-
-            /* set up & give default opts for added effects */
-            /* FIXME: Should look at return code and abort on ST_EOF */
-            (* efftab[neffects].h->getopts)(&efftab[neffects],(int)0,
-                                            (char **)0);
-
-            /* Copy format info to effect table */
-            effects_mask = st_updateeffect(&efftab[neffects], 
-                                           &file_desc[0]->info,
-                                           &file_desc[file_count-1]->info, 
-                                           effects_mask);
-
-            /* Rate can't handle multiple channels so be sure and
-             * account for that.
-             */
-            if (efftab[neffects].ininfo.channels > 1)
-            {
-                memcpy(&efftabR[neffects], &efftab[neffects],
-                       sizeof(struct st_effect));
-            }
-
-            neffects++;
-        }
-
-        /* If code up until now still hasn't added avg effect then
-         * do it now.
-         */
-        if (needchan && !(effects_mask & ST_EFF_CHAN))
-        {
-            st_geteffect(&efftab[neffects], "avg");
-
-            /* set up & give default opts for added effects */
-            /* FIXME: Should look at return code and abort on ST_EOF */
-            (* efftab[neffects].h->getopts)(&efftab[neffects],(int)0,
-                                            (char **)0);
-
-            /* Copy format info to effect table */
-            effects_mask = st_updateeffect(&efftab[neffects], 
-                                           &file_desc[0]->info,
-                                           &file_desc[file_count-1]->info, 
-                                           effects_mask);
-
-            neffects++;
-        }
+    fprintf(stderr, "\rTime: %02i:%05.2f [%02i:%05.2f] of %02i:%05.2f (% 5.1f%%) Output Buffer: % 5ld%c", read_min, read_sec, left_min, left_sec, in_min, in_sec, completed, out_size, unit);
 }
 
-static void statistics(void) {
-        if (clipped > 0)
-                st_report("Volume change clipped %d samples", clipped);
+static void statistics(void) 
+{
+    if (clipped > 0)
+        st_report("Volume change clipped %d samples", clipped);
 }
 
 static st_sample_t volumechange(st_sample_t *buf, st_ssize_t ct, 
@@ -1401,8 +1579,8 @@
         if (opt)
                 fprintf(stderr, "Failed: %s\n", opt);
         else {
-            fprintf(stderr,"gopts: -e -h -p -v volume -V\n\n");
-            fprintf(stderr,"fopts: -r rate -c channels -s/-u/-U/-A/-a/-i/-g/-f -b/-w/-l/-d -x\n\n");
+            fprintf(stderr,"gopts: -e -h -p -q -S -V\n\n");
+            fprintf(stderr,"fopts: -r rate -c channels -s/-u/-U/-A/-a/-i/-g/-f -b/-w/-l/-d -v volume -x\n\n");
             fprintf(stderr, "effect: ");
             for (i = 0; st_effects[i].name != NULL; i++) {
                 fprintf(stderr, "%s ", st_effects[i].name);
--- a/src/st.h
+++ b/src/st.h
@@ -148,7 +148,8 @@
     st_loopinfo_t   loops[ST_MAX_NLOOPS]; /* Looping specification */
     char            swap;                 /* do byte- or word-swap */
     char            seekable;             /* can seek on this file */
-    st_size_t       length; /* estimate of total samples in file - for seeking*/
+    /* Total samples per channel of file.  Zero if unknown. */
+    st_size_t       length;    
     char            *filename;            /* file name */
     char            *filetype;            /* type of file */
     char            *comment;             /* comment string */
@@ -199,10 +200,12 @@
 #define ST_ENCODING_INV_ULAW    9 /* Inversed bit-order u-law */
 #define ST_ENCODING_INV_ALAW    10/* Inversed bit-order A-law */
 #define ST_ENCODING_MP3         11/* MP3 compression */
-#define ST_ENCODING_MAX         11 
+#define ST_ENCODING_VORBIS      12/* Vorbis compression */
+#define ST_ENCODING_MAX         12 
 
 /* declared in misc.c */
 extern const char *st_sizes_str[];
+extern const char *st_size_bits_str[];
 extern const char *st_encodings_str[];
 
 #define ST_EFF_CHAN     1               /* Effect can mix channels up/down */
--- a/src/vorbis.c
+++ b/src/vorbis.c
@@ -126,9 +126,15 @@
 
         /* Record audio info */
         ft->info.rate = vi->rate;
-        ft->info.size = ST_SIZE_16BIT;
-        ft->info.encoding = ST_ENCODING_SIGN2;
+        ft->info.size = ST_SIZE_32BIT;
+        ft->info.encoding = ST_ENCODING_VORBIS;
         ft->info.channels = vi->channels;
+
+        /* ov_pcm_total doesn't work on non-seekable files so
+         * skip that step in that case.
+         */
+        if (ft->seekable)
+            ft->length = ov_pcm_total(vb->vf, -1);
         
         /* Record comments */
         if (vc->comments == 0)
@@ -343,6 +349,9 @@
         vorbis_t vb = (vorbis_t) ft->priv;
         vorbis_enc_t *ve;
         long rate;
+
+        ft->info.size = ST_SIZE_32BIT;
+        ft->info.encoding = ST_ENCODING_VORBIS;
 
         /* Allocate memory for all of the structures */
         ve = vb->vorbis_enc_data = (vorbis_enc_t *)malloc(sizeof(vorbis_enc_t));
--- a/src/wav.c
+++ b/src/wav.c
@@ -87,8 +87,8 @@
     unsigned short samplesPerBlock;
     unsigned short blockAlign;
     st_size_t dataStart;  /* need to for seeking */
-    int found_cooledit;
-    
+    int found_cooledit;   
+
     /* following used by *ADPCM wav files */
     unsigned short nCoefs;          /* ADPCM: number of coef sets */
     short         *iCoefs;          /* ADPCM: coef sets           */
@@ -878,7 +878,7 @@
                            wav->blockAlign, wav->samplesPerBlock);
         /*st_report("datalen %d, numSamples %d",dwDataLength, wav->numSamples);*/
         wav->blockSamplesRemaining = 0;        /* Samples left in buffer */
-        ft->length = wav->numSamples*ft->info.channels;
+        ft->length = wav->numSamples;
         break;
 
     case WAVE_FORMAT_IMA_ADPCM:
@@ -890,12 +890,12 @@
         /*st_report("datalen %d, numSamples %d",dwDataLength, wav->numSamples);*/
         wav->blockSamplesRemaining = 0;        /* Samples left in buffer */
         initImaTable();
-        ft->length = wav->numSamples*ft->info.channels;
+        ft->length = wav->numSamples;
         break;
 
 #ifdef ENABLE_GSM
     case WAVE_FORMAT_GSM610:
-        wav->numSamples = (((dwDataLength / wav->blockAlign) * wav->samplesPerBlock) * ft->info.channels);
+        wav->numSamples = ((dwDataLength / wav->blockAlign) * wav->samplesPerBlock);
         wavgsminit(ft);
         ft->length = wav->numSamples;
         break;
@@ -902,9 +902,8 @@
 #endif
 
     default:
-        wav->numSamples = dwDataLength/ft->info.size;   /* total samples */
+        wav->numSamples = dwDataLength/ft->info.size/ft->info.channels;
         ft->length = wav->numSamples;
-
     }
 
     st_report("Reading Wave file: %s format, %d channel%s, %d samp/sec",
@@ -916,25 +915,27 @@
     /* Can also report extended fmt information */
     switch (wav->formatTag)
     {
-    case WAVE_FORMAT_ADPCM:
-        st_report("        %d Extsize, %d Samps/block, %d bytes/block %d Num Coefs",
-                wExtSize,wav->samplesPerBlock,bytesPerBlock,wav->nCoefs);
-        break;
+        case WAVE_FORMAT_ADPCM:
+            st_report("        %d Extsize, %d Samps/block, %d bytes/block %d Num Coefs, %d Samps/chan",
+                      wExtSize,wav->samplesPerBlock,bytesPerBlock,wav->nCoefs,
+                      wav->numSamples);
+            break;
 
-    case WAVE_FORMAT_IMA_ADPCM:
-        st_report("        %d Extsize, %d Samps/block, %d bytes/block",
-                wExtSize,wav->samplesPerBlock,bytesPerBlock);
-        break;
+        case WAVE_FORMAT_IMA_ADPCM:
+            st_report("        %d Extsize, %d Samps/block, %d bytes/block %d Samps/chan",
+                      wExtSize, wav->samplesPerBlock, bytesPerBlock, 
+                      wav->numSamples);
+            break;
 
 #ifdef ENABLE_GSM
-    case WAVE_FORMAT_GSM610:
-        st_report("GSM .wav: %d Extsize, %d Samps/block,  %d samples",
-                wExtSize,wav->samplesPerBlock,wav->numSamples);
-        break;
+        case WAVE_FORMAT_GSM610:
+            st_report("GSM .wav: %d Extsize, %d Samps/block, %d Samples/chan",
+                      wExtSize, wav->samplesPerBlock, wav->numSamples);
+            break;
 #endif
 
-    default:
-        break;
+        default:
+            st_report("        %d Samps/chans", wav->numSamples);
     }
 
     /* Horrible way to find Cool Edit marker points. Taken from Quake source*/
@@ -1066,10 +1067,6 @@
         case ST_ENCODING_IMA_ADPCM:
         case ST_ENCODING_ADPCM:
 
-            /* FIXME: numSamples is not used consistently in
-             * wav handler.  Sometimes it accounts for stereo,
-             * sometimes it does not.
-             */
             /* See reason for cooledit check in comments below */
             if (wav->found_cooledit && len > (wav->numSamples*ft->info.channels)) 
                 len = (wav->numSamples*ft->info.channels);
@@ -1121,8 +1118,8 @@
 #ifdef ENABLE_GSM
         case ST_ENCODING_GSM:
             /* See reason for cooledit check in comments below */
-            if (wav->found_cooledit && len > wav->numSamples) 
-                len = wav->numSamples;
+            if (wav->found_cooledit && len > wav->numSamples*ft->info.channels) 
+                len = (wav->numSamples*ft->info.channels);
 
             done = wavgsmread(ft, buf, len);
             if (done == 0 && wav->numSamples != 0)
@@ -1140,8 +1137,8 @@
              * greater then 2Gig and can't be represented
              * by the 32-bit size field.
              */
-            if (wav->found_cooledit && len > wav->numSamples) 
-                len = wav->numSamples;
+            if (wav->found_cooledit && len > wav->numSamples*ft->info.channels) 
+                len = (wav->numSamples*ft->info.channels);
 
             done = st_rawread(ft, buf, len);
             /* If software thinks there are more samples but I/O */
@@ -1150,7 +1147,7 @@
                 st_warn("Premature EOF on .wav input file");
         }
 
-        wav->numSamples -= done;
+        wav->numSamples -= (done/ft->info.channels);
         return done;
 }
 
@@ -1629,13 +1626,13 @@
 #ifdef ENABLE_GSM
         case WAVE_FORMAT_GSM610:
             len = wavgsmwrite(ft, buf, len);
-            wav->numSamples += len;
+            wav->numSamples += (len/ft->info.channels);
             return len;
             break;
 #endif
         default:
             len = st_rawwrite(ft, buf, len);
-            wav->numSamples += len; /* must later be divided by wChannels */
+            wav->numSamples += (len/ft->info.channels);
             return len;
         }
 }
@@ -1769,7 +1766,8 @@
             ft->st_errno = st_seek(ft, new_offset, SEEK_SET);
 
             if( ft->st_errno == ST_SUCCESS )
-                wav->numSamples = ft->length - (new_offset / ft->info.size);
+                wav->numSamples = ft->length - (new_offset / ft->info.size /
+                                  ft->info.channels);
     }
 
     return(ft->st_errno);
--- a/src/wve.c
+++ b/src/wve.c
@@ -124,7 +124,7 @@
         ft->info.channels = 1;
 
         p->dataStart = st_tell(ft);
-        ft->length = p->length/ft->info.size;
+        ft->length = p->length/ft->info.size/ft->info.channels;
 
         return (ST_SUCCESS);
 }