shithub: sox

Download patch

ref: 02fb037bc06ea15f3a5d5ce38f2d9af42533ed6f
parent: 2c65075425ac1ba118a3b520554dc165b28afe90
author: robs <robs>
date: Sun Jan 21 17:35:11 EST 2007

Display the (auto-)selected effects chain with -V.
Don't list deprecated effects in sox usage; move them to separate
section of man page.
mixer is the new name for avg.
Null pointer idiom for effect driver functions.

--- a/sox.1
+++ b/sox.1
@@ -287,7 +287,7 @@
 plays an audio file (through the default sound device) whilst applying a
 bass boosting effect,
 .SP
-	play \-c 4 \-n \-c 1 synth sin %\-12 sin %\-9 sin %\-5 sin %\-2 vol 0\*d7 avg fade q 0\*d1 1 0\*d1
+	play \-c 4 \-n \-c 1 synth sin %\-12 sin %\-9 sin %\-5 sin %\-2 vol 0\*d7 mixer fade q 0\*d1 1 0\*d1
 .SP
 plays a synthesised `A minor seventh' chord with a pipe-organ sound,
 .SP
@@ -388,8 +388,9 @@
 and will be merged together (instead of concatenated)
 to form the output file.
 A merged audio file comprises all of the channels from all of the input
-files; a merged file could be un-merged using the
-.B pick
+files; a merged file could be un-merged by using multiple invocations of
+SoX with the
+.B mixer
 effect.
 .SP
 For example, two mono files could be merged to form one
@@ -484,9 +485,9 @@
 the output file to have a different number of channels than the input
 file, include this option with the output file options.
 If the input and output file have a different number of channels then the
-.B avg
+.B mixer
 effect must be used.  If the
-.B avg
+.B mixer
 effect is not specified on the
 command line it will be invoked internally with default parameters.
 .TP
@@ -1029,52 +1030,6 @@
 .SP
 This effect supports the \fB\-\-octave\fR global option.
 .TP
-\fBavg\fR [ \fB\-l\fR\^|\^\fB\-r\fR\^|\^\fB\-f\fR\^|\^\fB\-b\fR\^|\^\fB\-1\fR\^|\^\fB\-2\fR\^|\^\fB\-3\fR\^|\^\fB\-4\fR\^|\^\fIn\fR{\fB,\fIn\fR} ]
-Reduce the number of audio channels by mixing or selecting channels,
-or duplicate channels to increase the number of channels.
-This effect is automatically used when the number of input
-channels differ from the number of output channels.  When reducing
-the number of channels it is possible to manually specify the
-.B avg
-effect and use the \fB\-l\fR, \fB\-r\fR, \fB\-f\fR, \fB\-b\fR,
-\fB\-1\fR, \fB\-2\fR, \fB\-3\fR, \fB\-4\fR, options to select only
-the left, right, front, back channel(s) or specific channel
-for the output instead of averaging the channels.
-The \fB\-l\fR, and \fB\-r\fR options will do averaging
-in quad-channel files so select the exact channel to prevent this.
-.SP
-The
-.B avg
-effect can also be invoked with up to 16
-numbers, separated by commas, which specify the proportion (0 = 0% and 1 = 100%)
-of each input channel that is to be mixed into each output channel.
-In two-channel mode, 4 numbers are given: l \*(RA l, l \*(RA r, r \*(RA l, and r \*(RA r,
-respectively.
-In four-channel mode, the first 4 numbers give the proportions for the
-left-front output channel, as follows: lf \*(RA lf, rf \*(RA lf, lb \*(RA lf, and
-rb \*(RA rf.
-The next 4 give the right-front output in the same order, then
-left-back and right-back.
-.SP
-It is also possible to use the 16 numbers to expand or reduce the
-channel count; just specify 0 for unused channels.
-.SP
-Finally, certain reduced combination of numbers can be specified
-for certain input/output channel combinations.
-.TS
-center box ;
-cB cB cB lB
-c c c l .
-In Ch	Out Ch	Num	Mappings
-2	1	2	l \*(RA l, r \*(RA l
-2	2	1	adjust balance
-4	1	4	lf \*(RA l, rf \*(RA l, lb \*(RA l, rb \*(RA l
-4	2	2	lf \*(RA l&rf \*(RA r, lb \*(RA l&rb \*(RA r
-4	4	1	adjust balance
-4	4	2	front balance, back balance
-.TE
-.SP
-.TP
 \fBband\fR [\fB\-n\fR] \fIcenter\fR [width\fR[\fBh\fR\^|\^\fBo\fR\^|\^\fBq\fR]]
 Apply a band-pass filter.
 The frequency response drops logarithmically
@@ -1386,14 +1341,6 @@
 .SP
 See [3] for a detailed description of flanging.
 .TP
-\fBhighp\fR \fIfrequency\fR
-Apply a high-pass filter.
-This effect is just an alias for the
-.B highpass
-effect used with its
-.B -1
-option; it is retained for backwards compatibilty only.
-.TP
 \fBhighpass\fR\^|\^\fBlowpass\fR [\fB-1\fR|\fB-2\fR] \fIfrequency\fR [\fRwidth\fR[\fBq\fR\^|\^\fBo\fR\^|\^\fBh\fR]]
 Apply a high-pass or low-pass filter with 3dB point \fIfrequency\fR.
 The filter can be either single-pole (with
@@ -1412,16 +1359,8 @@
 .SP
 See also \fBfilter\fR for filters with a steeper roll-off.
 .TP
-\fBlowp \fIfrequency\fR
+\fBlowpass\fR [\fB-1\fR|\fB-2\fR] \fIfrequency\fR [\fRwidth\fR[\fBq\fR\^|\^\fBo\fR\^|\^\fBh\fR]]
 Apply a low-pass filter.
-This effect is just an alias for the
-.B lowpass
-effect used with its
-.B -1
-option; it is retained for backwards compatibilty only.
-.TP
-\fBlowpass\fR [\fB-1\fR|\fB-2\fR] \fIfrequency\fR [\fIQ\fR]
-Apply a low-pass filter.
 See the description of the \fBhighpass\fR effect for details.
 .TP
 \fBlua\fR \fIlua-script\fR [\fIoption...\fR]
@@ -1429,9 +1368,6 @@
 .BR soxlua (7)
 for details.
 .TP
-\fBmask\fR [\fIdepth\fR]
-This effect is just a deprecated alias for the \fBdither\fR effect, left for historical reasons.
-.TP
 \fBmcompand "\fIattack1\fB,\fIdecay1\fR{\fB,\fIattack2\fB,\fIdecay2\fR}
 \fIin-dB1\fB,\fIout-dB1\fR{\fB,\fIin-dB2\fB,\fIout-dB2\fR}
 .br
@@ -1445,6 +1381,52 @@
 \fIxover-freq\fR. This can be repeated multiple times to create
 multiple bands.
 .TP
+\fBmixer\fR [ \fB\-l\fR\^|\^\fB\-r\fR\^|\^\fB\-f\fR\^|\^\fB\-b\fR\^|\^\fB\-1\fR\^|\^\fB\-2\fR\^|\^\fB\-3\fR\^|\^\fB\-4\fR\^|\^\fIn\fR{\fB,\fIn\fR} ]
+Reduce the number of audio channels by mixing or selecting channels,
+or duplicate channels to increase the number of channels.
+This effect is automatically used when the number of input
+channels differ from the number of output channels.  When reducing
+the number of channels it is possible to manually specify the
+.B mixer
+effect and use the \fB\-l\fR, \fB\-r\fR, \fB\-f\fR, \fB\-b\fR,
+\fB\-1\fR, \fB\-2\fR, \fB\-3\fR, \fB\-4\fR, options to select only
+the left, right, front, back channel(s) or specific channel
+for the output instead of averaging the channels.
+The \fB\-l\fR, and \fB\-r\fR options will do averaging
+in quad-channel files so select the exact channel to prevent this.
+.SP
+The
+.B mixer
+effect can also be invoked with up to 16
+numbers, separated by commas, which specify the proportion (0 = 0% and 1 = 100%)
+of each input channel that is to be mixed into each output channel.
+In two-channel mode, 4 numbers are given: l \*(RA l, l \*(RA r, r \*(RA l, and r \*(RA r,
+respectively.
+In four-channel mode, the first 4 numbers give the proportions for the
+left-front output channel, as follows: lf \*(RA lf, rf \*(RA lf, lb \*(RA lf, and
+rb \*(RA rf.
+The next 4 give the right-front output in the same order, then
+left-back and right-back.
+.SP
+It is also possible to use the 16 numbers to expand or reduce the
+channel count; just specify 0 for unused channels.
+.SP
+Finally, certain reduced combination of numbers can be specified
+for certain input/output channel combinations.
+.TS
+center box ;
+cB cB cB lB
+c c c l .
+In Ch	Out Ch	Num	Mappings
+2	1	2	l \*(RA l, r \*(RA l
+2	2	1	adjust balance
+4	1	4	lf \*(RA l, rf \*(RA l, lb \*(RA l, rb \*(RA l
+4	2	2	lf \*(RA l&rf \*(RA r, lb \*(RA l&rb \*(RA r
+4	4	1	adjust balance
+4	4	2	front balance, back balance
+.TE
+.SP
+.TP
 \fBnoiseprof\fR [\fIprofile-file\fR]
 Calculate a profile of the audio for use in noise reduction.
 See the description of the \fBnoisered\fR effect for details.
@@ -1515,11 +1497,6 @@
 (\fB\-t\fR).  The decay should be less than 0\*d5 to avoid
 feedback.  Gain-out is the volume of the output.
 .TP
-\fBpick\fR [ \fB\-l\fR\^|\^\fB\-r\fR\^|\^\fB\-f\fR\^|\^\fB\-b\fR\^|\^\fB\-1\fR\^|\^\fB\-2\fR\^|\^\fB\-3\fR\^|\^\fB\-4\fR\^|\^\fIn\fR{\fB,\fIn\fR} ]
-Pick a subset of channels to be copied into the output file.  This effect is just an alias of the
-.B avg
-effect and is retained for backwards compatibility only.
-.TP
 \fBpitch \fIshift\fR [\fIwidth interpolate fade\fR]
 Change the pitch of file without affecting its duration by cross-fading
 shifted samples.
@@ -1576,10 +1553,6 @@
 for other sample-rate changing effects, and see
 \fBresample\fR for more discussion of resampling.
 .TP
-\fBrate\fR
-Does the same as \fBresample\fR with no parameters; it exists for
-backwards compatibility.
-.TP
 \fBrepeat \fIcount\fR
 Repeat the entire audio \fIcount\fR times.
 Requires disk space to store the data to be repeated.
@@ -2032,6 +2005,46 @@
 Not specifying this parameter will cause no limiter to be used.  In verbose
 mode, this effect will display the percentage of the audio that needed to be
 limited.
+.SS Deprecated Effects
+The following effects have ben renamed or have their functionality
+included in another effect.  They continue to work in this version of
+SoX but may be removed in future.
+.TP
+\fBavg\fR [ \fB\-l\fR\^|\^\fB\-r\fR\^|\^\fB\-f\fR\^|\^\fB\-b\fR\^|\^\fB\-1\fR\^|\^\fB\-2\fR\^|\^\fB\-3\fR\^|\^\fB\-4\fR\^|\^\fIn\fR{\fB,\fIn\fR} ]
+Reduce the number of audio channels by mixing or selecting channels,
+or duplicate channels to increase the number of channels.
+This effect is just an alias of the
+.B mixer
+effect and is retained for backwards compatibility only.
+.TP
+\fBhighp\fR \fIfrequency\fR
+Apply a high-pass filter.
+This effect is just an alias for the
+.B highpass
+effect used with its
+.B -1
+option; it is retained for backwards compatibilty only.
+.TP
+\fBlowp \fIfrequency\fR
+Apply a low-pass filter.
+This effect is just an alias for the
+.B lowpass
+effect used with its
+.B -1
+option; it is retained for backwards compatibilty only.
+.TP
+\fBmask\fR [\fIdepth\fR]
+This effect is just a deprecated alias for the \fBdither\fR effect, left for historical reasons.
+.TP
+\fBpick\fR [ \fB\-l\fR\^|\^\fB\-r\fR\^|\^\fB\-f\fR\^|\^\fB\-b\fR\^|\^\fB\-1\fR\^|\^\fB\-2\fR\^|\^\fB\-3\fR\^|\^\fB\-4\fR\^|\^\fIn\fR{\fB,\fIn\fR} ]
+Pick a subset of channels to be copied into the output file.
+This effect is just an alias of the
+.B mixer
+effect and is retained for backwards compatibility only.
+.TP
+\fBrate\fR
+Does the same as \fBresample\fR with no parameters; it exists for
+backwards compatibility.
 .SH DIAGNOSTICS
 Exit status is 0 for no error, 1 if there is a problem with the
 command-line parameters, or 2 if an error occurs during file processing.
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -22,9 +22,9 @@
 	  sfircam.h skelform.c smp.c sndfile.c sndrtool.c sphere.c tx16w.c \
 	  voc.c vorbis.c vox.c wav.c wav.h wve.c xa.c
 
-effects = avg.c band.h biquad.c biquad.h biquads.c chorus.c compand.c dcshift.c\
-	  deemph.h earwax.c echo.c echos.c fade.c FFT.c FFT.h filter.c \
-	  flanger.c luaeff.c luaform.c lintlib.c mask.c mcompand.c noiseprof.c \
+effects = band.h biquad.c biquad.h biquads.c chorus.c compand.c dcshift.c\
+	  deemph.h dither.c earwax.c echo.c echos.c fade.c FFT.c FFT.h filter.c\
+	  flanger.c luaeff.c luaform.c lintlib.c mcompand.c mixer.c noiseprof.c\
 	  noisered.c noisered.h pad.c pan.c phaser.c pitch.c polyphas.c \
 	  rabbit.c rate.c repeat.c resample.c reverb.c reverse.c silence.c \
 	  skeleff.c speed.c stat.c stretch.c swap.c synth.c trim.c vibro.c vol.c
--- a/src/avg.c
+++ /dev/null
@@ -1,582 +1,0 @@
-
-/*
- * July 5, 1991
- * Copyright 1991 Lance Norskog And Sundry Contributors
- * This source code is freely redistributable and may be used for
- * any purpose.  This copyright notice must be maintained. 
- * Lance Norskog And Sundry Contributors are not responsible for 
- * the consequences of using this software.
- *
- * Channel duplication code by Graeme W. Gill - 93/5/18
- * General-purpose panning by Geoffrey H. Kuenning -- 2000/11/28
- */
-
-/*
- * Sound Tools stereo/quad -> mono mixdown effect file.
- * and mono/stereo -> stereo/quad channel duplication.
- */
-
-#include "st_i.h"
-#include <ctype.h>
-#include <string.h>
-#include <stdlib.h>
-
-static st_effect_t st_avg_effect;
-
-typedef struct avgstuff {
-        /* How to generate each output channel.  sources[i][j] */
-        /* represents the fraction of channel i that should be passed */
-        /* through to channel j on output, and so forth.  Channel 0 is */
-        /* left front, channel 1 is right front, and 2 and 3 are left */
-        /* and right rear, respectively. (GHK) */
-        double  sources[4][4];
-        int     num_pans;
-        int     mix;                    /* How are we mixing it? */
-} *avg_t;
-
-/* MIX_CENTER is shorthand to mix channels together at 50% each */
-#define MIX_CENTER      0
-#define MIX_LEFT        1
-#define MIX_RIGHT       2
-#define MIX_FRONT       3
-#define MIX_BACK        4
-#define MIX_SPECIFIED   5
-#define MIX_LEFT_FRONT  6
-#define MIX_RIGHT_FRONT 7
-#define MIX_LEFT_BACK   8
-#define MIX_RIGHT_BACK  9
-
-
-/*
- * Process options
- */
-static int st_avg_getopts(eff_t effp, int n, char **argv) 
-{
-    avg_t avg = (avg_t) effp->priv;
-    double* pans = &avg->sources[0][0];
-    int i;
-
-    for (i = 0;  i < 16;  i++)
-        pans[i] = 0.0;
-    avg->mix = MIX_CENTER;
-    avg->num_pans = 0;
-
-    /* Parse parameters.  Since we don't yet know the number of */
-    /* input and output channels, we'll record the information for */
-    /* later. */
-    if (n == 1) {
-        if(!strcmp(argv[0], "-l"))
-            avg->mix = MIX_LEFT;
-        else if (!strcmp(argv[0], "-r"))
-            avg->mix = MIX_RIGHT;
-        else if (!strcmp(argv[0], "-f"))
-            avg->mix = MIX_FRONT;
-        else if (!strcmp(argv[0], "-b"))
-            avg->mix = MIX_BACK;
-        else if (!strcmp(argv[0], "-1"))
-            avg->mix = MIX_LEFT_FRONT;
-        else if (!strcmp(argv[0], "-2"))
-            avg->mix = MIX_RIGHT_FRONT;
-        else if (!strcmp(argv[0], "-3"))
-            avg->mix = MIX_LEFT_BACK;
-        else if (!strcmp(argv[0], "-4"))
-            avg->mix = MIX_RIGHT_BACK;
-        else if (argv[0][0] == '-' && !isdigit((int)argv[0][1])
-                && argv[0][1] != '.') {
-            st_fail(st_avg_effect.usage);
-            return (ST_EOF);
-        }
-        else {
-            int commas;
-            char *s;
-            avg->mix = MIX_SPECIFIED;
-            pans[0] = atof(argv[0]);
-            for (s = argv[0], commas = 0; *s; ++s) {
-                if (*s == ',') {
-                    ++commas;
-                    if (commas >= 16) {
-                        st_fail("avg can only take up to 16 pan values");
-                        return (ST_EOF);
-                    }
-                    pans[commas] = atof(s+1);
-                }
-            }
-            avg->num_pans = commas + 1;
-        }
-    }
-    else if (n == 0) {
-        avg->mix = MIX_CENTER;
-    }
-    else {
-        st_fail(st_avg_effect.usage);
-        return ST_EOF;
-    }
-
-    return (ST_SUCCESS);
-}
-
-/*
- * Start processing
- */
-static int st_avg_start(eff_t effp)
-{
-    /*
-       Hmmm, this is tricky.  Lemme think:
-       channel orders are [0][0],[0][1], etc.
-       i.e., 0->0, 0->1, 0->2, 0->3, 1->0, 1->1, ...
-       trailing zeros are omitted
-       L/R balance is x= -1 for left only, 1 for right only
-       1->1 channel effects:
-       changing volume by x is x,0,0,0
-       1->2 channel effects:
-       duplicating everywhere is 1,1,0,0
-       1->4 channel effects:
-       duplicating everywhere is 1,1,1,1
-       2->1 channel effects:
-       left only is 1,0,0,0 0,0,0,0
-       right only is 0,0,0,0 1,0,0,0
-       left+right is 0.5,0,0,0 0.5,0,0,0
-       left-right is 1,0,0,0 -1,0,0,0
-       2->2 channel effects:
-       L/R balance can be done several ways.  The standard stereo
-       way is both the easiest and the most sensible:
-       min(1-x,1),0,0,0 0,min(1+x,1),0,0
-       left to both is 1,1,0,0
-       right to both is 0,0,0,0 1,1,0,0
-       left+right to both is 0.5,0.5,0,0 0.5,0.5,0,0
-       left-right to both is 1,1,0,0 -1,-1,0,0
-       left-right to left, right-left to right is 1,-1,0,0 -1,1,0,0
-       2->4 channel effects:
-       front duplicated into rear is 1,0,1,0 0,1,0,1
-       front swapped into rear (why?) is 1,0,0,1 0,1,1,0
-       front put into rear as mono (why?) is 1,0,0.5,0.5 0,1,0.5,0.5
-       4->1 channel effects:
-       left front only is 1,0,0,0
-       left only is 0.5,0,0,0 0,0,0,0 0.5,0,0,0
-       etc.
-       4->2 channel effects:
-       merge front/back is 0.5,0,0,0 0,0.5,0,0 0.5,0,0,0 0,0.5,0,0
-       selections similar to above
-       4->4 channel effects:
-       left front to all is 1,1,1,1 0,0,0,0
-       right front to all is 0,0,0,0 1,1,1,1
-       left f/r to all f/r is 1,1,0,0 0,0,0,0 0,0,1,1 0,0,0,0
-       etc.
-
-       The interesting cases from above (deserving of abbreviations of
-       less than 16 numbers) are:
-
-       0) n->n volume change (1 number)
-       1) 1->n duplication (0 numbers)
-       2) 2->1 mixdown (0 or 2 numbers)
-       3) 2->2 balance (1 number)
-       4) 2->2 fully general mix (4 numbers)
-       5) 2->4 duplication (0 numbers)
-       6) 4->1 mixdown (0 or 4 numbers)
-       7) 4->2 mixdown (0, or 2 numbers)
-       8) 4->4 balance (1 or 2 numbers)
-
-       The above has one ambiguity: n->n volume change conflicts with
-       n->n balance for n != 1.  In such a case, we'll prefer
-       balance, since there is already a volume effect in vol.c.
-
-       GHK 2000/11/28
-     */
-     avg_t avg = (avg_t) effp->priv;
-     double pans[16];
-     int i, j;
-     int ichan, ochan;
-
-     for (i = 0;  i < 16;  i++)
-         pans[i] = ((double*)&avg->sources[0][0])[i];
-
-     ichan = effp->ininfo.channels;
-     ochan = effp->outinfo.channels;
-     if (ochan == -1) {
-         st_fail("Output must have known number of channels to use avg effect");
-         return(ST_EOF);
-     }
-
-     if ((ichan != 1 && ichan != 2 && ichan != 4)
-             ||  (ochan != 1 && ochan != 2 && ochan != 4)) {
-         st_fail("Can't average %d channels into %d channels",
-                 ichan, ochan);
-         return (ST_EOF);
-     }
-
-     /* Handle the special-case flags */
-     switch (avg->mix) {
-         case MIX_CENTER:
-             if (ichan == ochan) {
-                 st_fail("Output must have different number of channels to use avg effect");
-                 return(ST_EOF);
-             }
-             break;             /* Code below will handle this case */
-         case MIX_LEFT:
-             if (ichan == 2 && ochan == 1)
-             {
-                 pans[0] = 1.0;
-                 pans[1] = 0.0;
-                 avg->num_pans = 2;
-             }
-             else if (ichan == 4 && ochan == 1)
-             {
-                 pans[0] = 0.5;
-                 pans[1] = 0.0;
-                 pans[2] = 0.5;
-                 pans[3] = 0.0;
-                 avg->num_pans = 4;
-             }
-             else
-             {
-                 st_fail("Can't average %d channels into %d channels",
-                         ichan, ochan);
-                 return ST_EOF;
-             }
-             break;
-         case MIX_RIGHT:
-             if (ichan == 2 && ochan == 1)
-             {
-                 pans[0] = 0.0;
-                 pans[1] = 1.0;
-                 avg->num_pans = 2;
-             }
-             else if (ichan == 4 && ochan == 1)
-             {
-                 pans[0] = 0.0;
-                 pans[1] = 0.5;
-                 pans[2] = 0.0;
-                 pans[3] = 0.5;
-                 avg->num_pans = 4;
-             }
-             else
-             {
-                 st_fail("Can't average %d channels into %d channels",
-                         ichan, ochan);
-                 return ST_EOF;
-             }
-             break;
-         case MIX_FRONT:
-             if (ichan == 4 && ochan == 2)
-             {
-                 pans[0] = 1.0;
-                 pans[1] = 0.0;
-                 avg->num_pans = 2;
-             }
-             else
-             {
-                 st_fail("avg: -f option requires 4 channels input and 2 channel output");
-                 return ST_EOF;
-             }
-             break;
-         case MIX_BACK:
-             if (ichan == 4 && ochan == 2)
-             {
-                 pans[0] = 0.0;
-                 pans[1] = 1.0;
-                 avg->num_pans = 2;
-             }
-             else
-             {
-                 st_fail("avg: -b option requires 4 channels input and 2 channel output");
-                 return ST_EOF;
-             }
-             break;
-         case MIX_LEFT_FRONT:
-             if (ichan == 2 && ochan == 1)
-             {
-                 pans[0] = 1.0;
-                 pans[1] = 0.0;
-                 avg->num_pans = 2;
-             }
-             else if (ichan == 4 && ochan == 1)
-             {
-                 pans[0] = 1.0;
-                 pans[1] = 0.0;
-                 pans[2] = 0.0;
-                 pans[3] = 0.0;
-                 avg->num_pans = 4;
-             }
-             else
-             {
-                 st_fail("avg: -1 option requires 4 channels input and 1 channel output");
-                 return ST_EOF;
-             }
-             break;
-         case MIX_RIGHT_FRONT:
-             if (ichan == 2 && ochan == 1)
-             {
-                 pans[0] = 0.0;
-                 pans[1] = 1.0;
-                 avg->num_pans = 2;
-             }
-             else if (ichan == 4 && ochan == 1)
-             {
-                 pans[0] = 0.0;
-                 pans[1] = 1.0;
-                 pans[2] = 0.0;
-                 pans[3] = 0.0;
-                 avg->num_pans = 4;
-             }
-             else
-             {
-                 st_fail("avg: -2 option requires 4 channels input and 1 channel output");
-                 return ST_EOF;
-             }
-             break;
-         case MIX_LEFT_BACK:
-             if (ichan == 4 && ochan == 1)
-             {
-                 pans[0] = 0.0;
-                 pans[1] = 0.0;
-                 pans[2] = 1.0;
-                 pans[3] = 0.0;
-                 avg->num_pans = 4;
-             }
-             else
-             {
-                 st_fail("avg: -3 option requires 4 channels input and 1 channel output");
-                 return ST_EOF;
-             }
-         case MIX_RIGHT_BACK:
-             if (ichan == 4 && ochan == 1)
-             {
-                 pans[0] = 0.0;
-                 pans[1] = 0.0;
-                 pans[2] = 0.0;
-                 pans[3] = 1.0;
-                 avg->num_pans = 4;
-             }
-             else
-             {
-                 st_fail("avg: -4 option requires 4 channels input and 1 channel output");
-                 return ST_EOF;
-             }
-
-         case MIX_SPECIFIED:
-             break;
-         default:
-             st_fail("Unknown mix option in average effect");
-             return ST_EOF;
-     }
-
-     /* If number of pans is 4 or less then its a shorthand
-      * representation.  If user specified it, then we have
-      * garbage in our sources[][] array.  Need to clear that
-      * now that all data is stored in pans[] array.
-      */
-     if (avg->num_pans <= 4)
-     {
-         for (i = 0; i < ichan; i++)
-         {
-             for (j = 0; j < ochan; j++) 
-             {
-                 avg->sources[i][j] = 0;
-             }
-         }
-     }
-
-     /* If the number of pans given is 4 or fewer, handle the special */
-     /* cases listed in the comments above.  The code is lengthy but */
-     /* straightforward. */
-     if (avg->num_pans == 0) {
-         /* CASE 1 */
-         if (ichan == 1 && ochan > ichan) {
-             avg->sources[0][0] = 1.0;
-             avg->sources[0][1] = 1.0;
-             avg->sources[0][2] = 1.0;
-             avg->sources[0][3] = 1.0;
-         }
-         /* CASE 2 */
-         else if (ichan == 2 && ochan == 1) {
-             avg->sources[0][0] = 0.5;
-             avg->sources[1][0] = 0.5;
-         }
-         /* CASE 5 */
-         else if (ichan == 2 && ochan == 4) {
-             avg->sources[0][0] = 1.0;
-             avg->sources[0][2] = 1.0;
-             avg->sources[1][1] = 1.0;
-             avg->sources[1][3] = 1.0;
-         }
-         /* CASE 6 */
-         else if (ichan == 4 && ochan == 1) {
-             avg->sources[0][0] = 0.25;
-             avg->sources[1][0] = 0.25;
-             avg->sources[2][0] = 0.25;
-             avg->sources[3][0] = 0.25;
-         }
-         /* CASE 7 */
-         else if (ichan == 4 && ochan == 2) {
-             avg->sources[0][0] = 0.5;
-             avg->sources[1][1] = 0.5;
-             avg->sources[2][0] = 0.5;
-             avg->sources[3][1] = 0.5;
-         }
-         else {
-             st_fail("You must specify at least one mix level when using avg with an unusual number of channels.");
-             return(ST_EOF);
-         }
-     }
-     else if (avg->num_pans == 1) {
-         /* Might be volume change or balance change */
-         /* CASE 3 and CASE 8 */
-         if ((ichan == 2 || ichan == 4) &&  ichan == ochan) {
-             /* -1 is left only, 1 is right only */
-             if (pans[0] <= 0.0) {
-                 avg->sources[1][1] = pans[0] + 1.0;
-                 if (avg->sources[1][1] < 0.0)
-                     avg->sources[1][1] = 0.0;
-                 avg->sources[0][0] = 1.0;
-             }
-             else {
-                 avg->sources[0][0] = 1.0 - pans[0];
-                 if (avg->sources[0][0] < 0.0)
-                     avg->sources[0][0] = 0.0;
-                 avg->sources[1][1] = 1.0;
-             }
-             if (ichan == 4) {
-                 avg->sources[2][2] = avg->sources[0][0];
-                 avg->sources[3][3] = avg->sources[1][1];
-             }
-         }
-         else
-         {
-             st_fail("Invalid options specified to avg while not mixing");
-             return ST_EOF;
-         }
-     }
-     else if (avg->num_pans == 2) {
-         /* CASE 2 */
-         if (ichan == 2 && ochan == 1) {
-             avg->sources[0][0] = pans[0];
-             avg->sources[1][0] = pans[1];
-         }
-         /* CASE 7 */
-         else if (ichan == 4 && ochan == 2) {
-             avg->sources[0][0] = pans[0];
-             avg->sources[1][1] = pans[0];
-             avg->sources[2][0] = pans[1];
-             avg->sources[3][1] = pans[1];
-         }
-         /* CASE 8 */
-         else if (ichan == 4 && ochan == 4) {
-             /* pans[0] is front -> front, pans[1] is for back */
-             avg->sources[0][0] = pans[0];
-             avg->sources[1][1] = pans[0];
-             avg->sources[2][2] = pans[1];
-             avg->sources[3][3] = pans[1];
-         }
-         else
-         {
-             st_fail("Invalid options specified to avg for this channel combination");
-             return ST_EOF;
-         }
-     }
-     else if (avg->num_pans == 4) {
-         /* CASE 4 */
-         if (ichan == 2 && ochan == 2) {
-             /* Shorthand for 2-channel case */
-             avg->sources[0][0] = pans[0];
-             avg->sources[0][1] = pans[1];
-             avg->sources[1][0] = pans[2];
-             avg->sources[1][1] = pans[3];
-         }
-         /* CASE 6 */
-         else if (ichan == 4 && ochan == 1) {
-             avg->sources[0][0] = pans[0];
-             avg->sources[1][0] = pans[1];
-             avg->sources[2][0] = pans[2];
-             avg->sources[3][0] = pans[3];
-         }
-         else
-         {
-             st_fail("Invalid options specified to avg for this channel combination");
-             return ST_EOF;
-         }
-     }
-     else
-     {
-         st_fail("Invalid options specified to avg while not mixing");
-         return ST_EOF;
-     }
-
-     return ST_SUCCESS;
-}
-
-/*
- * Process either isamp or osamp samples, whichever is smaller.
- */
-
-static int st_avg_flow(eff_t effp, const st_sample_t *ibuf, st_sample_t *obuf, 
-                st_size_t *isamp, st_size_t *osamp)
-{
-    avg_t avg = (avg_t) effp->priv;
-    st_size_t len, done;
-    int ichan, ochan;
-    int i, j;
-    double samp;
-
-    ichan = effp->ininfo.channels;
-    ochan = effp->outinfo.channels;
-    len = *isamp / ichan;
-    if (len > *osamp / ochan)
-        len = *osamp / ochan;
-    for (done = 0; done < len; done++, ibuf += ichan, obuf += ochan) {
-        for (j = 0; j < ochan; j++) {
-            samp = 0.0;
-            for (i = 0; i < ichan; i++)
-                samp += ibuf[i] * avg->sources[i][j];
-            ST_SAMPLE_CLIP_COUNT(samp, effp->clips);
-            obuf[j] = samp;
-        }
-    }
-    *isamp = len * ichan;
-    *osamp = len * ochan;
-    return (ST_SUCCESS);
-}
-
-/*
- * Do anything required when you stop reading samples.  
- * Don't close input file! 
- *
- * Should have statistics on right, left, and output amplitudes.
- */
-static int st_avg_stop(eff_t effp UNUSED)
-{
-    return (ST_SUCCESS); /* nothing to do */
-}
-
-static st_effect_t st_avg_effect = {
-  "avg",
-  "Usage: avg [ -l | -r | -f | -b | -1 | -2 | -3 | -4 | n,n,n...,n ]",
-  ST_EFF_MCHAN | ST_EFF_CHAN,
-  st_avg_getopts,
-  st_avg_start,
-  st_avg_flow,
-  st_effect_nothing_drain,
-  st_avg_stop,
-  st_effect_nothing
-};
-
-static st_effect_t st_pick_effect = {
-  "pick",
-  "Usage: pick [ -l | -r | -f | -b | -1 | -2 | -3 | -4 | n,n,n...,n ]",
-  ST_EFF_MCHAN | ST_EFF_CHAN,
-  st_avg_getopts,
-  st_avg_start,
-  st_avg_flow,
-  st_effect_nothing_drain,
-  st_avg_stop,
-  st_effect_nothing
-};
-
-const st_effect_t *st_avg_effect_fn(void)
-{
-    return &st_avg_effect;
-}
-
-const st_effect_t *st_pick_effect_fn(void)
-{
-    return &st_pick_effect;
-}
--- a/src/biquad.h
+++ b/src/biquad.h
@@ -81,16 +81,6 @@
 int st_biquad_flow(eff_t effp, const st_sample_t *ibuf, st_sample_t *obuf, 
                         st_size_t *isamp, st_size_t *osamp);
 
-#define BIQUAD_EFFECT(name,group,usage) \
-st_effect_t const * st_##name##_effect_fn(void) { \
-  static st_effect_t driver = { \
-    #name, "Usage: " #name " " usage, 0, \
-    group##_getopts, start, st_biquad_flow, \
-    st_effect_nothing_drain, st_effect_nothing, st_effect_nothing \
-  }; \
-  return &driver; \
-}
-
 #undef st_fail
 #define st_fail st_message_filename=effp->name,st_fail
 
--- a/src/biquads.c
+++ b/src/biquads.c
@@ -299,15 +299,24 @@
 }
 
 
-BIQUAD_EFFECT(highp,     hilo1,    "cutoff-frequency")
-BIQUAD_EFFECT(lowp,      hilo1,    "cutoff-frequency")
-BIQUAD_EFFECT(highpass,  hilo2,    "[-1|-2] frequency [width[q|o|h]]")
-BIQUAD_EFFECT(lowpass,   hilo2,    "[-1|-2] frequency [width[q|o|h]]")
-BIQUAD_EFFECT(bandpass,  bandpass, "[-c] frequency width[h|q|o]")
-BIQUAD_EFFECT(bandreject,bandrej,  "frequency width[h|q|o]")
-BIQUAD_EFFECT(allpass,   allpass,  "frequency width[h|q|o]")
-BIQUAD_EFFECT(bass,      tone,     "gain [frequency [width[s|h|q|o]]]")
-BIQUAD_EFFECT(treble,    tone,     "gain [frequency [width[s|h|q|o]]]")
-BIQUAD_EFFECT(equalizer, equalizer,"frequency width[q|o|h] gain")
-BIQUAD_EFFECT(band,      band,     "[-n] center [width[h|q|o]]")
-BIQUAD_EFFECT(deemph,    deemph,   "takes no options")
+#define BIQUAD_EFFECT(name,group,usage,flags) \
+st_effect_t const * st_##name##_effect_fn(void) { \
+  static st_effect_t driver = { \
+    #name, "Usage: " #name " " usage, flags, \
+    group##_getopts, start, st_biquad_flow, 0, 0, 0, \
+  }; \
+  return &driver; \
+}
+
+BIQUAD_EFFECT(highp,     hilo1,    "cutoff-frequency", ST_EFF_DEPRECATED)
+BIQUAD_EFFECT(lowp,      hilo1,    "cutoff-frequency", ST_EFF_DEPRECATED)
+BIQUAD_EFFECT(highpass,  hilo2,    "[-1|-2] frequency [width[q|o|h]]", 0)
+BIQUAD_EFFECT(lowpass,   hilo2,    "[-1|-2] frequency [width[q|o|h]]", 0)
+BIQUAD_EFFECT(bandpass,  bandpass, "[-c] frequency width[h|q|o]", 0)
+BIQUAD_EFFECT(bandreject,bandrej,  "frequency width[h|q|o]", 0)
+BIQUAD_EFFECT(allpass,   allpass,  "frequency width[h|q|o]", 0)
+BIQUAD_EFFECT(bass,      tone,     "gain [frequency [width[s|h|q|o]]]", 0)
+BIQUAD_EFFECT(treble,    tone,     "gain [frequency [width[s|h|q|o]]]", 0)
+BIQUAD_EFFECT(equalizer, equalizer,"frequency width[q|o|h] gain", 0)
+BIQUAD_EFFECT(band,      band,     "[-n] center [width[h|q|o]]", 0)
+BIQUAD_EFFECT(deemph,    deemph,   "takes no options", 0)
--- /dev/null
+++ b/src/dither.c
@@ -1,0 +1,108 @@
+/*
+ * Sound Tools dithering noise effect file.
+ *
+ * July 5, 1991
+ * Copyright 1991 Lance Norskog And Sundry Contributors
+ * This source code is freely redistributable and may be used for
+ * any purpose.  This copyright notice must be maintained. 
+ * Lance Norskog And Sundry Contributors are not responsible for 
+ * the consequences of using this software.
+ *
+ * TODO: does triangular noise, could do local shaping
+ *
+ */
+
+#include <stdlib.h>
+#include <math.h>
+#include "st_i.h"
+
+typedef struct dither {
+  double amount;
+} * dither_t;
+
+assert_static(sizeof(struct dither) <= ST_MAX_EFFECT_PRIVSIZE,
+              /* else */ dither_PRIVSIZE_too_big);
+
+static int getopts(eff_t effp, int n, char * * argv)
+{
+  dither_t dither = (dither_t) effp->priv;
+
+  if (n > 1) {
+    st_fail(effp->h->usage);
+    return ST_EOF;
+  }
+  
+  dither->amount = sqrt(2); /* M_SQRT2 missing in some places */   /* Default to half a bit. */
+  if (n == 1) {
+    double amount;
+    char dummy;
+    int scanned = sscanf(*argv, "%lf %c", &amount, &dummy);
+    if (scanned == 1 && amount > 0)
+      dither->amount *= amount;
+    else {
+      st_fail(effp->h->usage);
+      return ST_EOF;
+    }
+  }
+
+  return ST_SUCCESS;
+}
+
+static int start(eff_t effp)
+{
+  dither_t dither = (dither_t) effp->priv;
+
+  if (effp->outinfo.encoding == ST_ENCODING_ULAW ||
+      effp->outinfo.encoding == ST_ENCODING_ALAW) {
+    dither->amount *= 16;
+    return ST_SUCCESS;
+  } else if (effp->outinfo.size == ST_SIZE_BYTE) {
+    dither->amount *= 256;
+    return ST_SUCCESS;
+  } else if (effp->outinfo.size == ST_SIZE_16BIT)
+    return ST_SUCCESS;
+  else if (effp->outinfo.size == ST_SIZE_24BIT) {
+    dither->amount /= 256;
+    return ST_SUCCESS;
+  } else if (effp->outinfo.size == ST_SIZE_64BIT) {
+    dither->amount /= 16384;
+    return ST_SUCCESS;
+  }
+
+  st_fail("Invalid size %d", effp->outinfo.size);
+  return ST_EOF;
+}
+
+/* FIXME: Scale noise more sensibly for sizes >= 24 bits */
+static int flow(eff_t effp, const st_sample_t * ibuf,
+    st_sample_t * obuf, st_size_t * isamp, st_size_t * osamp)
+{
+  dither_t dither = (dither_t)effp->priv;
+  st_size_t len = min(*isamp, *osamp);
+
+  *isamp = *osamp = len;
+  while (len--) {             /* 16 signed bits of triangular noise */
+    int tri16 = ((rand() % 32768L) + (rand() % 32768L)) - 32767;
+    double l = *ibuf++ + tri16 * dither->amount;
+    *obuf++ = ST_ROUND_CLIP_COUNT(l, effp->clips);
+  }
+  return ST_SUCCESS;
+}
+
+st_effect_t const * st_dither_effect_fn(void)
+{
+  static st_effect_t driver = {
+    "dither", "Usage: dither [amount]", ST_EFF_MCHAN,
+    getopts, start, flow, 0, 0, 0
+  };
+  return &driver;
+}
+
+st_effect_t const * st_mask_effect_fn(void)
+{
+  static st_effect_t driver = {
+    "mask", "Usage: mask [amount]", ST_EFF_MCHAN | ST_EFF_DEPRECATED,
+    getopts, start, flow, 0, 0, 0
+  };
+  return &driver;
+}
--- a/src/handlers.c
+++ b/src/handlers.c
@@ -114,6 +114,7 @@
   st_lua_effect_fn,
   st_mask_effect_fn,
   st_mcompand_effect_fn,
+  st_mixer_effect_fn,
   st_noiseprof_effect_fn,
   st_noisered_effect_fn,
   st_pad_effect_fn,
--- a/src/mask.c
+++ /dev/null
@@ -1,124 +1,0 @@
-/*
- * Sound Tools masking noise effect file.
- *
- * July 5, 1991
- * Copyright 1991 Lance Norskog And Sundry Contributors
- * This source code is freely redistributable and may be used for
- * any purpose.  This copyright notice must be maintained. 
- * Lance Norskog And Sundry Contributors are not responsible for 
- * the consequences of using this software.
- *
- * TODO: does triangular noise, could do local shaping
- *
- */
-
-#include <stdlib.h>
-#include <math.h>
-#include "st_i.h"
-
-typedef struct mask {
-  double amount;
-} * mask_t;
-
-assert_static(sizeof(struct mask) <= ST_MAX_EFFECT_PRIVSIZE,
-              /* else */ mask_PRIVSIZE_too_big);
-
-static int st_mask_getopts(eff_t effp, int n, char * * argv)
-{
-  mask_t mask = (mask_t) effp->priv;
-
-  if (n > 1) {
-    st_fail(effp->h->usage);
-    return ST_EOF;
-  }
-  
-  mask->amount = sqrt(2); /* M_SQRT2 missing in some places */   /* Default to half a bit. */
-  if (n == 1) {
-    double amount;
-    char dummy;
-    int scanned = sscanf(*argv, "%lf %c", &amount, &dummy);
-    if (scanned == 1 && amount > 0)
-      mask->amount *= amount;
-    else {
-      st_fail(effp->h->usage);
-      return ST_EOF;
-    }
-  }
-
-  return ST_SUCCESS;
-}
-
-static int st_mask_start(eff_t effp)
-{
-  mask_t mask = (mask_t) effp->priv;
-
-  if (effp->outinfo.encoding == ST_ENCODING_ULAW ||
-      effp->outinfo.encoding == ST_ENCODING_ALAW) {
-    mask->amount *= 16;
-    return ST_SUCCESS;
-  } else if (effp->outinfo.size == ST_SIZE_BYTE) {
-    mask->amount *= 256;
-    return ST_SUCCESS;
-  } else if (effp->outinfo.size == ST_SIZE_16BIT)
-    return ST_SUCCESS;
-  else if (effp->outinfo.size == ST_SIZE_24BIT) {
-    mask->amount /= 256;
-    return ST_SUCCESS;
-  } else if (effp->outinfo.size == ST_SIZE_64BIT) {
-    mask->amount /= 16384;
-    return ST_SUCCESS;
-  }
-
-  st_fail("Invalid size %d", effp->outinfo.size);
-  return ST_EOF;
-}
-
-/* FIXME: Scale noise more sensibly for sizes >= 24 bits */
-static int st_mask_flow(eff_t effp, const st_sample_t * ibuf,
-    st_sample_t * obuf, st_size_t * isamp, st_size_t * osamp)
-{
-  mask_t mask = (mask_t)effp->priv;
-  st_size_t len = min(*isamp, *osamp);
-
-  *isamp = *osamp = len;
-  while (len--) {             /* 16 signed bits of triangular noise */
-    int tri16 = ((rand() % 32768L) + (rand() % 32768L)) - 32767;
-    double l = *ibuf++ + tri16 * mask->amount;
-    *obuf++ = ST_ROUND_CLIP_COUNT(l, effp->clips);
-  }
-  return ST_SUCCESS;
-}
-
-static st_effect_t st_mask_effect = {
-  "mask",
-  "Usage: mask [amount]",
-  ST_EFF_MCHAN,
-  st_mask_getopts,
-  st_effect_nothing,
-  st_mask_flow,
-  st_effect_nothing_drain,
-  st_effect_nothing,
-  st_effect_nothing
-};
-
-st_effect_t const * st_mask_effect_fn(void)
-{
-  return &st_mask_effect;
-}
-
-static st_effect_t st_dither_effect = {
-  "dither",
-  "Usage: dither [amount]",
-  ST_EFF_MCHAN,
-  st_mask_getopts,
-  st_mask_start,
-  st_mask_flow,
-  st_effect_nothing_drain,
-  st_effect_nothing,
-  st_effect_nothing
-};
-
-st_effect_t const * st_dither_effect_fn(void)
-{
-  return &st_dither_effect;
-}
--- /dev/null
+++ b/src/mixer.c
@@ -1,0 +1,569 @@
+/*
+ * July 5, 1991
+ * Copyright 1991 Lance Norskog And Sundry Contributors
+ * This source code is freely redistributable and may be used for
+ * any purpose.  This copyright notice must be maintained. 
+ * Lance Norskog And Sundry Contributors are not responsible for 
+ * the consequences of using this software.
+ *
+ * Channel duplication code by Graeme W. Gill - 93/5/18
+ * General-purpose panning by Geoffrey H. Kuenning -- 2000/11/28
+ */
+
+/*
+ * Sound Tools stereo/quad -> mono mixdown effect file.
+ * and mono/stereo -> stereo/quad channel duplication.
+ */
+
+#include "st_i.h"
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+
+static st_effect_t st_mixer_effect;
+
+typedef struct mixerstuff {
+        /* How to generate each output channel.  sources[i][j] */
+        /* represents the fraction of channel i that should be passed */
+        /* through to channel j on output, and so forth.  Channel 0 is */
+        /* left front, channel 1 is right front, and 2 and 3 are left */
+        /* and right rear, respectively. (GHK) */
+        double  sources[4][4];
+        int     num_pans;
+        int     mix;                    /* How are we mixing it? */
+} *mixer_t;
+
+/* MIX_CENTER is shorthand to mix channels together at 50% each */
+#define MIX_CENTER      0
+#define MIX_LEFT        1
+#define MIX_RIGHT       2
+#define MIX_FRONT       3
+#define MIX_BACK        4
+#define MIX_SPECIFIED   5
+#define MIX_LEFT_FRONT  6
+#define MIX_RIGHT_FRONT 7
+#define MIX_LEFT_BACK   8
+#define MIX_RIGHT_BACK  9
+
+
+/*
+ * Process options
+ */
+static int getopts(eff_t effp, int n, char **argv) 
+{
+    mixer_t mixer = (mixer_t) effp->priv;
+    double* pans = &mixer->sources[0][0];
+    int i;
+
+    for (i = 0;  i < 16;  i++)
+        pans[i] = 0.0;
+    mixer->mix = MIX_CENTER;
+    mixer->num_pans = 0;
+
+    /* Parse parameters.  Since we don't yet know the number of */
+    /* input and output channels, we'll record the information for */
+    /* later. */
+    if (n == 1) {
+        if(!strcmp(argv[0], "-l"))
+            mixer->mix = MIX_LEFT;
+        else if (!strcmp(argv[0], "-r"))
+            mixer->mix = MIX_RIGHT;
+        else if (!strcmp(argv[0], "-f"))
+            mixer->mix = MIX_FRONT;
+        else if (!strcmp(argv[0], "-b"))
+            mixer->mix = MIX_BACK;
+        else if (!strcmp(argv[0], "-1"))
+            mixer->mix = MIX_LEFT_FRONT;
+        else if (!strcmp(argv[0], "-2"))
+            mixer->mix = MIX_RIGHT_FRONT;
+        else if (!strcmp(argv[0], "-3"))
+            mixer->mix = MIX_LEFT_BACK;
+        else if (!strcmp(argv[0], "-4"))
+            mixer->mix = MIX_RIGHT_BACK;
+        else if (argv[0][0] == '-' && !isdigit((int)argv[0][1])
+                && argv[0][1] != '.') {
+            st_fail(st_mixer_effect.usage);
+            return (ST_EOF);
+        }
+        else {
+            int commas;
+            char *s;
+            mixer->mix = MIX_SPECIFIED;
+            pans[0] = atof(argv[0]);
+            for (s = argv[0], commas = 0; *s; ++s) {
+                if (*s == ',') {
+                    ++commas;
+                    if (commas >= 16) {
+                        st_fail("mixer can only take up to 16 pan values");
+                        return (ST_EOF);
+                    }
+                    pans[commas] = atof(s+1);
+                }
+            }
+            mixer->num_pans = commas + 1;
+        }
+    }
+    else if (n == 0) {
+        mixer->mix = MIX_CENTER;
+    }
+    else {
+        st_fail(st_mixer_effect.usage);
+        return ST_EOF;
+    }
+
+    return (ST_SUCCESS);
+}
+
+/*
+ * Start processing
+ */
+static int start(eff_t effp)
+{
+    /*
+       Hmmm, this is tricky.  Lemme think:
+       channel orders are [0][0],[0][1], etc.
+       i.e., 0->0, 0->1, 0->2, 0->3, 1->0, 1->1, ...
+       trailing zeros are omitted
+       L/R balance is x= -1 for left only, 1 for right only
+       1->1 channel effects:
+       changing volume by x is x,0,0,0
+       1->2 channel effects:
+       duplicating everywhere is 1,1,0,0
+       1->4 channel effects:
+       duplicating everywhere is 1,1,1,1
+       2->1 channel effects:
+       left only is 1,0,0,0 0,0,0,0
+       right only is 0,0,0,0 1,0,0,0
+       left+right is 0.5,0,0,0 0.5,0,0,0
+       left-right is 1,0,0,0 -1,0,0,0
+       2->2 channel effects:
+       L/R balance can be done several ways.  The standard stereo
+       way is both the easiest and the most sensible:
+       min(1-x,1),0,0,0 0,min(1+x,1),0,0
+       left to both is 1,1,0,0
+       right to both is 0,0,0,0 1,1,0,0
+       left+right to both is 0.5,0.5,0,0 0.5,0.5,0,0
+       left-right to both is 1,1,0,0 -1,-1,0,0
+       left-right to left, right-left to right is 1,-1,0,0 -1,1,0,0
+       2->4 channel effects:
+       front duplicated into rear is 1,0,1,0 0,1,0,1
+       front swapped into rear (why?) is 1,0,0,1 0,1,1,0
+       front put into rear as mono (why?) is 1,0,0.5,0.5 0,1,0.5,0.5
+       4->1 channel effects:
+       left front only is 1,0,0,0
+       left only is 0.5,0,0,0 0,0,0,0 0.5,0,0,0
+       etc.
+       4->2 channel effects:
+       merge front/back is 0.5,0,0,0 0,0.5,0,0 0.5,0,0,0 0,0.5,0,0
+       selections similar to above
+       4->4 channel effects:
+       left front to all is 1,1,1,1 0,0,0,0
+       right front to all is 0,0,0,0 1,1,1,1
+       left f/r to all f/r is 1,1,0,0 0,0,0,0 0,0,1,1 0,0,0,0
+       etc.
+
+       The interesting cases from above (deserving of abbreviations of
+       less than 16 numbers) are:
+
+       0) n->n volume change (1 number)
+       1) 1->n duplication (0 numbers)
+       2) 2->1 mixdown (0 or 2 numbers)
+       3) 2->2 balance (1 number)
+       4) 2->2 fully general mix (4 numbers)
+       5) 2->4 duplication (0 numbers)
+       6) 4->1 mixdown (0 or 4 numbers)
+       7) 4->2 mixdown (0, or 2 numbers)
+       8) 4->4 balance (1 or 2 numbers)
+
+       The above has one ambiguity: n->n volume change conflicts with
+       n->n balance for n != 1.  In such a case, we'll prefer
+       balance, since there is already a volume effect in vol.c.
+
+       GHK 2000/11/28
+     */
+     mixer_t mixer = (mixer_t) effp->priv;
+     double pans[16];
+     int i, j;
+     int ichan, ochan;
+
+     for (i = 0;  i < 16;  i++)
+         pans[i] = ((double*)&mixer->sources[0][0])[i];
+
+     ichan = effp->ininfo.channels;
+     ochan = effp->outinfo.channels;
+     if (ochan == -1) {
+         st_fail("Output must have known number of channels to use mixer effect");
+         return(ST_EOF);
+     }
+
+     if ((ichan != 1 && ichan != 2 && ichan != 4)
+             ||  (ochan != 1 && ochan != 2 && ochan != 4)) {
+         st_fail("Can't average %d channels into %d channels",
+                 ichan, ochan);
+         return (ST_EOF);
+     }
+
+     /* Handle the special-case flags */
+     switch (mixer->mix) {
+         case MIX_CENTER:
+             if (ichan == ochan) {
+                 st_fail("Output must have different number of channels to use mixer effect");
+                 return(ST_EOF);
+             }
+             break;             /* Code below will handle this case */
+         case MIX_LEFT:
+             if (ichan == 2 && ochan == 1)
+             {
+                 pans[0] = 1.0;
+                 pans[1] = 0.0;
+                 mixer->num_pans = 2;
+             }
+             else if (ichan == 4 && ochan == 1)
+             {
+                 pans[0] = 0.5;
+                 pans[1] = 0.0;
+                 pans[2] = 0.5;
+                 pans[3] = 0.0;
+                 mixer->num_pans = 4;
+             }
+             else
+             {
+                 st_fail("Can't average %d channels into %d channels",
+                         ichan, ochan);
+                 return ST_EOF;
+             }
+             break;
+         case MIX_RIGHT:
+             if (ichan == 2 && ochan == 1)
+             {
+                 pans[0] = 0.0;
+                 pans[1] = 1.0;
+                 mixer->num_pans = 2;
+             }
+             else if (ichan == 4 && ochan == 1)
+             {
+                 pans[0] = 0.0;
+                 pans[1] = 0.5;
+                 pans[2] = 0.0;
+                 pans[3] = 0.5;
+                 mixer->num_pans = 4;
+             }
+             else
+             {
+                 st_fail("Can't average %d channels into %d channels",
+                         ichan, ochan);
+                 return ST_EOF;
+             }
+             break;
+         case MIX_FRONT:
+             if (ichan == 4 && ochan == 2)
+             {
+                 pans[0] = 1.0;
+                 pans[1] = 0.0;
+                 mixer->num_pans = 2;
+             }
+             else
+             {
+                 st_fail("-f option requires 4 channels input and 2 channel output");
+                 return ST_EOF;
+             }
+             break;
+         case MIX_BACK:
+             if (ichan == 4 && ochan == 2)
+             {
+                 pans[0] = 0.0;
+                 pans[1] = 1.0;
+                 mixer->num_pans = 2;
+             }
+             else
+             {
+                 st_fail("-b option requires 4 channels input and 2 channel output");
+                 return ST_EOF;
+             }
+             break;
+         case MIX_LEFT_FRONT:
+             if (ichan == 2 && ochan == 1)
+             {
+                 pans[0] = 1.0;
+                 pans[1] = 0.0;
+                 mixer->num_pans = 2;
+             }
+             else if (ichan == 4 && ochan == 1)
+             {
+                 pans[0] = 1.0;
+                 pans[1] = 0.0;
+                 pans[2] = 0.0;
+                 pans[3] = 0.0;
+                 mixer->num_pans = 4;
+             }
+             else
+             {
+                 st_fail("-1 option requires 4 channels input and 1 channel output");
+                 return ST_EOF;
+             }
+             break;
+         case MIX_RIGHT_FRONT:
+             if (ichan == 2 && ochan == 1)
+             {
+                 pans[0] = 0.0;
+                 pans[1] = 1.0;
+                 mixer->num_pans = 2;
+             }
+             else if (ichan == 4 && ochan == 1)
+             {
+                 pans[0] = 0.0;
+                 pans[1] = 1.0;
+                 pans[2] = 0.0;
+                 pans[3] = 0.0;
+                 mixer->num_pans = 4;
+             }
+             else
+             {
+                 st_fail("-2 option requires 4 channels input and 1 channel output");
+                 return ST_EOF;
+             }
+             break;
+         case MIX_LEFT_BACK:
+             if (ichan == 4 && ochan == 1)
+             {
+                 pans[0] = 0.0;
+                 pans[1] = 0.0;
+                 pans[2] = 1.0;
+                 pans[3] = 0.0;
+                 mixer->num_pans = 4;
+             }
+             else
+             {
+                 st_fail("-3 option requires 4 channels input and 1 channel output");
+                 return ST_EOF;
+             }
+         case MIX_RIGHT_BACK:
+             if (ichan == 4 && ochan == 1)
+             {
+                 pans[0] = 0.0;
+                 pans[1] = 0.0;
+                 pans[2] = 0.0;
+                 pans[3] = 1.0;
+                 mixer->num_pans = 4;
+             }
+             else
+             {
+                 st_fail("-4 option requires 4 channels input and 1 channel output");
+                 return ST_EOF;
+             }
+
+         case MIX_SPECIFIED:
+             break;
+         default:
+             st_fail("Unknown mix option in average effect");
+             return ST_EOF;
+     }
+
+     /* If number of pans is 4 or less then its a shorthand
+      * representation.  If user specified it, then we have
+      * garbage in our sources[][] array.  Need to clear that
+      * now that all data is stored in pans[] array.
+      */
+     if (mixer->num_pans <= 4)
+     {
+         for (i = 0; i < ichan; i++)
+         {
+             for (j = 0; j < ochan; j++) 
+             {
+                 mixer->sources[i][j] = 0;
+             }
+         }
+     }
+
+     /* If the number of pans given is 4 or fewer, handle the special */
+     /* cases listed in the comments above.  The code is lengthy but */
+     /* straightforward. */
+     if (mixer->num_pans == 0) {
+         /* CASE 1 */
+         if (ichan == 1 && ochan > ichan) {
+             mixer->sources[0][0] = 1.0;
+             mixer->sources[0][1] = 1.0;
+             mixer->sources[0][2] = 1.0;
+             mixer->sources[0][3] = 1.0;
+         }
+         /* CASE 2 */
+         else if (ichan == 2 && ochan == 1) {
+             mixer->sources[0][0] = 0.5;
+             mixer->sources[1][0] = 0.5;
+         }
+         /* CASE 5 */
+         else if (ichan == 2 && ochan == 4) {
+             mixer->sources[0][0] = 1.0;
+             mixer->sources[0][2] = 1.0;
+             mixer->sources[1][1] = 1.0;
+             mixer->sources[1][3] = 1.0;
+         }
+         /* CASE 6 */
+         else if (ichan == 4 && ochan == 1) {
+             mixer->sources[0][0] = 0.25;
+             mixer->sources[1][0] = 0.25;
+             mixer->sources[2][0] = 0.25;
+             mixer->sources[3][0] = 0.25;
+         }
+         /* CASE 7 */
+         else if (ichan == 4 && ochan == 2) {
+             mixer->sources[0][0] = 0.5;
+             mixer->sources[1][1] = 0.5;
+             mixer->sources[2][0] = 0.5;
+             mixer->sources[3][1] = 0.5;
+         }
+         else {
+             st_fail("You must specify at least one mix level when using mixer with an unusual number of channels.");
+             return(ST_EOF);
+         }
+     }
+     else if (mixer->num_pans == 1) {
+         /* Might be volume change or balance change */
+         /* CASE 3 and CASE 8 */
+         if ((ichan == 2 || ichan == 4) &&  ichan == ochan) {
+             /* -1 is left only, 1 is right only */
+             if (pans[0] <= 0.0) {
+                 mixer->sources[1][1] = pans[0] + 1.0;
+                 if (mixer->sources[1][1] < 0.0)
+                     mixer->sources[1][1] = 0.0;
+                 mixer->sources[0][0] = 1.0;
+             }
+             else {
+                 mixer->sources[0][0] = 1.0 - pans[0];
+                 if (mixer->sources[0][0] < 0.0)
+                     mixer->sources[0][0] = 0.0;
+                 mixer->sources[1][1] = 1.0;
+             }
+             if (ichan == 4) {
+                 mixer->sources[2][2] = mixer->sources[0][0];
+                 mixer->sources[3][3] = mixer->sources[1][1];
+             }
+         }
+         else
+         {
+             st_fail("Invalid options specified to mixer while not mixing");
+             return ST_EOF;
+         }
+     }
+     else if (mixer->num_pans == 2) {
+         /* CASE 2 */
+         if (ichan == 2 && ochan == 1) {
+             mixer->sources[0][0] = pans[0];
+             mixer->sources[1][0] = pans[1];
+         }
+         /* CASE 7 */
+         else if (ichan == 4 && ochan == 2) {
+             mixer->sources[0][0] = pans[0];
+             mixer->sources[1][1] = pans[0];
+             mixer->sources[2][0] = pans[1];
+             mixer->sources[3][1] = pans[1];
+         }
+         /* CASE 8 */
+         else if (ichan == 4 && ochan == 4) {
+             /* pans[0] is front -> front, pans[1] is for back */
+             mixer->sources[0][0] = pans[0];
+             mixer->sources[1][1] = pans[0];
+             mixer->sources[2][2] = pans[1];
+             mixer->sources[3][3] = pans[1];
+         }
+         else
+         {
+             st_fail("Invalid options specified to mixer for this channel combination");
+             return ST_EOF;
+         }
+     }
+     else if (mixer->num_pans == 4) {
+         /* CASE 4 */
+         if (ichan == 2 && ochan == 2) {
+             /* Shorthand for 2-channel case */
+             mixer->sources[0][0] = pans[0];
+             mixer->sources[0][1] = pans[1];
+             mixer->sources[1][0] = pans[2];
+             mixer->sources[1][1] = pans[3];
+         }
+         /* CASE 6 */
+         else if (ichan == 4 && ochan == 1) {
+             mixer->sources[0][0] = pans[0];
+             mixer->sources[1][0] = pans[1];
+             mixer->sources[2][0] = pans[2];
+             mixer->sources[3][0] = pans[3];
+         }
+         else
+         {
+             st_fail("Invalid options specified to mixer for this channel combination");
+             return ST_EOF;
+         }
+     }
+     else
+     {
+         st_fail("Invalid options specified to mixer while not mixing");
+         return ST_EOF;
+     }
+
+     return ST_SUCCESS;
+}
+
+/*
+ * Process either isamp or osamp samples, whichever is smaller.
+ */
+
+static int flow(eff_t effp, const st_sample_t *ibuf, st_sample_t *obuf, 
+                st_size_t *isamp, st_size_t *osamp)
+{
+    mixer_t mixer = (mixer_t) effp->priv;
+    st_size_t len, done;
+    int ichan, ochan;
+    int i, j;
+    double samp;
+
+    ichan = effp->ininfo.channels;
+    ochan = effp->outinfo.channels;
+    len = *isamp / ichan;
+    if (len > *osamp / ochan)
+        len = *osamp / ochan;
+    for (done = 0; done < len; done++, ibuf += ichan, obuf += ochan) {
+        for (j = 0; j < ochan; j++) {
+            samp = 0.0;
+            for (i = 0; i < ichan; i++)
+                samp += ibuf[i] * mixer->sources[i][j];
+            ST_SAMPLE_CLIP_COUNT(samp, effp->clips);
+            obuf[j] = samp;
+        }
+    }
+    *isamp = len * ichan;
+    *osamp = len * ochan;
+    return (ST_SUCCESS);
+}
+
+st_effect_t const * st_mixer_effect_fn(void)
+{
+  static st_effect_t driver = {
+    "mixer",
+    "Usage: mixer [ -l | -r | -f | -b | -1 | -2 | -3 | -4 | n,n,n...,n ]",
+    ST_EFF_MCHAN | ST_EFF_CHAN,
+    getopts, start, flow, 0, 0, 0
+  };
+  return &driver;
+}
+
+st_effect_t const * st_avg_effect_fn(void)
+{
+  static st_effect_t driver = {
+    "avg",
+    "Usage: avg [ -l | -r | -f | -b | -1 | -2 | -3 | -4 | n,n,n...,n ]",
+    ST_EFF_MCHAN | ST_EFF_CHAN | ST_EFF_DEPRECATED,
+    getopts, start, flow, 0, 0, 0
+  };
+  return &driver;
+}
+
+st_effect_t const * st_pick_effect_fn(void)
+{
+  static st_effect_t driver = {
+    "pick",
+    "Usage: pick [ -l | -r | -f | -b | -1 | -2 | -3 | -4 | n,n,n...,n ]",
+    ST_EFF_MCHAN | ST_EFF_CHAN | ST_EFF_DEPRECATED,
+    getopts, start, flow, 0, 0, 0
+  };
+  return &driver;
+}
--- a/src/rate.c
+++ b/src/rate.c
@@ -17,7 +17,7 @@
 static st_effect_t st_rate_effect = {
   "rate",
   "Usage: Rate effect takes no options",
-  ST_EFF_RATE,
+  ST_EFF_RATE | ST_EFF_DEPRECATED,
   st_resample_getopts,
   st_resample_start,
   st_resample_flow,
--- a/src/skeleff.c
+++ b/src/skeleff.c
@@ -49,7 +49,6 @@
 /*
  * Prepare processing.
  * Do all initializations.
- * If there's nothing to do, use st_effect_nothing instead.
  */
 static int start(eff_t effp)
 {
@@ -97,7 +96,6 @@
 
 /*
  * Drain out remaining samples if the effect generates any.
- * If there's nothing to do, use st_effect_nothing_drain instead.
  */
 static int drain(eff_t effp, st_sample_t *obuf, st_size_t *osamp)
 {
@@ -111,7 +109,6 @@
 
 /*
  * Do anything required when you stop reading samples.  
- * If there's nothing to do, use st_effect_nothing instead.
  */
 static int stop(eff_t effp)
 {
@@ -121,7 +118,6 @@
 /*
  * Do anything required when you delete an effect.  
  *      (free allocated memory, etc.)
- * If there's nothing to do, use st_effect_nothing instead.
  */
 static int delete(eff_t effp)
 {
@@ -131,8 +127,9 @@
 
 /*
  * Effect descriptor.
- * If one of the methods does nothing, use the relevant
- * st_effect_nothing* method.
+ * If no specific processing is needed for any of
+ * the 6 functions, then the function can be deleted
+ * and 0 used in place of the its name below.
  */
 static st_effect_t st_skel_effect = {
   "skel",
--- a/src/sox.c
+++ b/src/sox.c
@@ -681,7 +681,7 @@
  */
 
 static void process(void) {
-  int e, flowstatus;
+  int e, flowstatus = 0;
   size_t current_input = 0;
   st_size_t s, f;
   st_ssize_t ilen[MAX_INPUT_FILES];
@@ -963,200 +963,130 @@
 static void parse_effects(int argc, char **argv)
 {
   int argc_effect;
-  int effect_rc;
 
-  nuser_effects = 0;
+  for (nuser_effects = 0; optind < argc; ++nuser_effects) {
+    struct st_effect * e = &user_efftab[nuser_effects];
+    int (*getopts)(eff_t effp, int argc, char *argv[]);
 
-  while (optind < argc) {
     if (nuser_effects >= MAX_USER_EFF) {
-      st_fail("too many effects specified (at most %d allowed)", MAX_USER_EFF);
+      st_fail("too many effects specified (at most %i allowed)", MAX_USER_EFF);
       exit(1);
     }
 
-    argc_effect = st_geteffect_opt(&user_efftab[nuser_effects],
-                                   argc - optind, &argv[optind]);
-
+    argc_effect = st_geteffect_opt(e, argc - optind, &argv[optind]);
     if (argc_effect == ST_EOF) {
       st_fail("Effect `%s' does not exist!", argv[optind]);
       exit(1);
     }
 
-    /* Skip past effect name */
-    optind++;
-    
-    user_efftab[nuser_effects].globalinfo = &globalinfo;
-    effect_rc = (*user_efftab[nuser_effects].h->getopts)
-      (&user_efftab[nuser_effects], argc_effect, &argv[optind]);
-    
-    if (effect_rc == ST_EOF) 
+    optind++; /* Skip past effect name */
+    e->globalinfo = &globalinfo;
+    getopts = e->h->getopts?  e->h->getopts : st_effect_nothing_getopts;
+    if (getopts(e, argc_effect, &argv[optind]) == ST_EOF) 
       exit(2);
 
-    /* Skip past the effect arguments */
-    optind += argc_effect;
-    nuser_effects++;
+    optind += argc_effect; /* Skip past the effect arguments */
   }
 }
 
-/*
- * 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 build_effects_table(void)
-{
-  int i;
-  int needchan = 0, needrate = 0, haschan = 0, hasrate = 0;
-  int effects_mask = 0;
-  int status;
+static void add_effect(int * effects_mask)
+{ 
+  struct st_effect * e = &efftab[neffects];
 
-  needrate = (file_desc[0]->signal.rate != ofile->signal.rate);
-  needchan = (file_desc[0]->signal.channels != ofile->signal.channels);
+  /* Copy format info to effect table */
+  *effects_mask = 
+    st_updateeffect(e, &file_desc[0]->signal, &ofile->signal, *effects_mask);
 
-  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 this effect can't handle multiple channels then account for this. */
+  if (e->ininfo.channels > 1 && !(e->h->flags & ST_EFF_MCHAN))
+    memcpy(&efftabR[neffects], e, sizeof(*e));
 
-  if (haschan > 1) {
-    st_fail("Cannot specify multiple effects that modify number of channels");
-    exit(2);
-  }
-  if (hasrate > 1)
-    st_report("Cannot specify multiple effects that change sample rate");
+  st_report("Effects chain:%10s %-6s %uHz", e->name,
+      e->ininfo.channels < 2? "mono" :
+      (e->h->flags & ST_EFF_MCHAN)? "multi" : "stereo", e->ininfo.rate);
 
-  /* --------- add the effects ------------------------ */
+  ++neffects;
+}
 
-  /* efftab[0] is always the input stream and always exists */
-  neffects = 1;
+static void add_default_effect(char const * name, int * effects_mask)
+{ 
+  struct st_effect * e = &efftab[neffects];
+  int (*getopts)(eff_t effp, int argc, char *argv[]);
 
-  /* If reducing channels then its faster to run all effects     
-   * after the avg effect.      
-   */   
-  if (needchan && !(haschan) &&
-      (file_desc[0]->signal.channels > ofile->signal.channels))
-  { 
-      /* Find effect and update initial pointers */
-      st_geteffect(&efftab[neffects], "avg");
+  /* Find effect and update initial pointers */
+  st_geteffect(e, name);
 
-      /* give default opts for added effects */
-      efftab[neffects].globalinfo = &globalinfo;
-      status = (* efftab[neffects].h->getopts)(&efftab[neffects], (int)0,
-                                               (char **)0);
+  /* Set up & give default opts for added effects */ 
+  e->globalinfo = &globalinfo;
+  getopts = e->h->getopts?  e->h->getopts : st_effect_nothing_getopts;
+  if (getopts(e, 0, NULL) == ST_EOF)
+    exit(2);
 
-      if (status == ST_EOF)
-          exit(2);
+  add_effect(effects_mask);
+}
 
-      /* Copy format info to effect table */
-      effects_mask = st_updateeffect(&efftab[neffects], 
-                                     &file_desc[0]->signal,
-                                     &ofile->signal,
-                                     effects_mask);
+/* If needed effects are not given, auto-add at (performance) optimal point.
+ */
+static void build_effects_table(void)
+{
+  int i;
+  int effects_mask = 0;
+  st_bool need_rate = file_desc[0]->signal.rate     != ofile->signal.rate;
+  st_bool need_chan = file_desc[0]->signal.channels != ofile->signal.channels;
 
-      neffects++;
-  }
+  { /* Check if we have to add effects to change rate/chans or if the
+       user has specified effects to do this, in which case, check if
+       too many rate/channel-changing effects have been specified:     */
+    int user_chan_effects = 0, user_rate_effects = 0;
 
-  /* If reducing the number of samples, it's faster to run all effects 
-   * after the resample effect. 
-   */
-  if (needrate && !(hasrate) &&
-      (file_desc[0]->signal.rate > ofile->signal.rate)) 
-  {
-      st_geteffect(&efftab[neffects], "resample");
-
-      /* set up & give default opts for added effects */ 
-      efftab[neffects].globalinfo = &globalinfo;
-      status = (* efftab[neffects].h->getopts)(&efftab[neffects], (int)0,
-                                               (char **)0);
-
-      if (status == ST_EOF)
-          exit(2);
-
-      /* Copy format info to effect table */ 
-      effects_mask = st_updateeffect(&efftab[neffects],
-                                     &file_desc[0]->signal,
-                                     &ofile->signal,
-                                     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]->signal,
-                                   &ofile->signal, 
-                                   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 it's faster to run
-   * rate on less channels then more.
-   */
-  if (needrate && !(effects_mask & ST_EFF_RATE)) {
-    st_geteffect(&efftab[neffects], "resample");
-
-    /* Set up & give default opts for added effect */
-    efftab[neffects].globalinfo = &globalinfo;
-    status = (* efftab[neffects].h->getopts)(&efftab[neffects], 0, NULL);
-
-    if (status == ST_EOF)
+    for (i = 0; i < nuser_effects; i++) {
+      if (user_efftab[i].h->flags & ST_EFF_CHAN) {
+        need_chan = st_false;
+        ++user_chan_effects;
+      }
+      if (user_efftab[i].h->flags & ST_EFF_RATE) {
+        need_rate = st_false;
+        ++user_rate_effects;
+      }
+    }
+    if (user_chan_effects > 1) {
+      st_fail("Cannot specify multiple effects that change number of channels");
       exit(2);
-
-    /* Copy format info to effect table */
-    effects_mask = st_updateeffect(&efftab[neffects], 
-                                   &file_desc[0]->signal,
-                                   &ofile->signal, 
-                                   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 (user_rate_effects > 1)
+      st_report("Cannot specify multiple effects that change sample rate");
+      /* FIXME: exit here or add comment as to why not */
   }
 
-  /* If we haven't added avg effect yet then do it now.
-   */
-  if (needchan && !(effects_mask & ST_EFF_CHAN)) {
-    st_geteffect(&efftab[neffects], "avg");
+  /* --------- add the effects ------------------------ */
 
-    /* set up & give default opts for added effects */
-    efftab[neffects].globalinfo = &globalinfo;
-    status = (* efftab[neffects].h->getopts)(&efftab[neffects], 0, NULL);
-    if (status == ST_EOF)
-      exit(2);
+  /* efftab[0] is always the input stream and always exists */
+  neffects = 1;
 
-    /* Copy format info to effect table */
-    effects_mask = st_updateeffect(&efftab[neffects], 
-                                   &file_desc[0]->signal,
-                                   &ofile->signal, 
-                                   effects_mask);
-    
-    neffects++;
+  /* If reducing channels, it's faster to do so before all other effects: */
+  if (need_chan && file_desc[0]->signal.channels > ofile->signal.channels) {
+    add_default_effect("mixer", &effects_mask);
+    need_chan = st_false;
   }
+  /* If reducing rate, it's faster to do so before all other effects
+   * (except reducing channels): */
+  if (need_rate && file_desc[0]->signal.rate > ofile->signal.rate) {
+    add_default_effect("resample", &effects_mask);
+    need_rate = st_false;
+  }
+  /* Copy user specified effects into the real efftab */
+  for (i = 0; i < nuser_effects; i++) {
+    memcpy(&efftab[neffects], &user_efftab[i], sizeof(efftab[0]));
+    add_effect(&effects_mask);
+  }
+  /* If rate/channels-changing effects are needed but haven't yet been
+   * added, then do it here.  Change rate before channels because it's
+   * faster to change rate on a smaller # of channels and # of channels
+   * can not be reduced, only increased, at this point. */
+  if (need_rate)
+    add_default_effect("resample", &effects_mask);
+  if (need_chan)
+    add_default_effect("mixer", &effects_mask);
 }
 
 static int start_all_effects(void)
@@ -1164,16 +1094,17 @@
   int e, ret = ST_SUCCESS;
 
   for (e = 1; e < neffects; e++) {
+    int (*start)(eff_t effp) = 
+       efftab[e].h->start? efftab[e].h->start : st_effect_nothing;
     efftab[e].clips = 0;
-    if ((ret = (*efftab[e].h->start)(&efftab[e])) == ST_EOF)
+    if ((ret = start(&efftab[e])) == ST_EOF)
       break;
     if (efftabR[e].name) {
       efftabR[e].clips = 0;
-      if ((ret = (*efftabR[e].h->start)(&efftabR[e])) != ST_SUCCESS)
+      if ((ret = start(&efftabR[e])) != ST_SUCCESS)
         break;
     }
   }
-
   return ret;
 }
 
@@ -1326,6 +1257,8 @@
   const st_sample_t *ibuf;
   st_sample_t *obuf;
   int effstatus, effstatusl, effstatusr;
+  int (*flow)(eff_t, st_sample_t const*, st_sample_t*, st_size_t*, st_size_t*) =
+    efftab[e].h->flow? efftab[e].h->flow : st_effect_nothing_flow;
 
   /* Do not attempt to do any more effect processing during
    * user aborts as we may be stuck in an infinite flow loop.
@@ -1348,11 +1281,11 @@
     st_debug("pre %s idone=%d, odone=%d", efftab[e].name, idone, odone);
     st_debug("pre %s odone1=%d, olen1=%d odone=%d olen=%d", efftab[e].name, efftab[e-1].odone, efftab[e-1].olen, efftab[e].odone, efftab[e].olen); 
 
-    effstatus = (* efftab[e].h->flow)(&efftab[e],
-                                      &efftab[e - 1].obuf[efftab[e - 1].odone],
-                                      &efftab[e].obuf[efftab[e].olen], 
-                                      (st_size_t *)&idone, 
-                                      (st_size_t *)&odone);
+    effstatus = flow(&efftab[e],
+                     &efftab[e - 1].obuf[efftab[e - 1].odone],
+                     &efftab[e].obuf[efftab[e].olen], 
+                     (st_size_t *)&idone, 
+                     (st_size_t *)&odone);
 
     efftab[e - 1].odone += idone;
     /* Don't update efftab[e].odone as we didn't consume data */
@@ -1380,16 +1313,16 @@
     st_debug("pre %s idone=%d, odone=%d", efftab[e].name, idone, odone);
     st_debug("pre %s odone1=%d, olen1=%d odone=%d olen=%d", efftab[e].name, efftab[e - 1].odone, efftab[e - 1].olen, efftab[e].odone, efftab[e].olen); 
     
-    effstatusl = (* efftab[e].h->flow)(&efftab[e],
-                                       ibufl, obufl, (st_size_t *)&idonel, 
-                                       (st_size_t *)&odonel);
+    effstatusl = flow(&efftab[e],
+                      ibufl, obufl, (st_size_t *)&idonel, 
+                      (st_size_t *)&odonel);
     
     /* right */
     idoner = idone / 2;               /* odd-length logic */
     odoner = odone / 2;
-    effstatusr = (* efftabR[e].h->flow)(&efftabR[e],
-                                        ibufr, obufr, (st_size_t *)&idoner, 
-                                        (st_size_t *)&odoner);
+    effstatusr = flow(&efftabR[e],
+                      ibufr, obufr, (st_size_t *)&idoner, 
+                      (st_size_t *)&odoner);
     
     obuf = &efftab[e].obuf[efftab[e].olen];
     /* This loop implies left and right effect will always output
@@ -1453,11 +1386,12 @@
   st_ssize_t i, olen, olenl, olenr;
   st_sample_t *obuf;
   int rc;
+  int (*drain)(eff_t effp, st_sample_t *obuf, st_size_t *osamp) =
+    efftab[e].h->drain? efftab[e].h->drain : st_effect_nothing_drain;
 
   if (! efftabR[e].name) {
     efftab[e].olen = ST_BUFSIZ;
-    rc = (* efftab[e].h->drain)(&efftab[e],efftab[e].obuf,
-                                &efftab[e].olen);
+    rc = drain(&efftab[e],efftab[e].obuf, &efftab[e].olen);
     efftab[e].odone = 0;
   } else {
     int rc_l, rc_r;
@@ -1466,13 +1400,11 @@
 
     /* left */
     olenl = olen/2;
-    rc_l = (* efftab[e].h->drain)(&efftab[e], obufl, 
-                                  (st_size_t *)&olenl);
+    rc_l = drain(&efftab[e], obufl, (st_size_t *)&olenl);
 
     /* right */
     olenr = olen/2;
-    rc_r = (* efftab[e].h->drain)(&efftabR[e], obufr, 
-                                  (st_size_t *)&olenr);
+    rc_r = drain(&efftabR[e], obufr, (st_size_t *)&olenr);
 
     if (rc_l == ST_EOF || rc_r == ST_EOF)
       rc = ST_EOF;
@@ -1499,17 +1431,22 @@
 
   for (e = 1; e < neffects; e++) {
     st_size_t clips;
-    (*efftab[e].h->stop)(&efftab[e]);
+    int (*stop)(eff_t effp) = 
+       efftab[e].h->stop? efftab[e].h->stop : st_effect_nothing;
+    int (*delete)(eff_t effp) = 
+       efftab[e].h->delete? efftab[e].h->delete : st_effect_nothing;
+
+    stop(&efftab[e]);
     clips = efftab[e].clips;
-    (*efftab[e].h->delete)(&efftab[e]);
+    delete(&efftab[e]);
+    
     if (efftabR[e].name) {
-      (*efftabR[e].h->stop)(&efftabR[e]);
+      stop(&efftabR[e]);
       clips += efftab[e].clips;
-      (*efftabR[e].h->delete)(&efftabR[e]);
+      delete(&efftabR[e]);
     }
     if (clips != 0)
-      st_warn("%s clipped %u samples; decrease volume?", efftab[e].name,
-              clips);
+      st_warn("%s clipped %u samples; decrease volume?", efftab[e].name, clips);
   }
 }
 
@@ -1642,7 +1579,7 @@
          "-v, --volume    volume input file volume adjustment factor (real number)\n"
          "\n");
 
-  printf("Supported file formats:");
+  printf("SUPPORTED FILE FORMATS:");
   for (i = 0, formats = 0; st_format_fns[i]; i++) {
     char const * const *names = st_format_fns[i]()->names;
     while (*names++)
@@ -1659,10 +1596,10 @@
     printf(" %s", format_list[i]);
   free(format_list);
 
-  printf("\n\nSupported effects:");
+  printf("\n\nSUPPORTED EFFECTS:");
   for (i = 0; st_effect_fns[i]; i++) {
     e = st_effect_fns[i]();
-    if (e && e->name)
+    if (e && e->name && !(e->flags & ST_EFF_DEPRECATED))
       printf(" %s", e->name);
   }
 
--- a/src/st.h
+++ b/src/st.h
@@ -324,10 +324,11 @@
 extern const char * const st_size_bits_str[];
 extern const char * const st_encodings_str[];
 
-#define ST_EFF_CHAN     1               /* Effect can mix channels up/down */
-#define ST_EFF_RATE     2               /* Effect can alter data rate */
-#define ST_EFF_MCHAN    4               /* Effect can handle multi-channel */
-#define ST_EFF_REPORT   8               /* Effect does not affect the audio */
+#define ST_EFF_CHAN     1           /* Effect can mix channels up/down */
+#define ST_EFF_RATE     2           /* Effect can alter data rate */
+#define ST_EFF_MCHAN    4           /* Effect can handle multi-channel */
+#define ST_EFF_REPORT   8           /* Effect does not affect the audio */
+#define ST_EFF_DEPRECATED 16        /* Effect is living on borrowed time */
 
 /*
  * Handler structure for each effect.
@@ -337,7 +338,7 @@
 
 typedef struct
 {
-    char const *name;                /* effect name */
+    char const *name;               /* effect name */
     char const *usage;
     unsigned int flags;
 
--- a/src/st_i.h
+++ b/src/st_i.h
@@ -340,6 +340,7 @@
 extern const st_effect_t *st_lua_effect_fn(void);
 extern const st_effect_t *st_mask_effect_fn(void);
 extern const st_effect_t *st_mcompand_effect_fn(void);
+extern const st_effect_t *st_mixer_effect_fn(void);
 extern const st_effect_t *st_noiseprof_effect_fn(void);
 extern const st_effect_t *st_noisered_effect_fn(void);
 extern const st_effect_t *st_pad_effect_fn(void);