ref: 98c0f2834f32b28ca0582aeef91fc602a9ea6517
parent: 689b86e5ae687e7c484fc87a508dae47125ec47a
author: cbagwell <cbagwell>
date: Mon Mar 27 14:13:20 EST 2000
Added pan and vol effects.
--- a/Makefile.dos
+++ b/Makefile.dos
@@ -18,9 +18,9 @@
EOBJ = avg.obj band.obj bandpass.obj breject.obj btrworth.obj chorus.obj \
compand.obj copy.obj cut.obj deemphas.obj echo.obj \
echos.obj filter.obj flanger.obj highp.obj highpass.obj lowp.obj \
- lowpass.obj map.obj mask.obj phaser.obj pick.obj polyphas.obj \
- rate.obj resample.obj reverb.obj reverse.obj split.obj \
- stat.obj swap.obj vibro.obj
+ lowpass.obj map.obj mask.obj phaser.obj pick.obj pan.o \
+ polyphase.obj rate.obj resample.obj reverb.obj \
+ reverse.obj split.obj stat.obj swap.obj vibro.obj vol.obj
LIBOBJS = $(FOBJ) $(EOBJ) handlers.obj libst.obj misc.obj getopt.obj util.obj
--- a/Makefile.gcc
+++ b/Makefile.gcc
@@ -35,9 +35,9 @@
EOBJ = avg.o band.o bandpass.o breject.o btrworth.o chorus.o compand.o \
copy.o cut.o deemphas.o echo.o echos.o filter.o flanger.o \
- highp.o highpass.o lowp.o lowpass.o map.o mask.o phaser.o pick.o \
- polyphas.o rate.o resample.o reverb.o reverse.o split.o \
- stat.o swap.o vibro.o
+ highp.o highpass.o lowp.o lowpass.o map.o mask.o pan.o \
+ phaser.o pick.o polyphas.o rate.o resample.o reverb.o \
+ reverse.o split.o stat.o swap.o vibro.o vol.o
SOUNDLIB = libst.a
LIBOBJS = $(FOBJ) $(EOBJ) handlers.o libst.o misc.o util.o getopt.o
--- a/Makefile.os9
+++ b/Makefile.os9
@@ -27,9 +27,11 @@
$(RDIR)/dyn.r $(RDIR)/echo.r $(RDIR)/echos.r $(RDIR)/filter.r \
$(RDIR)/flanger.r \
$(RDIR)/highp.r $(RDIR)/lowp.r $(RDIR)/map.r $(RDIR)/mask.r \
- $(RDIR)/phaser.r $(RDIR)/pick.r $(RDIR)/polyphas.r $(RDIR)/rate.r \
+ $(RDIR)/pan.r $(RDIR)/phaser.r $(RDIR)/pick.r \
+ $(RDIR)/polyphas.r $(RDIR)/rate.r \
$(RDIR)/resample.r $(RDIR)/reverb.r $(RDIR)/reverse.r \
- $(RDIR)/split.r $(RDIR)/stat.r $(RDIR)/swap.r $(RDIR)/vibro.r
+ $(RDIR)/split.r $(RDIR)/stat.r $(RDIR)/swap.r $(RDIR)/vibro.r \
+ $(RDIR)/vol.r
LIBOBJS = $(FOBJ) $(EOBJ) $(RDIR)/handlers.r $(RDIR)/libst.r \
--- a/sox.1
+++ b/sox.1
@@ -89,6 +89,8 @@
.br
mask
.br
+ pan \fIdirection\fB
+.br
phaser \fIgain-in gain-out delay decay speed -s\fB | \fI-t\fB
.br
pick
@@ -112,6 +114,8 @@
swap [ \fI1 2 3 4\fB ]
.br
vibro \fIspeed \fB[ \fIdepth\fB ]
+.br
+ vol \fIgain \fB[ \fItype\fB ]
.SH DESCRIPTION
.I SoX
is a command line program that can convert most popular audio files
@@ -674,6 +678,19 @@
It adds 1/2 bit of noise to the sound file at the
output bit depth.
.TP 10
+pan \fIdirection\fB
+Pan the sound of an audio file from one channel to another. This is done by
+changing the volume of the input channels so that it fade's out on one
+channel and fades-in on another. If the number of input channels is
+different then the number of output channels then this effect tries to
+intellegently handle this. For instance, if the input contains 1 channel
+and the output contains 2 channels, then it will create the missing channel
+itself. The
+.I direction
+is a value from -1.0 to 1.0. -1.0 represents
+far left and 1.0 represents far right. Numbers in between will start the
+pan effect without totally muting the opposite channel.
+.TP 10
phaser \fIgain-in gain-out delay decay speed -s \fR| \fI-t
Add a phaser to a sound sample. Each triple
delay/decay/speed gives the delay in milliseconds
@@ -738,7 +755,7 @@
If you are wondering which of
.B SoX's
rate changing effects to use, you will want to read a
-detailed analysis of all of them at http://eakaw2.et.tu-dresden.de/~andreas/resample/resample.html
+detailed analysis of all of them at http://eakaw2.et.tu-dresden.de/~wilde/resample/resample.html
[Nov,1999: These tests need to be updated for sox-12.17, which has bugfixes to the
resample and polyphase code.]
.TP 10
@@ -873,6 +890,26 @@
gives the amount the volume is cut into
by the sine wave,
ranging 0.0 to 1.0 and defaulting to 0.5.
+.TP 10
+vol \fIgain \fB [ \fItype\fB ]
+The vol effect is much like the command line option -v. It allows you to
+adjust the volume of an input file and allows you to specify the adjustment
+in relation to amplitude, power, or dB. When type is
+.I amplitude
+then a linear change of the amplitude is performed based on the gain. Therefore,
+a value of 1.0 will keep the volume the same, 0.0 to < 1.0 will cause the
+volume to decrease and values of > 1.0 will cause the volume to increase.
+Beware of clipping audio data when the gain is greater then 1.0. A negative
+value performs the same adjustment while also changing the phase.
+.br
+When type is
+.I power
+then a value of 1.0 also means no change in volume.
+.br
+When type is
+.I dB
+the amplitude is change logrithmically.
+0.0 is constant while +6 doubles the amplitude.
.P
.I Sox
enforces certain effects.
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -47,9 +47,9 @@
EOBJ = avg.o band.o bandpass.o breject.o btrworth.o chorus.o compand.o \
copy.o cut.o deemphas.o echo.o echos.o filter.o flanger.o \
- highp.o highpass.o lowp.o lowpass.o map.o mask.o phaser.o pick.o \
- polyphas.o rate.o resample.o reverb.o reverse.o split.o \
- stat.o swap.o vibro.o
+ highp.o highpass.o lowp.o lowpass.o map.o mask.o pan.o \
+ phaser.o pick.o polyphas.o rate.o resample.o reverb.o \
+ reverse.o split.o stat.o swap.o vibro.o vol.o
OSSOBJ_0 =
OSSOBJ_1 = oss.o
--- a/src/handlers.c
+++ b/src/handlers.c
@@ -536,6 +536,11 @@
extern int st_mask_getopts();
extern int st_mask_flow();
+extern int st_pan_getopts();
+extern int st_pan_start();
+extern int st_pan_flow();
+extern int st_pan_stop();
+
extern int st_phaser_getopts();
extern int st_phaser_start();
extern int st_phaser_flow();
@@ -597,6 +602,12 @@
extern int st_vibro_flow();
extern int st_vibro_stop();
+extern int st_vol_getopts();
+extern int st_vol_start();
+extern int st_vol_flow();
+extern int st_vol_stop();
+
+
/*
* EFF_CHAN means that the number of channels can change.
* EFF_RATE means that the sample rate can change.
@@ -667,6 +678,9 @@
{"mask", ST_EFF_MCHAN,
st_mask_getopts, st_nothing, st_mask_flow,
st_null_drain, st_nothing},
+ {"pan", ST_EFF_MCHAN|ST_EFF_CHAN,
+ st_pan_getopts, st_pan_start, st_pan_flow,
+ st_null_drain, st_pan_stop},
{"phaser", 0,
st_phaser_getopts, st_phaser_start, st_phaser_flow,
st_phaser_drain, st_phaser_stop},
@@ -700,6 +714,9 @@
{"vibro", 0,
st_vibro_getopts, st_vibro_start, st_vibro_flow,
st_null_drain, st_nothing},
+ {"vol", 0,
+ st_vol_getopts, st_vol_start, st_vol_flow,
+ st_null_drain, st_vol_stop},
{0, 0, 0, 0, 0, 0, 0}
};
--- /dev/null
+++ b/src/pan.c
@@ -1,0 +1,412 @@
+/*
+ * (c) 20/03/2000 Fabien COELHO <fabien@coelho.net> for sox.
+ *
+ * Same copyright as SOX. See Sox (and Sun?;-)
+ *
+ * Change panorama of sound file with basic linear volume interpolation.
+ * The human ear is not sensible to phases? What about delay? too short?
+ *
+ * Volume is kept constant (?).
+ * Beware of saturations!
+ * Operations are carried out on floats.
+ * Can handle different number of channels.
+ * Cannot handle rate change.
+ *
+ * Initially based on avg effect.
+ * pan 0.0 basically behaves as avg.
+ */
+
+#include "st.h"
+
+/* should be taken care in st.h? */
+#include <limits.h> /* LONG_MAX */
+
+/* type for computations.
+ float mantissa is okay for 8 or 16 bits signals.
+ maybe a little bit short for 24 bits.
+ switch to double if you're not happy.
+ */
+#ifndef PAN_FLOAT
+#define PAN_FLOAT float /* float or double */
+#define PAN_FLOAT_SCAN "%f" /* "%f" or "%lf" */
+#endif
+
+/* constants */
+
+#define TWO ((PAN_FLOAT)(2.0e0))
+#define ONEHALF ((PAN_FLOAT)(1.5e0))
+#define ONE ((PAN_FLOAT)(1.0e0))
+#define MONE ((PAN_FLOAT)(-1.0e0))
+#define HALF ((PAN_FLOAT)(0.5e0))
+#define QUARTER ((PAN_FLOAT)(0.25e0))
+#define ZERO ((PAN_FLOAT)(0.0e0))
+
+/* error message */
+
+#define PAN_USAGE "Usage: pan direction (in [-1.0 .. 1.0])"
+
+/* structure to hold pan parameter */
+
+typedef struct {
+ /* pan option
+ */
+ PAN_FLOAT dir; /* direction, from left (-1.0) to right (1.0) */
+
+ /* local data
+ */
+ int clipped; /* number of clipped values */
+} * pan_t;
+
+/*
+ * Process options
+ */
+int st_pan_getopts(effp, n, argv)
+eff_t effp;
+int n;
+char **argv;
+{
+ pan_t pan = (pan_t) effp->priv;
+
+ pan->dir = ZERO; /* default is no change */
+ pan->clipped = 0;
+
+ if (n && (!sscanf(argv[0], PAN_FLOAT_SCAN, &pan->dir) ||
+ pan->dir < MONE || pan->dir > ONE))
+ {
+ fail(PAN_USAGE);
+ return ST_EOF;
+ }
+
+ return ST_SUCCESS;
+}
+
+/*
+ * Start processing
+ */
+int st_pan_start(effp)
+eff_t effp;
+{
+ if (effp->outinfo.channels==1)
+ warn("PAN onto a mono channel...");
+
+ if (effp->outinfo.rate != effp->ininfo.rate)
+ {
+ fail("PAN cannot handle different rates (in=%ld, out=%ld)"
+ " use resample or rate", effp->ininfo.rate, effp->outinfo.rate);
+ return ST_EOF;
+ }
+
+ return ST_SUCCESS;
+}
+
+
+/* clip value if necessary. see comments about such a function in vol.c
+ * Okay, it might be quite slow to have such a function for so small
+ * a task. Hopefully the function can be inlined by the compiler?
+ */
+static LONG clip(pan_t pan, PAN_FLOAT value)
+{
+ if (value < -LONG_MAX)
+ {
+ pan->clipped++;
+ return -LONG_MAX;
+ }
+ else if (value > LONG_MAX)
+ {
+ pan->clipped++;
+ return LONG_MAX;
+ } /* else */
+
+ return (LONG) value;
+}
+
+#ifndef MIN
+#define MIN(s1,s2) ((s1)<(s2)?(s1):(s2))
+#endif
+
+#define UNEXPECTED_CHANNELS \
+ fail("unexpected number of channels (in=%d, out=%d)", ich, och); \
+ return ST_EOF
+
+/*
+ * Process either isamp or osamp samples, whichever is smaller.
+ */
+int st_pan_flow(effp, ibuf, obuf, isamp, osamp)
+eff_t effp;
+LONG *ibuf, *obuf;
+LONG *isamp, *osamp;
+{
+ pan_t pan = (pan_t) effp->priv;
+ register LONG len;
+ register int done, ich, och;
+ register PAN_FLOAT left, right, dir, hdir;
+
+ dir = pan->dir; /* -1 <= dir <= 1 */
+ hdir = HALF * dir; /* -0.5 <= hdir <= 0.5 */
+ left = HALF - hdir; /* 0 <= left <= 1 */
+ right = HALF + hdir; /* 0 <= right <= 1 */
+
+ ich = effp->ininfo.channels;
+ och = effp->outinfo.channels;
+
+ len = MIN(*osamp/och,*isamp/ich);
+
+ /* report back how much is processed. */
+ *isamp = len*ich;
+ *osamp = len*och;
+
+ /* 9 different cases to handle: (1,2,4) X (1,2,4) */
+ switch (och) {
+ case 1: /* pan on mono channel... not much sense. just avg. */
+ switch (ich) {
+ case 1: /* simple copy */
+ for (done=0; done<len; done++)
+ *obuf++ = *ibuf++;
+ break;
+ case 2: /* average 2 */
+ for (done=0; done<len; done++)
+ *obuf++ = clip(pan, HALF*ibuf[0] + HALF*ibuf[1]),
+ ibuf += 2;
+ break;
+ case 4: /* average 4 */
+ for (done=0; done<len; done++)
+ *obuf++ = clip(pan,
+ QUARTER*ibuf[0] + QUARTER*ibuf[1] +
+ QUARTER*ibuf[2] + QUARTER*ibuf[3]),
+ ibuf += 4;
+ break;
+ default:
+ UNEXPECTED_CHANNELS;
+ break;
+ } /* end first switch in channel */
+ break;
+ case 2:
+ switch (ich) {
+ case 1: /* linear */
+ for (done=0; done<len; done++)
+ {
+ obuf[0] = clip(pan, left * ibuf[0]);
+ obuf[1] = clip(pan, right * ibuf[0]);
+ obuf += 2;
+ ibuf++;
+ }
+ break;
+ case 2: /* linear panorama.
+ * I'm not sure this is the right way to do it.
+ */
+ if (dir <= ZERO) /* to the left */
+ {
+ register PAN_FLOAT volume, cll, clr, cr;
+
+ volume = ONE - HALF*dir;
+ cll = volume*(ONEHALF-left);
+ clr = volume*(left-HALF);
+ cr = volume*(ONE+dir);
+
+ for (done=0; done<len; done++)
+ {
+ obuf[0] = clip(pan, cll * ibuf[0] + clr * ibuf[1]);
+ obuf[1] = clip(pan, cr * ibuf[1]);
+ obuf += 2;
+ ibuf += 2;
+ }
+ }
+ else /* to the right */
+ {
+ register PAN_FLOAT volume, cl, crl, crr;
+
+ volume = ONE + HALF*dir;
+ cl = volume*(ONE-dir);
+ crl = volume*(right-HALF);
+ crr = volume*(ONEHALF-right);
+
+ for (done=0; done<len; done++)
+ {
+ obuf[0] = clip(pan, cl * ibuf[0]);
+ obuf[1] = clip(pan, crl * ibuf[0] + crr * ibuf[1]);
+ obuf += 2;
+ ibuf += 2;
+ }
+ }
+ break;
+ case 4:
+ if (dir <= ZERO) /* to the left */
+ {
+ register PAN_FLOAT volume, cll, clr, cr;
+
+ volume = ONE - HALF*dir;
+ cll = volume*(ONEHALF-left);
+ clr = volume*(left-HALF);
+ cr = volume*(ONE+dir);
+
+ for (done=0; done<len; done++)
+ {
+ register PAN_FLOAT ibuf0, ibuf1;
+
+ /* build stereo signal */
+ ibuf0 = HALF*ibuf[0] + HALF*ibuf[2];
+ ibuf1 = HALF*ibuf[1] + HALF*ibuf[3];
+
+ /* pan it */
+ obuf[0] = clip(pan, cll * ibuf0 + clr * ibuf1);
+ obuf[1] = clip(pan, cr * ibuf1);
+ obuf += 2;
+ ibuf += 4;
+ }
+ }
+ else /* to the right */
+ {
+ register PAN_FLOAT volume, cl, crl, crr;
+
+ volume = ONE + HALF*dir;
+ cl = volume*(ONE-dir);
+ crl = volume*(right-HALF);
+ crr = volume*(ONEHALF-right);
+
+ for (done=0; done<len; done++)
+ {
+ register PAN_FLOAT ibuf0, ibuf1;
+
+ ibuf0 = HALF*ibuf[0] + HALF*ibuf[2];
+ ibuf1 = HALF*ibuf[1] + HALF*ibuf[3];
+
+ obuf[0] = clip(pan, cl * ibuf0);
+ obuf[1] = clip(pan, crl * ibuf0 + crr * ibuf1);
+ obuf += 2;
+ ibuf += 4;
+ }
+ }
+ break;
+ default:
+ UNEXPECTED_CHANNELS;
+ break;
+ } /* end second switch in channel */
+ break;
+ case 4:
+ switch (ich) {
+ case 1: /* linear */
+ {
+ register PAN_FLOAT cr, cl;
+
+ cl = HALF*left;
+ cr = HALF*right;
+
+ for (done=0; done<len; done++)
+ {
+ obuf[0] = clip(pan, cl * ibuf[0]);
+ obuf[2] = obuf[0];
+ obuf[1] = clip(pan, cr * ibuf[0]);
+ ibuf[3] = obuf[1];
+ obuf += 4;
+ ibuf++;
+ }
+ }
+ break;
+ case 2: /* simple linear panorama */
+ if (dir <= ZERO) /* to the left */
+ {
+ register PAN_FLOAT volume, cll, clr, cr;
+
+ volume = HALF - QUARTER*dir;
+ cll = volume * (ONEHALF-left);
+ clr = volume * (left-HALF);
+ cr = volume * (ONE+dir);
+
+ for (done=0; done<len; done++)
+ {
+ obuf[0] = clip(pan, cll * ibuf[0] + clr * ibuf[1]);
+ obuf[2] = obuf[0];
+ obuf[1] = clip(pan, cr * ibuf[1]);
+ ibuf[3] = obuf[1];
+ obuf += 4;
+ ibuf += 2;
+ }
+ }
+ else /* to the right */
+ {
+ register PAN_FLOAT volume, cl, crl, crr;
+
+ volume = HALF + QUARTER*dir;
+ cl = volume * (ONE-dir);
+ crl = volume * (right-HALF);
+ crr = volume * (ONEHALF-right);
+
+ for (done=0; done<len; done++)
+ {
+ obuf[0] = clip(pan, cl * ibuf[0]);
+ obuf[2] = obuf[0];
+ obuf[1] = clip(pan, crl * ibuf[0] + crr * ibuf[1]);
+ ibuf[3] = obuf[1];
+ obuf += 4;
+ ibuf += 2;
+ }
+ }
+ break;
+ case 4:
+ /* maybe I could improve the formula to reverse...
+ also, turn only by quarters.
+ */
+ if (dir <= ZERO) /* to the left */
+ {
+ register PAN_FLOAT cown, cright;
+
+ cright = -dir;
+ cown = ONE + dir;
+
+ for (done=0; done<len; done++)
+ {
+ obuf[0] = clip(pan, cown*ibuf[0] + cright*ibuf[1]);
+ obuf[1] = clip(pan, cown*ibuf[1] + cright*ibuf[3]);
+ obuf[2] = clip(pan, cown*ibuf[2] + cright*ibuf[0]);
+ obuf[3] = clip(pan, cown*ibuf[3] + cright*ibuf[2]);
+ obuf += 4;
+ ibuf += 4;
+ }
+ }
+ else /* to the right */
+ {
+ register PAN_FLOAT cleft, cown;
+
+ cleft = dir;
+ cown = ONE - dir;
+
+ for (done=0; done<len; done++)
+ {
+ obuf[0] = clip(pan, cleft*ibuf[2] + cown*ibuf[0]);
+ obuf[1] = clip(pan, cleft*ibuf[0] + cown*ibuf[1]);
+ obuf[2] = clip(pan, cleft*ibuf[3] + cown*ibuf[2]);
+ obuf[3] = clip(pan, cleft*ibuf[1] + cown*ibuf[3]);
+ obuf += 4;
+ ibuf += 4;
+ }
+ }
+ break;
+ default:
+ UNEXPECTED_CHANNELS;
+ break;
+ } /* end third switch in channel */
+ break;
+ default:
+ UNEXPECTED_CHANNELS;
+ break;
+ } /* end switch out channel */
+
+ 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.
+ */
+int st_pan_stop(effp)
+eff_t effp;
+{
+ pan_t pan = (pan_t) effp->priv;
+ if (pan->clipped) {
+ warn("PAN clipped %d values, maybe adjust volume?",
+ pan->clipped);
+ }
+ return ST_SUCCESS;
+}
--- a/src/smp.c
+++ b/src/smp.c
@@ -85,9 +85,9 @@
ft->loops[i].length =
trailer->loops[i].end - trailer->loops[i].start;
st_readb(ft, &(trailer->loops[i].type));
- ft->loops[i].type = trailer->loops[8].type;
+ ft->loops[i].type = trailer->loops[i].type;
st_readw(ft, &(trailer->loops[i].count));
- ft->loops[8].count = trailer->loops[8].count;
+ ft->loops[i].count = trailer->loops[i].count;
}
for(i = 0; i < 8; i++) { /* read the 8 markers */
if (fread(trailer->markers[i].name, 1, 10, ft->fp) != 10)
--- /dev/null
+++ b/src/vol.c
@@ -1,0 +1,172 @@
+/*
+ * (c) 20/03/2000 Fabien COELHO <fabien@coelho.net> for sox.
+ *
+ * Change volume of sound file, with basic linear amplitude formula.
+ * Pretty redundant with -v general option.
+ * Beware of saturations! clipping is checked and reported.
+ * Cannot handle different number of channels.
+ * Cannot handle rate change.
+ */
+
+#include "st.h"
+
+#include <math.h> /* exp(), sqrt() */
+#include <limits.h> /* LONG_MAX */
+
+/* type used for computations.
+ */
+#ifndef VOL_FLOAT
+#define VOL_FLOAT float
+#define VOL_FLOAT_SCAN "%f"
+#endif
+
+/* constants
+ */
+#define ZERO ((VOL_FLOAT)(0.0e0))
+#define LOG_10_20 ((VOL_FLOAT)(0.1151292546497022842009e0))
+#define ONE ((VOL_FLOAT)(1.0e0))
+#define TWENTY ((VOL_FLOAT)(20.0e0))
+
+#define VOL_USAGE \
+ "Usage: vol gain type" \
+ " (default type=amplitude: 1.0 is constant, <0.0 change phase;" \
+ " type=power 1.0 is constant; type=dB: 0.0 is constant, +6 doubles ampl.)"
+
+typedef struct {
+ VOL_FLOAT gain; /* amplitude gain. */
+ int clipped; /* number of clipped values to report. */
+} * vol_t;
+
+/*
+ * Process options: gain (float) type (amplitude, power, dB)
+ */
+int st_vol_getopts(effp, n, argv)
+eff_t effp;
+int n;
+char **argv;
+{
+ vol_t vol = (vol_t) effp->priv;
+
+ vol->gain = ONE; /* default is no change */
+
+ if (n && (!sscanf(argv[0], VOL_FLOAT_SCAN, &vol->gain)))
+ {
+ fail(VOL_USAGE);
+ return ST_EOF;
+ }
+
+ /* adjust gain depending on type (what a great parser;-) */
+ if (n>1)
+ {
+ switch (argv[1][0])
+ {
+ case 'd': /* decibels to amplitude */
+ case 'D':
+ vol->gain = exp(vol->gain*LOG_10_20);
+ break;
+ case 'p':
+ case 'P': /* power to amplitude, keep phase change */
+ if (vol->gain > ZERO)
+ vol->gain = sqrt(vol->gain);
+ else
+ vol->gain = -sqrt(-vol->gain);
+ break;
+ case 'a': /* amplitude */
+ case 'A':
+ default:
+ break;
+ }
+ }
+
+ return ST_SUCCESS;
+}
+
+/*
+ * Start processing
+ */
+int st_vol_start(effp)
+eff_t effp;
+{
+ vol_t vol = (vol_t) effp->priv;
+
+ if (effp->outinfo.channels != effp->ininfo.channels)
+ {
+ warn("VOL cannot handle different channels (in=%d, out=%d)"
+ " use avg or pan", effp->ininfo.channels, effp->outinfo.channels);
+ }
+
+ if (effp->outinfo.rate != effp->ininfo.rate)
+ {
+ fail("VOL cannot handle different rates (in=%ld, out=%ld)"
+ " use resample or rate", effp->ininfo.rate, effp->outinfo.rate);
+ return ST_EOF;
+ }
+
+ vol->clipped = 0;
+
+ return ST_SUCCESS;
+}
+
+/* conversion. clipping could be smoother at high ends?
+ * this could be a function on its own, with clip count and report
+ * handled by eff_t and caller.
+ */
+static LONG clip(vol_t vol, const VOL_FLOAT v)
+{
+ if (v > LONG_MAX)
+ {
+ vol->clipped++;
+ return LONG_MAX;
+ }
+ else if (v < -LONG_MAX)
+ {
+ vol->clipped++;
+ return -LONG_MAX;
+ }
+ /* else */
+ return (LONG) v;
+}
+
+#ifndef MIN
+#define MIN(s1,s2) ((s1)<(s2)?(s1):(s2))
+#endif
+
+/*
+ * Process data.
+ */
+int st_vol_flow(effp, ibuf, obuf, isamp, osamp)
+eff_t effp;
+LONG *ibuf, *obuf;
+LONG *isamp, *osamp;
+{
+ vol_t vol = (vol_t) effp->priv;
+ register VOL_FLOAT gain = vol->gain;
+ register LONG len;
+
+ len = MIN(*osamp, *isamp);
+
+ /* report back dealt with amount. */
+ *isamp = len; *osamp = len;
+
+ /* quite basic, with clipping */
+ for (;len>0; len--)
+ *obuf++ = clip(vol, gain * *ibuf++);
+
+ return ST_SUCCESS;
+}
+
+/*
+ * Do anything required when you stop reading samples.
+ * Don't close input file!
+ */
+int st_vol_stop(effp)
+eff_t effp;
+{
+ vol_t vol = (vol_t) effp->priv;
+ if (vol->clipped)
+ {
+ warn("VOL clipped %d values, amplitude gain=%f too high...",
+ vol->clipped, vol->gain);
+ }
+ return ST_SUCCESS;
+}