shithub: sox

Download patch

ref: c1ac6fab84630212cdc0bc0f9bf84c11b1df82a1
parent: 38edf321981b365fe199f9b2607cc7b88e329714
author: cbagwell <cbagwell>
date: Fri Dec 15 12:23:04 EST 2000

Added a set of patches from Geoff Kuenning.  Includes a rewrite of
the average effect, support for new delay option for compander effect,
and bugfixes to the trim and fade effect.

--- a/Changelog
+++ b/Changelog
@@ -4,8 +4,22 @@
 This file contains a list of all changes starting after the release of
 sox-11gamma.
 
+sox-12.17.2
+-----------
+  o Geoff Kuenning rewrote the average effect into a general-purpose
+    parametric mapping from N channels to M channels.
+  o Geoff Kuenning added an optional delay-time parameter to the compander
+    effect to allow companding to effectively operate based on future
+    knowledge.
+  o Geoff Kuenning Added support to fade and trim effect for specifying time
+    in hh:mm:ss.frac format.
+    Fixed a bug that caused integer overflow when large start/stop times
+    were used.
+  o Geoff Kuenning updated play/rec/soxeffect scripts to handle all effects
+    added since 12.17. Spell-checked soxexam.1 file.
+
 sox-12.17.1
----------
+-----------
   o Andreas Kies fixed a bug were we were not detecting correctly
     if an output file was seekable.
   o Fixed a bug in the mask effect introduced in 12.17.  If the libc
--- a/play
+++ b/play
@@ -65,7 +65,7 @@
 # loop over arguments
 while [ $# -ne 0 ]; do
     case "$1" in
-	avg|band|bandreject|chorus|compand|copy|cut|deemph|echo|echos|filter|flanger|highp|lowp|map|mask|pan|phaser|pick|pitch|polyphase|rate|resample|reverb|reverse|speed|split|stat|stretch|vibro|vol)
+	avg|band|bandpass|bandreject|chorus|compand|copy|cut|deemph|earwax|echo|echos|fade|filter|flanger|highp|highpass|lowp|lowpass|map|mask|pan|phaser|pick|pitch|polyphase|rate|resample|reverb|reverse|speed|split|stat|stretch|swap|trim|vibro|vol)
 	    effects="$@"
 	    break
 	    ;;
--- a/sox.1
+++ b/sox.1
@@ -38,7 +38,7 @@
 .P
 .B Effects:
 .br
-    \fBavg\fR [ -l | -r ]
+    \fBavg\fR [ -l | -r | -f | -b | n,n,...,n ]
 .br
     \fBband\fR [ -n ] \fIcenter\fR [ \fIwidth\fR ]
 .br
@@ -54,7 +54,7 @@
 .br
             \fIin-dB1\fR,\fIout-dB1\fR[,\fIin-dB2\fR,\fIout-dB2\fR...]
 .br
-            [ \fIgain\fR ] [ \fIinitial-volume\fR ]
+            [ \fIgain\fR [ \fIinitial-volume\fR [ \fIdelay\fR ] ] ]
 .br
     \fBcopy\fR
 .br
@@ -545,15 +545,35 @@
 Multiple effects may be applied to the audio data by specifying them
 one after another at the end of the command line.
 .TP 10
-avg [ \fI-l\fR | \fI-r\fR ]
+avg [ \fI-l\fR | \fI-r\fR | \fI-f\fR | \fI-b\fR | \fIn,n,...,n\fR ]
 Reduce the number of channels by averaging the samples,
 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
-avg effect and use the \fI-l\fR and \fI-r\fR options to select only
-the left or right channel for the output instead of averaging the
-two channels.
+avg effect and use the \fI-l\fR, \fI-r\fR, \fI-f\fR, or \fI-b\fR
+options to select only
+the left, right, front, or back channel(s) for the output instead of
+averaging the channels.
+The \fI-f\fR and \fI-b\fR options maintain left/right stereo
+separation; use the avg effect twice to select a single channel.
+
+The avg effect can also be invoked with up to 16 double-precision
+numbers, which specify the proportion of each input channel that is
+to be mixed into each output channel.
+In two-channel mode, 4 numbers are given: l->l, l->r, r->l, and r->r,
+respectively.
+In four-channel mode, the first 4 numbers give the proportions for the
+left-front output channel, as follows: lf->lf, rf->lf, lb->lf, and
+rb->rf.
+The next 4 give the right-front output in the same order, then
+left-back and right-back.
+
+It is also possible to use the 16 numbers to expand or reduce the
+channel count; just specify 0 for unused channels.
+Finally, if fewer than 4 numbers are given, certain special
+abbreviations may be
+invoked; see the source code for details.
 .TP 10
 band \fB[ \fI-n \fB] \fIcenter \fB[ \fIwidth\fB ]
 Apply a band-pass filter.
@@ -607,11 +627,12 @@
 .TP 
         \fIin-dB1,out-dB1\fR[,\fIin-dB2,out-dB2\fR...]
 .TP 10
-        [\fIgain\fR] [\fIinitial-volume\fR]
+        [\fIgain\fR [\fIinitial-volume\fR [\fIdelay\fR ] ] ]
 Compand (compress or expand) the dynamic range of a sample.  The
 attack and decay time specify the integration time over which the
 absolute value of the input signal is integrated to determine its
-volume.  Where more than one pair of attack/decay parameters are
+volume; attacks refer to increases in volume and decays refer to
+decreases.  Where more than one pair of attack/decay parameters are
 specified, each channel is treated separately and the number of pairs
 must agree with the number of input channels.  The second parameter is
 a list of points on the compander's transfer function specified in dB
@@ -620,7 +641,9 @@
 not have to be monotonically rising.  The special value \fI-inf\fR may
 be used to indicate that the input volume should be associated output
 volume.  The points \fI-inf,-inf\fR and \fI0,0\fR are assumed; the
-latter may be overridden, but the former may not.  The third
+latter may be overridden, but the former may not.
+
+The third
 (optional) parameter is a postprocessing gain in dB which is applied
 after the compression has taken place; the fourth (optional) parameter
 is an initial volume to be assumed for each channel when the effect
@@ -629,6 +652,13 @@
 levels before the companding action has begun to operate: it is quite
 probable that in such an event, the output would be severely clipped
 while the compander gain properly adjusts itself.
+
+The fifth (optional) parameter is a delay in seconds.
+The input signal is analyzed immediately to control the compander, but
+it is delayed before being fed to the volume adjuster.
+Specifying a delay approximately equal to the attack/decay times
+allows the compander to effectively operate in a "predictive" rather than a
+reactive mode.
 .TP 10
 copy
 Copy the input file to the output file.
@@ -674,7 +704,11 @@
 
 For fade-ins, this starts from the first sample and ramps the volume of the audio from 0 to full volume over \fIfade-in-length\fR seconds.  Specify 0 seconds if no fade-in is wanted.
 
-For fade-outs, the audio data will be trucated at the stop-time and the volume will be ramped from full volume down to 0 starting at \fIfade-out-length\fR seconds before the \fIstop-time\fR.  No fade-out is performed if these options are not specified.
+For fade-outs, the audio data will be truncated at the stop-time and
+the volume will be ramped from full volume down to 0 starting at
+\fIfade-out-length\fR seconds before the \fIstop-time\fR.  No fade-out
+is performed if these options are not specified.  All times can be
+specified in seconds, mm:ss.frac, or hh:mm:ss.frac format.
 
 An optional \fItype\fR can be specified to change the type of envelope.  Choices are q for quarter of a sinewave, h for half a sinewave, t for linear slope, l for logarithmic, and p for inverted parabola.  The default is a linear slope.
 .TP 10
@@ -1037,12 +1071,15 @@
 the \fIstart\fR location is reached.  \fIstart\fR is a floating point number
 that tells the number of seconds to wait before starting.  If you know the
 sample number you would like to start at then the seconds can be obtained
-by multiply (sample # * sample rate).
+by multiplying (sample # * sample rate).
 .br
 The optional \fIlength\fR parameter tells the number of samples to output
 after the \fIstart\fR sample and is used to trim off the back side of the
 audio data.  Using a value of 0 for the \fIstart\fR parameter will allow
 trimming off the back side only.
+.br
+Both \fIstart\fR and \fIlength\fR can also be specified in mm:ss.frac
+or hh:mm:ss.frac format.
 .TP 10
 vibro \fIspeed \fB [ \fIdepth\fB ]
 Add the world-famous Fender Vibro-Champ sound
--- a/soxeffect
+++ b/soxeffect
@@ -25,7 +25,7 @@
   echo "to stdout.  This means that [ fopts ] need to be given so that"
   echo "sox will know what format the audio data is in."
   echo
-  echo "effectname: avg/band/chorus/copy/cut/deemph/echo/echos/flanger/highp/lowp/map/mask/phaser/pick/polyphase/rate/resample/reverb/reverse/split/stat/vibro"
+  echo "effectname: avg/band/bandpass/bandreject/chorus/compand/copy/cut/deemph/earwax/echo/echos/fade/filter/flanger/highp/highpass/lowp/lowpass/map/mask/pan/phaser/pick/pitch/polyphase/rate/resample/reverb/reverse/speed/split/stat/stretch/swap/trim/vibro/vol"
   echo
   echo "fopts: -c channels -h -r rate -t type -v volume -s/-u/-U/-A -b/-w/-l/-f/-d/-D -x"
   echo ""
@@ -76,7 +76,7 @@
 	*sox)
 		exec $SOX $*
 	;;
-	*avg|*band|*chorus|*copy|*cut|*deemph|*echo|*echos|*flanger|*Highp|*lowp|*map|*mask|*Phaser|*pick|*polyphase|*rate|*resample|*reverb|*reverse|*split|*stat|*vibro)
+	*avg|*band|*bandpass|*bandreject|*chorus|*compand|*copy|*cut|*deemph|*earwax|*echo|*echos|*fade|*filter|*flanger|*highp|*highpass|*lowp|*lowpass|*map|*mask|*pan|*phaser|*pick|*pitch|*polyphase|*rate|*resample|*reverb|*reverse|*speed|*split|*stat|*stretch|*swap|*trim|*vibro|*vol)
 		$SOX $volume $fopts - $fopts - $NAME $effectopts
 	;;
 esac
--- a/soxexam.1
+++ b/soxexam.1
@@ -33,7 +33,7 @@
 .P
 When working with headerless files (raw files), you may take advantage of
 they pseudo-file types of .ub, .uw, .sb, .sw, .ul, and .sl.  By using these
-extensions on your filenames you will not have to specify the corrisponding
+extensions on your filenames you will not have to specify the corresponding
 options on the command line.
 .P
 .B Precision
@@ -154,7 +154,7 @@
 sounds. Likewise, for drums, vocals or guitars.)
 .P
 Single effects will be explained and some given parameter settings
-that can be used to understand the theorie by listening to the sound file
+that can be used to understand the theory by listening to the sound file
 with the added effect.
 .P
 Using multiple effects in parallel or in sequel can result either
@@ -163,7 +163,7 @@
 you will feel unsatisfied. Hence, for the first time using effects
 try to compose them as less as possible. We don't regard the
 composition of effects in the examples because to many combinations
-are possible and you really need a very fast maschine and a lot of
+are possible and you really need a very fast machine and a lot of
 memory to play them in real-time.
 .P
 And real-time playing of sounds will speed up learning the parameter
@@ -171,7 +171,7 @@
 .P
 Basically, we will use the "play" front-end of SOX since it is easier
 to listen sounds coming out of the speaker or earphone instead
-of looking at cryptical data in sound files.
+of looking at cryptic data in sound files.
 .P
 For easy listening of file.xxx ( "xxx" is any sound format ):
 .P
@@ -198,14 +198,14 @@
 .P
 Notes:
 .P
-I played all examples in real-time on a Pentium 100 with 32 Mb and 
+I played all examples in real-time on a Pentium 100 with 32 MB and 
 Linux 2.0.30 using a self-recorded sample ( 3:15 min long in "wav"
 format with 44.1 kHz sample rate and stereo 16 bit ). 
 The sample should not contain any of the effects. However,
 if you take any recording of a sound track from radio or tape or cd,
 and it sounds like a live concert or ten people are playing the same
-rhythm with their drums or funky-groves, then take any other sample.
-(Typically, less then four different intruments and no synthesizer
+rhythm with their drums or funky-grooves, then take any other sample.
+(Typically, less then four different instruments and no synthesizer
 in the sample is suitable. Likewise, the combination vocal, drums, bass
 and guitar.)
 .P
@@ -214,7 +214,7 @@
 .B Echo
 .P
 An echo effect can be naturally found in the mountains, standing somewhere
-on a moutain and shouting a single word will result in one or more repetitions
+on a mountain and shouting a single word will result in one or more repetitions
 of the word ( if not, turn a bit around ant try next, or climb to the next
 mountain ).
 .P
@@ -232,7 +232,7 @@
 .BR
 	play file.xxx echo 0.8 0.88 60.0 0.4
 .P
-If the delay is very short then it sound like a (metallic) roboter playing
+If the delay is very short then it sound like a (metallic) robot playing
 music:
 .P
 .BR
@@ -278,13 +278,13 @@
 too.
 .P
 It works like the echo effect with a short delay, but the delay isn't constant.
-The delay is varied using a sinodial or triangular modulation. The modulation
+The delay is varied using a sinusoidal or triangular modulation. The modulation
 depth defines the range the modulated delay is played before or after the
 delay. Hence the delayed sound will sound slower or faster, that is the delayed
 sound tuned around the original one, like in a chorus where some vocal are
 a bit out of tune.
 .P
-The typical delay is around 40ms to 60ms, the speed of the modualtion is best
+The typical delay is around 40ms to 60ms, the speed of the modulation is best
 near 0.25Hz and the modulation depth around 2ms.
 .P
 A single delay will make the sample more overloaded:
@@ -320,7 +320,7 @@
 .BR
 	play file.xxx flanger 0.6 0.87 3.0 0.9 0.5 -s
 .P
-listen carefully between the difference of sinodial and triangular modulation:
+listen carefully between the difference of sinusoidal and triangular modulation:
 .P
 .BR
 	play file.xxx flanger 0.6 0.87 3.0 0.9 0.5 -t
@@ -330,7 +330,7 @@
 .BR
 	play file.xxx flanger 0.8 0.88 3.0 0.4 0.5 -t
 .P
-The drunken loundspeaker system:
+The drunken loudspeaker system:
 .P
 .BR
 	play file.xxx flanger 0.9 0.9 4.0 0.23 1.3 -s
@@ -344,17 +344,17 @@
 the walls.
 .P
 The biggest problem in using the reverb effect is the correct setting of the
-(wall) delays such that the sound is relistic an doesn't sound like music
-playing in a tin or overloaded feedback distroys any illusion of any big hall.
-To help you for much realisitc reverb effects, you should decide first, how
+(wall) delays such that the sound is realistic an doesn't sound like music
+playing in a tin or overloaded feedback destroys any illusion of any big hall.
+To help you for much realistic reverb effects, you should decide first, how
 long the reverb should take place until it is not loud enough to be registered
 by your ears. This is be done by the reverb time "t", in small halls 200ms in
 bigger one 1000ms, if you like. Clearly, the walls of such a hall aren't far
 away, so you should define its setting be given every wall its delay time.
-However, if the wall is to far eway for the reverb time, you won't hear the
-reverb, so the nearest wall will be best "t/4" delay and the farest "t/2".
+However, if the wall is to far away for the reverb time, you won't hear the
+reverb, so the nearest wall will be best "t/4" delay and the farthest "t/2".
 You can try other distances as well, but it won't sound very realistic.
-The walls shouldn't stand to close to each other and not in a multiple interger
+The walls shouldn't stand to close to each other and not in a multiple integer
 distance to each other ( so avoid wall like: 200.0 and 202.0, or something
 like 100.0 and 200.0 ).
 .P
@@ -380,8 +380,8 @@
 	play file.xxx reverb 1.0 600.0 180.0 200.0 220.0 240.0 280.0 300.0
 .P
 If you run out of machine power or memory, then stop as much applications
-as possible ( every interupt will consume a lot of cpu time which for
-bigger halls is absolutely neccessary ).
+as possible ( every interrupt will consume a lot of CPU time which for
+bigger halls is absolutely necessary ).
 .P
 .B Phaser
 .P
@@ -388,10 +388,10 @@
 The phaser effect is like the flanger effect, but it uses a reverb instead of
 an echo and does phase shifting. You'll hear the difference in the examples
 comparing both effects ( simply change the effect name ).
-The delay modulation can be done sinodial or triangular, preferable is the
+The delay modulation can be done sinusoidal or triangular, preferable is the
 later one for multiple instruments playing. For single instrument sounds
-the sinodial phaser effect will give a sharper phasing effect.
-The decay shouln't be to close to 1.0 which will cause dramatic feedback.
+the sinusoidal phaser effect will give a sharper phasing effect.
+The decay shouldn't be to close to 1.0 which will cause dramatic feedback.
 A good range is about 0.5 to 0.1 for the decay.
 .P
 We will take a parameter setting as for the flanger before ( gain-out is
@@ -400,7 +400,7 @@
 .BR
 	play file.xxx phaser 0.8 0.74 3.0 0.4 0.5 -t
 .P
-The drunken loundspeaker system ( now less alkohol ):
+The drunken loudspeaker system ( now less alcohol ):
 .P
 .BR
 	play file.xxx phaser 0.9 0.85 4.0 0.23 1.3 -s
@@ -415,9 +415,43 @@
 .BR
 	play file.xxx phaser 0.6 0.66 3.0 0.6 2.0 -t
 .P
+.B Compander
+.P
+The compander effect allows the dynamic range of a signal to be
+compressed or expanded.
+For most situations, the attack time (response to the music getting
+louder) should be shorter than the decay time because our ears are more
+sensitive to suddenly loud music than to suddenly soft music.
+.P
+For example, suppose you are listening to Strauss' "Also Sprach
+Zarathustra" in a noisy environment such as a car.
+If you turn up the volume enough to hear the soft passages over the
+road noise, the loud sections will be too loud.
+You could try this:
+.P
+.BR
+	play file.xxx compand 0.3,1 -90,-90,-70,-70,-60,-20,0,0 -5 0 0.2
+.P
+The transfer function ("-90,...") says that
+.I very
+soft sounds between -90 and -70 decibels (-90 is about the limit of
+16-bit encoding) will remain unchanged.
+That keeps the compander from boosting the volume on "silent" passages
+such as between movements.
+However, sounds in the range -60 decibels to 0 decibels (maximum
+volume) will be boosted so that the 60-dB dynamic range of the
+original music will be compressed 3-to-1 into a 20-dB range, which is
+wide enough to enjoy the music but narrow enough to get around the
+road noise.
+The -5 dB output gain is needed to avoid clipping (the number is
+inexact, and was derived by experimentation).
+The 0 for the initial volume will work fine for a clip that starts
+with a bit of silence, and the delay of 0.2 has the effect of causing
+the compander to react a bit more quickly to sudden volume changes.
+.P
 .B Other effects ( copy, rate, avg, stat, vibro, lowp, highp, band, reverb )
 .P
-The other effects are simply to use. However, an "easy to use manual" should
+The other effects are simple to use. However, an "easy to use manual" should
 be given here.
 .P
 .B More effects ( to do ! )
@@ -424,13 +458,13 @@
 .P
 There are a lot of effects around like noise gates, compressors, waw-waw,
 stereo effects and so on. They should be implemented making SOX to be more
-useful in sound mixing technics coming together with a great varity of
+useful in sound mixing techniques coming together with a great variety of
 different sound effects.
 .P
-Combining effects be using then in parallel or sequel on different channels
+Combining effects by using them in parallel or sequence on different channels
 needs some easy mechanism which is real-time stable.
 .P
-Really missing, is the changing of the parameters, starting and stoping of
+Really missing, is the changing of the parameters, starting and stopping of
 effects while playing samples in real-time!
 .P
 Good luck and have fun with all the effects!
--- a/src/avg.c
+++ b/src/avg.c
@@ -8,6 +8,7 @@
  * 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
  */
 
 /*
@@ -18,9 +19,17 @@
  */
 
 #include "st.h"
+#include <ctype.h>
 
 /* Private data for SKEL file */
 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;
 
@@ -27,7 +36,14 @@
 #define MIX_CENTER	0
 #define MIX_LEFT	1
 #define MIX_RIGHT	2
+#define MIX_FRONT	3
+#define MIX_BACK	4
+#define MIX_SPECIFIED	5
 
+extern double atof();
+
+#define CLIP_LEVEL	((double)(((unsigned)1 << 31) - 1))
+
 /*
  * Process options
  */
@@ -37,24 +53,54 @@
 char **argv;
 {
 	avg_t avg = (avg_t) effp->priv;
+	double* pans = &avg->sources[0][0];
+	int i;
 
-	/* NOTE :- should probably have a pan option for both */
-	/* 2 -> 1 and 1 -> 2 etc. conversion, rather than just */
-	/* left and right. If 4 channels is to be fully supported, */
-	/* front and back pan is also needed. (GWG) */
-	/* (should  at least have MIX_FRONT and MIX_BACK) */
+	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) {
 		if(!strcmp(argv[0], "-l"))
 			avg->mix = MIX_LEFT;
 		else if (!strcmp(argv[0], "-r"))
 			avg->mix = MIX_RIGHT;
-		else
-		{
-			st_fail("Usage: avg [ -l | -r ]");
+		else if (!strcmp(argv[0], "-f"))
+			avg->mix = MIX_FRONT;
+		else if (!strcmp(argv[0], "-b"))
+			avg->mix = MIX_BACK;
+		else if (argv[0][0] == '-' && !isdigit(argv[0][1])
+		    && argv[0][1] != '.') {
+			st_fail("Usage: avg [ -l | -r | -f | -b | n,n,n...,n ]");
 			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 {
+		pans[0] = 0.5;
+		pans[1] = 0.5;
+		avg->num_pans = 2;
+	}
 	return (ST_SUCCESS);
 }
 
@@ -65,47 +111,240 @@
 int st_avg_start(effp)
 eff_t effp;
 {
-        if ((effp->ininfo.channels == effp->outinfo.channels) ||
-	    effp->outinfo.channels == -1)
-	{
-	    st_fail("Output must have different number of channels to use avg effect");
+	/*
+	    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 ones from above (deserving of abbreviations of
+	    less than 16 numbers) are:
+
+		n->n volume change (1 number)
+		1->n duplication (0 numbers)
+		2->1 mixdown (0 or 2 numbers)
+		2->2 balance (1 number)
+		2->2 fully general mix (4 numbers)
+		2->4 duplication (0 numbers)
+		4->1 mixdown (0 or 4 numbers)
+		4->2 mixdown (0 or 2 numbers)
+		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;
+	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);
 	}
 
-	switch (effp->outinfo.channels) 
-	{
-	case 1: switch (effp->ininfo.channels) 
-		{
-		case 2: 
-		case 4:
-			return (ST_SUCCESS);
-		default:
+	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) {
+				st_fail("Input must have at least two channels to use avg -l");
+				return(ST_EOF);
+			}
+			pans[0] = 1.0;
+			pans[1] = 0.0;
+			avg->num_pans = 2;
 			break;
-		}
-		break;
-	case 2: switch (effp->ininfo.channels) 
-		{
-		case 1:
-		case 4:
-			return (ST_SUCCESS);
-		default:
+		case MIX_RIGHT:
+			if (ichan < 2) {
+				st_fail("Input must have at least two channels to use avg -r");
+				return(ST_EOF);
+			}
+			pans[0] = 0.0;
+			pans[1] = 1.0;
+			avg->num_pans = 2;
 			break;
-		}
-		break;
-	case 4: switch (effp->ininfo.channels) 
-		{
-		case 1:
-		case 2:
-			return (ST_SUCCESS);
+		case MIX_FRONT:
+			if (ichan < 4) {
+				st_fail("Input must have at four channels to use avg -f");
+				return(ST_EOF);
+			}
+			pans[0] = 1.0;
+			pans[1] = 0.0;
+			avg->num_pans = 2;
+			break;
+		case MIX_BACK:
+			if (ichan < 4) {
+				st_fail("Input must have at four channels to use avg -b");
+				return(ST_EOF);
+			}
+			pans[0] = 0.0;
+			pans[1] = 1.0;
+			avg->num_pans = 2;
+			break;
 		default:
 			break;
+	}
+
+	/* 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) {
+		if (ichan == 1) {
+			avg->sources[0][0] = 1.0;
+			avg->sources[0][1] = 1.0;
+			avg->sources[0][2] = 1.0;
+			avg->sources[0][3] = 1.0;
 		}
-	default:
-		break;
-	}	
-	st_fail("Can't average %d channels into %d channels",
-		effp->ininfo.channels, effp->outinfo.channels);
-	return (ST_EOF);
+		else if (ichan == 2 && ochan == 1) {
+			avg->sources[0][0] = 0.5;
+			avg->sources[1][0] = 0.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;
+		}
+		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;
+		}
+		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 */
+		if ((ichan == 2 || ichan == 4) &&  ichan == ochan) {
+			/* -1 is left only, 1 is right only */
+			if (avg->sources[0][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 if (avg->num_pans == 2) {
+		if (ichan == 2 && ochan == 1) {
+			avg->sources[0][0] = pans[0];
+			avg->sources[0][1] = 0.0;
+			avg->sources[1][0] = pans[1];
+		}
+		else if (ichan == 4 && ochan == 2) {
+			avg->sources[0][0] = pans[0];
+			avg->sources[0][1] = 0.0;
+			avg->sources[1][1] = pans[0];
+			avg->sources[2][0] = pans[1];
+			avg->sources[3][1] = pans[1];
+		}
+		else if (ichan == 4 && ochan == 4) {
+			/* pans[0] is front -> front, pans[1] is for back */
+			avg->sources[0][0] = pans[0];
+			avg->sources[0][1] = 0.0;
+			avg->sources[1][1] = pans[0];
+			avg->sources[2][2] = pans[1];
+			avg->sources[3][3] = pans[1];
+		}
+	}
+	else if (avg->num_pans == 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[0][2] = 0.0;
+			avg->sources[0][3] = 0.0;
+			avg->sources[1][0] = pans[2];
+			avg->sources[1][1] = pans[3];
+		}
+		else if (ichan == 4 && ochan == 1) {
+			avg->sources[0][0] = pans[0];
+			avg->sources[0][1] = 0.0;
+			avg->sources[0][2] = 0.0;
+			avg->sources[0][3] = 0.0;
+			avg->sources[1][0] = pans[1];
+			avg->sources[2][0] = pans[2];
+			avg->sources[3][0] = pans[3];
+		}
+	}
+	return (ST_SUCCESS);
 }
 
 /*
@@ -119,163 +358,29 @@
 {
 	avg_t avg = (avg_t) effp->priv;
 	int len, done;
+	int ichan, ochan;
+	int i, j;
+	double samp;
 	
-	switch (effp->outinfo.channels) {
-		case 1: switch (effp->ininfo.channels) {
-			case 2:
-				/* average 2 channels into 1 */
-				len = ((*isamp/2 > *osamp) ? *osamp : *isamp/2);
-				switch(avg->mix) {
-				    case MIX_CENTER:
-					for(done = 0; done < len; done++) {
-						*obuf++ = ibuf[0]/2 + ibuf[1]/2;
-						ibuf += 2;
-					}
-					break;
-				    case MIX_LEFT:
-					for(done = 0; done < len; done++) {
-						*obuf++ = ibuf[0];
-						ibuf += 2;
-					}
-					break;
-				    case MIX_RIGHT:
-					for(done = 0; done < len; done++) {
-						*obuf++ = ibuf[1];
-						ibuf += 2;
-					}
-					break;
-				}
-				*isamp = len * 2;
-				*osamp = len;
-				break;
-			case 4:
-				/* average 4 channels into 1 */
-				len = ((*isamp/4 > *osamp) ? *osamp : *isamp/4);
-				switch(avg->mix) {
-				    case MIX_CENTER:
-					for(done = 0; done < len; done++) {
-						*obuf++ = ibuf[0]/4 + ibuf[1]/4 +
-							ibuf[2]/4 + ibuf[3]/4;
-						ibuf += 4;
-					}
-					break;
-				    case MIX_LEFT:
-					for(done = 0; done < len; done++) {
-						*obuf++ = ibuf[0]/2 + ibuf[2]/2;
-						ibuf += 4;
-					}
-					break;
-				    case MIX_RIGHT:
-					for(done = 0; done < len; done++) {
-						*obuf++ = ibuf[1]/2 + ibuf[3]/2;
-						ibuf += 4;
-					}
-					break;
-				}
-				*isamp = len * 4;
-				*osamp = len;
-				break;
-			}
-			break;
-		case 2: switch (effp->ininfo.channels) {
-			case 1:
-				/* duplicate 1 channel into 2 */
-				len = ((*isamp > *osamp/2) ? *osamp/2 : *isamp);
-				switch(avg->mix) {
-				    case MIX_CENTER:
-					for(done = 0; done < len; done++) {
-						obuf[0] = obuf[1] = ibuf[0];
-						ibuf += 1;
-						obuf += 2;
-					}
-					break;
-				    case MIX_LEFT:
-					for(done = 0; done < len; done++) {
-						obuf[0] = ibuf[0];
-						obuf[1] = 0;
-						ibuf += 1;
-						obuf += 2;
-					}
-					break;
-				    case MIX_RIGHT:
-					for(done = 0; done < len; done++) {
-						obuf[0] = 0;
-						obuf[1] = ibuf[0];
-						ibuf += 1;
-						obuf += 2;
-					}
-					break;
-				}
-				*isamp = len;
-				*osamp = len * 2;
-				break;
-			/*
-			 * After careful inspection of CSOUND source code,
-			 * I'm mildly sure the order is:
-			 * 	front-left, front-right, rear-left, rear-right
-			 */
-			case 4:
-				/* average 4 channels into 2 */
-				len = ((*isamp/4 > *osamp/2) ? *osamp/2 : *isamp/4);
-				for(done = 0; done < len; done++) {
-					obuf[0] = ibuf[0]/2 + ibuf[2]/2;
-					obuf[1] = ibuf[1]/2 + ibuf[3]/2;
-					ibuf += 4;
-					obuf += 2;
-				}
-				*isamp = len * 4;
-				*osamp = len * 2;
-				break;
-			}
-			break;
-		case 4: switch (effp->ininfo.channels) {
-			case 1:
-				/* duplicate 1 channel into 4 */
-				len = ((*isamp > *osamp/4) ? *osamp/4 : *isamp);
-				switch(avg->mix) {
-				    case MIX_CENTER:
-					for(done = 0; done < len; done++) {
-						obuf[0] = obuf[1] = 
-						obuf[2] = obuf[3] = ibuf[0];
-						ibuf += 1;
-						obuf += 4;
-					}
-					break;
-				    case MIX_LEFT:
-					for(done = 0; done < len; done++) {
-						obuf[0] = obuf[2] = ibuf[0];
-						obuf[1] = obuf[3] = 0;
-						ibuf += 1;
-						obuf += 4;
-					}
-					break;
-				    case MIX_RIGHT:
-					for(done = 0; done < len; done++) {
-						obuf[0] = obuf[2] = 0;
-						obuf[1] = obuf[3] = ibuf[0];
-						ibuf += 1;
-						obuf += 4;
-					}
-					break;
-				}
-				*isamp = len;
-				*osamp = len * 4;
-				break;
-			case 2:
-				/* duplicate 2 channels into 4 */
-				len = ((*isamp/2 > *osamp/4) ? *osamp/4 : *isamp/2);
-				for(done = 0; done < len; done++) {
-					obuf[0] = obuf[2] = ibuf[0];
-					obuf[1] = obuf[3] = ibuf[1];
-					ibuf += 2;
-					obuf += 4;
-				}
-				*isamp = len * 2;
-				*osamp = len * 4;
-				break;
-			}
-			break;
-	}	/* end switch out channels */
+	ichan = effp->ininfo.channels;
+	ochan = effp->outinfo.channels;
+	len = *isamp;
+	if (len > *osamp)
+		len = *osamp;
+	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];
+			if (samp < -CLIP_LEVEL)
+			    samp = -CLIP_LEVEL;
+			else if (samp > CLIP_LEVEL)
+			    samp = CLIP_LEVEL;
+			obuf[j] = samp;
+		}
+	}
+	*isamp = len;
+	*osamp = len;
 	return (ST_SUCCESS);
 }
 
--- a/src/compand.c
+++ b/src/compand.c
@@ -1,9 +1,8 @@
 /*
  * Compander effect
  *
- * Written by Nick Bailey (nick@polonius.demon.co.uk or
- * N.J.Bailey@leeds.ac.uk).  Hope page for this effect:
- * http://www.ee.keeds.ac.uk/homes/NJB/Softwere/Compand/compand.html
+ * Written by Nick Bailey (nick@bailey-family.org.uk or
+ *                         n.bailey@elec.gla.ac.uk)
  *
  * Copyright 1999 Chris Bagwell And Nick Bailey
  * This source code is freely redistributable and may be used for
@@ -17,6 +16,33 @@
 #include <math.h>
 #include "st.h"
 
+/*
+ * Compressor/expander effect for dsp.
+ *
+ * Flow diagram for one channel:
+ *
+ *		 ------------	   ---------------
+ *		|	     |	  |		  |	---
+ * ibuff ---+---| integrator |--->| transfer func |--->|   |
+ *	    |	|	     |	  |		  |    |   |
+ *	    |	 ------------	   ---------------     |   |  * gain
+ *	    |					       | * |----------->obuff
+ *	    |	    -------			       |   |
+ *	    |	   |	   |			       |   |
+ *	    +----->| delay |-------------------------->|   |
+ *		   |	   |			        ---
+ *		    -------
+ *
+ * Usage:
+ *   compand attack1,decay1[,attack2,decay2...]
+ *                  in-dB1,out-dB1[,in-dB2,out-dB2...]
+ *                 [ gain [ initial-volume [ delay ] ] ] 
+ *
+ * Note: clipping can occur if the transfer function pushes things too
+ * close to 0 dB.  In that case, use a negative gain, or reduce the
+ * output level of the transfer function.
+ */
+
 /* Private data for SKEL file */
 typedef struct {
   int expectedChannels; /* Also flags that channels aren't to be treated
@@ -28,8 +54,12 @@
   double *transferIns;  /*    ... and points on the transfer function */
   double *transferOuts;
   double *volume;       /* Current "volume" of each channel */
-  LONG   *lastSamp;     /* Remeber the value of the previous sample */
   double outgain;       /* Post processor gain */
+  double delay;		/* Delay to apply before companding */
+  LONG   *delay_buf;	/* Old samples, used for delay processing */
+  long	 delay_buf_size; /* Size of delay_buf in samples */
+  long	 delay_buf_ptr; /* Index into delay_buf */
+  long	 delay_buf_cnt;	/* No. of active entries in delay_buf */
 } *compand_t;
 
 /*
@@ -45,11 +75,11 @@
 {
     compand_t l = (compand_t) effp->priv;
 
-    if (n < 2 || n > 4)
+    if (n < 2 || n > 5)
     {
       st_fail("Wrong number of arguments for the compander effect\n"
 	   "Use: {<attack_time>,<decay_time>}+ {<dB_in>,<db_out>}+ "
-	   "[<dB_postamp>]\n"
+	   "[<dB_postamp> [<initial-volume> [<delay_time]]]\n"
 	   "where {}+ means `one or more in a comma-separated, "
 	   "white-space-free list'\n"
 	   "and [] indications possible omission.  dB values are floating\n"
@@ -75,13 +105,13 @@
       rates = 1 + commas/2;
       if ((l->attackRate = malloc(sizeof(double) * rates)) == NULL ||
 	  (l->decayRate  = malloc(sizeof(double) * rates)) == NULL ||
-	  (l->volume     = malloc(sizeof(double) * rates)) == NULL ||
-	  (l->lastSamp   = calloc(rates, sizeof(LONG)))    == NULL)
+	  (l->volume     = malloc(sizeof(double) * rates)) == NULL)
       {
 	st_fail("Out of memory");
 	return (ST_EOF);
       }
       l->expectedChannels = rates;
+      l->delay_buf = NULL;
 
       /* Now tokenise the rates string and set up these arrays.  Keep
 	 them in seconds at the moment: we don't know the sample rate yet. */
@@ -155,6 +185,10 @@
       for (i = 0; i < l->expectedChannels; ++i) {
 	double v = n>=4 ? pow(10.0, atof(argv[3])/20) : 1.0;
 	l->volume[i] = v;
+
+      /* If there is a delay, store it. */
+      if (n >= 5) l->delay = atof(argv[4]);
+      else l->delay = 0.0;
       }
     }
     return (ST_SUCCESS);
@@ -205,6 +239,19 @@
     else
       l->decayRate[i] = 1.0;
   }
+
+  /* Allocate the delay buffer */
+  l->delay_buf_size = l->delay * effp->outinfo.rate * effp->outinfo.channels;
+  if (l->delay_buf_size > 0
+   && (l->delay_buf = malloc(sizeof(long) * l->delay_buf_size)) == NULL) {
+    st_fail("Out of memory");
+    return (ST_EOF);
+  }
+  for (i = 0;  i < l->delay_buf_size;  i++)
+    l->delay_buf[i] = 0;
+  l->delay_buf_ptr = 0;
+  l->delay_buf_cnt = 0;
+
   return (ST_SUCCESS);
 }
 
@@ -239,8 +286,7 @@
   int filechans = effp->outinfo.channels;
   int done;
 
-  for (done = 0; done < len;
-       done += filechans, obuf += filechans, ibuf += filechans) {
+  for (done = 0; done < len; ibuf += filechans) {
     int chan;
 
     /* Maintain the volume fields by simulating a leaky pump circuit */
@@ -280,11 +326,70 @@
 	(v - l->transferIns[piece-1]) /
 	(l->transferIns[piece] - l->transferIns[piece-1]);
 
-      obuf[chan] = ibuf[chan]*(outv/v)*l->outgain;
-	
+      if (l->delay_buf_size <= 0)
+        obuf[done++] = ibuf[chan]*(outv/v)*l->outgain;
+      else {
+	if (l->delay_buf_cnt >= l->delay_buf_size)
+	    obuf[done++] = l->delay_buf[l->delay_buf_ptr]*(outv/v)*l->outgain;
+	else
+	    l->delay_buf_cnt++;
+        l->delay_buf[l->delay_buf_ptr++] = ibuf[chan];
+        l->delay_buf_ptr %= l->delay_buf_size;
+      }
     }
   }
 
-  *isamp = len; *osamp = len;
+  *isamp = done; *osamp = done;
+  return (ST_SUCCESS);
+}
+
+/*
+ * Drain out compander delay lines. 
+ */
+int st_compand_drain(effp, obuf, osamp)
+eff_t effp;
+LONG *obuf;
+LONG *osamp;
+{
+  compand_t l = (compand_t) effp->priv;
+  int done;
+
+  /*
+   * Drain out delay samples.  Note that this loop does all channels.
+   */
+  for (done = 0;  done < *osamp  &&  l->delay_buf_cnt > 0;  done++) {
+    obuf[done] = l->delay_buf[l->delay_buf_ptr++];
+    l->delay_buf_ptr %= l->delay_buf_size;
+    l->delay_buf_cnt--;
+  }
+
+  /* tell caller number of samples played */
+  *osamp = done;
+  return (ST_SUCCESS);
+}
+
+
+/*
+ * Clean up compander effect.
+ */
+int st_compand_stop(effp)
+eff_t effp;
+{
+  compand_t l = (compand_t) effp->priv;
+
+  free((char *) l->delay_buf);
+  free((char *) l->transferOuts);
+  free((char *) l->transferIns);
+  free((char *) l->volume);
+  free((char *) l->decayRate);
+  free((char *) l->attackRate);
+
+  l->delay_buf = NULL;
+  l->transferOuts = NULL;
+  l->transferIns = NULL;
+  l->volume = NULL;
+  l->decayRate = NULL;
+  l->attackRate = NULL;
+
   return (ST_SUCCESS);
 }
--- a/src/gsm.c
+++ b/src/gsm.c
@@ -60,6 +60,9 @@
 	if (!ft->info.rate)
 		ft->info.rate = 8000;
 
+	if (ft->info.channels == -1)
+	    ft->info.channels == 1;
+
 	p->channels = ft->info.channels;
 	if (p->channels > MAXCHANS || p->channels <= 0)
 	{
--- a/src/handlers.c
+++ b/src/handlers.c
@@ -477,6 +477,8 @@
 extern int st_compand_getopts();
 extern int st_compand_start();
 extern int st_compand_flow();
+extern int st_compand_drain();
+extern int st_compand_stop();
 
 extern int st_copy_getopts(); 
 extern int st_copy_start();
@@ -658,7 +660,7 @@
 	{"null", 0, 			/* stand-in, never gets called */
 		st_nothing, st_nothing, st_nothing, 
 		st_null_drain, st_nothing},
-	{"avg", ST_EFF_MCHAN | ST_EFF_CHAN, 
+	{"avg", ST_EFF_MCHAN, 
 		st_avg_getopts, st_avg_start, st_avg_flow, 
 		st_null_drain, st_avg_stop},
 	{"band", 0, 
@@ -675,7 +677,7 @@
 	 	st_chorus_drain, st_chorus_stop},
 	{"compand", ST_EFF_MCHAN,
 	        st_compand_getopts, st_compand_start, st_compand_flow,
-		st_null_drain, st_nothing},
+		st_compand_drain, st_compand_stop},
 	{"copy", ST_EFF_MCHAN, 
 		st_copy_getopts, st_copy_start, st_copy_flow, 
 		st_null_drain, st_nothing},
--- a/src/sox.c
+++ b/src/sox.c
@@ -412,7 +412,7 @@
  */
 
 static void process(P0) {
-    int e, f, havedata;
+    int e, f, havedata, flowstatus;
 
     st_gettype(&informat);
     if (writing)
@@ -542,14 +542,18 @@
 	for(e = 1; e < neffects; e++)
 	    efftab[e].odone = efftab[e].olen = 0;
 
+	flowstatus = 0;
+
 	do {
 	    ULONG w;
 
 	    /* run entire chain BACKWARDS: pull, don't push.*/
 	    /* this is because buffering system isn't a nice queueing system */
-	    for(e = neffects - 1; e > 0; e--) 
-		if (flow_effect(e))
+	    for(e = neffects - 1; e > 0; e--) {
+		flowstatus = flow_effect(e);
+		if (flowstatus)
 		    break;
+	    }
 
 	    /* If outputing and output data was generated then write it */
 	    if (writing&&(efftab[neffects-1].olen>efftab[neffects-1].odone)) 
@@ -563,6 +567,12 @@
 	    if (outformat.st_errno)
 		st_fail(outformat.st_errstr);
 
+	    /* If any effect will never again produce data, give up.  This */
+	    /* works because of the pull status: the effect won't be able to */
+	    /* shut us down until all downstream buffers have been emptied. */
+	    if (flowstatus < 0)
+		break;
+
 	    /* if stuff still in pipeline, set up to flow effects again */
 	    havedata = 0;
 	    for(e = 0; e < neffects - 1; e++)
@@ -572,6 +582,10 @@
 		}
 	} while (havedata);
 
+	/* Negative flowstatus says no more output will ever be generated. */
+	if (flowstatus < 0)
+	    break;
+
         /* Read another chunk of input data. */
         efftab[0].olen = (*informat.h->read)(&informat, 
                                              efftab[0].obuf, (LONG) BUFSIZ);
@@ -635,6 +649,7 @@
 {
     LONG i, done, idone, odone, idonel, odonel, idoner, odoner;
     LONG *ibuf, *obuf;
+    int effstatus;
 
     /* I have no input data ? */
     if (efftab[e-1].odone == efftab[e-1].olen)
@@ -646,7 +661,7 @@
 	 */
 	idone = efftab[e-1].olen - efftab[e-1].odone;
 	odone = BUFSIZ;
-	(* efftab[e].h->flow)(&efftab[e], 
+	effstatus = (* efftab[e].h->flow)(&efftab[e], 
 			      &efftab[e-1].obuf[efftab[e-1].odone], 
 			      efftab[e].obuf, &idone, &odone);
 	efftab[e-1].odone += idone;
@@ -669,12 +684,14 @@
 	/* left */
 	idonel = (idone + 1)/2;		/* odd-length logic */
 	odonel = odone/2;
-	(* efftab[e].h->flow)(&efftab[e], ibufl, obufl, &idonel, &odonel);
+	effstatus = (* efftab[e].h->flow)(&efftab[e],
+					  ibufl, obufl, &idonel, &odonel);
 	
 	/* right */
 	idoner = idone/2;		/* odd-length logic */
 	odoner = odone/2;
-	(* efftabR[e].h->flow)(&efftabR[e], ibufr, obufr, &idoner, &odoner);
+	effstatus = (* efftabR[e].h->flow)(&efftabR[e],
+					   ibufr, obufr, &idoner, &odoner);
 
 	obuf = efftab[e].obuf;
 	 /* This loop implies left and right effect will always output
@@ -691,6 +708,8 @@
     } 
     if (done == 0) 
 	st_fail("Effect took & gave no samples!");
+    if (effstatus == ST_EOF)
+	return -1;
     return 1;
 }
 
--- a/src/st.h
+++ b/src/st.h
@@ -352,6 +352,7 @@
 void st_checkformat(P1(ft_t));
 void st_copyformat(P2(ft_t, ft_t));
 void st_cmpformats(P2(ft_t, ft_t));
+double st_parsetime(P1(char *));
 
 /* FIXME: Recording hacks shouldn't display a "sigint" style interface.
  * Instead we should provide a function to call when done playing/recording.
--- a/src/trim.c
+++ b/src/trim.c
@@ -48,7 +48,8 @@
 
         switch (n) {
             case 2:
-			if (sscanf(argv[1], "%lf", &time) == 1)
+			time = st_parsetime(argv[1]);
+			if (time >= 0.0)
 			{
                             trim->length = time * TIMERES;
 			}
@@ -58,7 +59,8 @@
 			    return(ST_EOF);
 			}
             case 1:
-			if (sscanf(argv[0], "%lf", &time) == 1)
+			time = st_parsetime(argv[0]);
+			if (time >= 0.0)
 			{
                             trim->start = time * TIMERES;
 			}
@@ -86,13 +88,13 @@
         trim_t trim = (trim_t) effp->priv;
 
 
-        trim->start = effp->ininfo.channels * effp->ininfo.rate * trim->start / TIMERES;
+        trim->start = (double)effp->ininfo.channels * effp->ininfo.rate * trim->start / TIMERES;
         if (trim->start < 0) 
         {
                 st_fail("trim: start must be positive");
         }
 
-	trim->length = effp->ininfo.channels * effp->ininfo.rate * trim->length / TIMERES;
+	trim->length = (double)effp->ininfo.channels * effp->ininfo.rate * trim->length / TIMERES;
         if (trim->length < 0) 
         {
                 st_fail("trim: start must be positive");
@@ -128,7 +130,7 @@
 
 	if (trim->done) {
 		*osamp = 0;
-		return (ST_SUCCESS);
+		return (ST_EOF);
 	}
 
 	trim->index += done;
--- a/src/util.c
+++ b/src/util.c
@@ -414,3 +414,30 @@
 	ft_queue[1] = ft;
     signal(SIGINT, sigint);
 }
+
+/* Parse a time specification in hh:mm:ss.frac format.  Returns -1 */
+/* on failure. */
+double
+st_parsetime(str)
+char *str;
+{
+    double time, moretime;
+    if (sscanf(str, "%lf", &time) != 1)
+	return -1;
+    str = strchr(str, ':');
+    if (str == NULL)
+	return time;
+    str++;				/* Skip colon */
+    time *= 60.0;
+    if (sscanf(str, "%lf", &moretime) != 1)
+	return -1;
+    time += moretime;
+    str = strchr(str, ':');
+    if (str == NULL)
+	return time;
+    str++;				/* Skip colon */
+    time *= 60.0;
+    if (sscanf(str, "%lf", &moretime) != 1)
+	return -1;
+    return time + moretime;
+}