shithub: sox

Download patch

ref: b9cb8289a553aa616c8ecaa450b5c07b0e76d6c7
parent: e1bb8ea335d3517f9b3153bdc56b237847e2c7cd
author: cbagwell <cbagwell>
date: Wed Feb 28 16:05:38 EST 2001

Added new synth effect and nul file handler.  Allows sounds to be
created.

Added new mix program that will mix two seperate audio files into
one.

--- a/Changelog
+++ b/Changelog
@@ -34,6 +34,14 @@
   o With help from David Blythe, updated OSS drivers to use newer format
     interface.  OSS driver will now attempt to detect a valid endian type
     to use with sound card.
+  o Carsten Borchardt pointed out a bug in lowp filter.  Added new
+    nul file handler that reads and writes from/to nothing.
+    Also added new synth effect that creates sounds using a simple
+    synthesizer.  Created a testcd.sh that uses two new features
+    to create a test sound CD for testing audio equipment.
+  o Ben Last added a new program that uses libst and will merge two
+    seperate audio files into a single file with multiple channels.
+    Written for sox 12.16 so doesn't work with 12.17.2 yet.
 
 sox-12.17.1
 -----------
--- a/Makefile.dos
+++ b/Makefile.dos
@@ -12,7 +12,7 @@
 
 FOBJ	= 8svx.obj adpcm.obj aiff.obj alsa.obj au.obj auto.obj avr.obj cdr.obj \
 	  cvsd.obj dat.obj g721.obj g723_24.obj g723_40.obj g72x.obj gsm.obj \
-	  hcom.obj ima_rw.obj maud.obj oss.obj raw.obj sf.obj smp.obj \
+	  hcom.obj ima_rw.obj maud.obj nul.obj oss.obj raw.obj sf.obj smp.obj \
 	  sndrtool.obj sphere.obj sunaudio.obj tx16w.obj voc.obj \
 	  wav.obj wve.obj
 
@@ -22,7 +22,7 @@
           lowp.obj lowpass.obj map.obj mask.obj phaser.obj pick.obj \
 	  pitch.obj pan.obj polyphase.obj rate.obj resample.obj reverb.obj \
 	  reverse.obj speed.obj split.obj stat.obj stretch.obj \
-	  swap.obj trim.obj vibro.obj vol.obj
+	  swap.obj synth.obj trim.obj vibro.obj vol.obj
 
 LIBOBJS   = $(FOBJ) $(EOBJ) handlers.obj libst.obj misc.obj getopt.obj util.obj
 
@@ -66,14 +66,20 @@
 #       $(CC) $(CFLAGS) $*.c
 #       $(LDD) libst -+$*,,
 
-all: sox.exe
+all: sox.exe mix.exe
 
 sox.exe: sox.obj libst.lib
         $(CC) $(LFLAGS) -L$(LIBDIR) sox.obj libst.lib
 
+mix.exe: mix.obj libst.lib
+        $(CC) $(LFLAGS) -L$(LIBDIR) mix.obj libst.lib
+
 libst.lib: $(LIBOBJS)
 
 sox.obj: sox.c st.h
+        $(CC) $(CFLAGS) -I$(INCDIR) -L$(LIBDIR) $*.c
+
+mix.obj: mix.c st.h
         $(CC) $(CFLAGS) -I$(INCDIR) -L$(LIBDIR) $*.c
 
 clean:
--- a/Makefile.gcc
+++ b/Makefile.gcc
@@ -30,14 +30,14 @@
 
 FOBJ	= 8svx.o adpcm.o aiff.o alsa.o au.o auto.o avr.o cdr.o cvsd.o dat.o \
 	  g721.o g723_24.o g723_40.o g72x.o gsm.o hcom.o ima_rw.o maud.o \
-	  oss.o raw.o sf.o smp.o sndrtool.o sphere.o sunaudio.o tx16w.o \
-	  voc.o wav.o wve.o
+	  nul.o oss.o raw.o sf.o smp.o sndrtool.o sphere.o sunaudio.o \
+	  tx16w.o voc.o wav.o wve.o
 
 EOBJ    = avg.o band.o bandpass.o breject.o btrworth.o chorus.o compand.o \
           copy.o cut.o deemphas.o earwax.o echo.o echos.o fade.o filter.o \
           flanger.o highp.o highpass.o lowp.o lowpass.o map.o mask.o pan.o \
           phaser.o pick.o pitch.o polyphas.o rate.o resample.o reverb.o \
-          reverse.o speed.o split.o stat.o stretch.o swap.o \
+          reverse.o speed.o split.o stat.o stretch.o swap.o synth.o \
 	  trimo vibro.o vol.o
 
 SOUNDLIB = libst.a
@@ -188,10 +188,13 @@
 
 CFLAGS  = $O $(SOX_DEFINES) $(SOX_INCLUDES)
 
-all: sox
+all: sox mix
 
 sox: sox.o $(SOUNDLIB)
 	$(CC) $(CFLAGS) -o sox sox.o $(SOUNDLIB) $(SOX_PRE_LIBS) $(SOX_POST_LIBS)
+
+mix: mix.o $(SOUNDLIB)
+	$(CC) $(CFLAGS) -o mix mix.o $(SOUNDLIB) $(SOX_PRE_LIBS) $(SOX_POST_LIBS)
 
 $(SOUNDLIB): $(LIBOBJS)
 	$(RM) $(SOUNDLIB)
--- a/README
+++ b/README
@@ -37,6 +37,7 @@
   o Psion (palmtop) A-law WVE files
   o Pseudo-file fomats that allow direct playing/recording
     from some audio devices under unix.
+  o Pseudo-nul file that reads and writes from/to nowhere
 
 The sound effects include:
 
@@ -65,10 +66,11 @@
   o Reverse the sound samples (to search for Satanic messages ;-)
   o Change the speed of samples being played (like speeding up the motor
     on a tape recorder)
-  o Stretch/shorten the duration of a sound file.
   o Convert from mono to stereo
-  o Swap stereo channels
   o Display general stats on a sound sample
+  o Stretch/shorten the duration of a sound file.
+  o Swap stereo channels
+  o Create sounds with a simple synthesizer.
   o Trim audio data from beginning and end of file.
   o Add the world-famous Fender Vibro-Champ effect
   o Adjust volume of samples
--- /dev/null
+++ b/mix.c
@@ -1,0 +1,565 @@
+/*
+ * Mix - the audiofile mixing program of SOX
+ *
+ * This is the main function for the command line sox program.
+ *
+ * January 26, 2001
+ * Copyright 2001 ben last, Lance Norskog And Sundry Contributors
+ * This source code is freely redistributable and may be used for
+ * any purpose.  This copyright notice must be maintained. 
+ * ben last, Lance Norskog And Sundry Contributors are not responsible
+ * for the consequences of using this software.
+ *
+ * Change History:
+ *
+ * January 26, 2001 - ben last (ben@benlast.com)
+ *   Derived mix.c from sox.c and then rewrote it for
+ *   mix functionality.
+ */ 
+
+/* FIXME: Quickly ported from 12.16 to 12.17.2... Make sure all st_*
+ * functions are checking return values!
+ */
+
+#include "st.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>		/* for malloc() */
+#ifdef HAVE_MALLOC_H
+#include <malloc.h>
+#endif
+#include <errno.h>
+#include <sys/types.h>		/* for fstat() */
+#include <sys/stat.h>		/* for fstat() */
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>		/* for unlink() */
+#endif
+
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#else
+#ifndef HAVE_GETOPT
+int getopt(int,char **,char *);
+extern char *optarg;
+extern int optind;
+#endif
+#endif
+
+#ifdef VMS
+#include <perror.h>
+#define LASTCHAR        ']'
+#else
+#define LASTCHAR        '/'
+#endif
+
+/*
+ * SOX mix main program.
+ */
+
+static int dovolume = 0;	/* User wants volume change */
+static double volume = 1.0;	/* Linear volume change */
+static int clipped = 0;		/* Volume change clipping errors */
+static int writing = 0;		/* are we writing to a file? */
+
+void init();
+void doopts(int, char **);
+void usage(char *);
+int filetype(int);
+void process();
+void statistics();
+LONG volumechange();
+void checkeffect(eff_t);
+int flow_effect(int);
+int drain_effect(int);
+
+struct st_soundstream informat, mixformat, outformat;
+
+static ft_t ft;
+
+char *ifile, *mfile, *ofile, *itype, *mtype, *otype;
+extern char *optarg;
+extern int optind;
+
+int main(argc, argv)
+int argc;
+char **argv;
+{
+	myname = argv[0];
+	init();
+	
+	ifile = mfile = ofile = NULL;
+
+	/* Get input format options */
+	ft = &informat;
+	doopts(argc, argv);
+	/* Get input file */
+	if (optind >= argc)
+		usage("No input file?");
+	
+	ifile = argv[optind];
+	if (! strcmp(ifile, "-"))
+		ft->fp = stdin;
+	else if ((ft->fp = fopen(ifile, READBINARY)) == NULL)
+		st_fail("Can't open input file '%s': %s", 
+			ifile, strerror(errno));
+	ft->filename = ifile;
+	optind++;
+
+	/* Get mix format options */
+	ft = &mixformat;
+	doopts(argc, argv);
+	/* Get mix file */
+	if (optind >= argc)
+		usage("No mix file?");
+	
+	mfile = argv[optind];
+	if (! strcmp(mfile, "-"))
+		ft->fp = stdin;
+	else if ((ft->fp = fopen(mfile, READBINARY)) == NULL)
+		st_fail("Can't open mix file '%s': %s", 
+			mfile, strerror(errno));
+	ft->filename = mfile;
+	optind++;
+
+	/* Get output format options */
+	ft = &outformat;
+	doopts(argc, argv);
+	writing = 1;
+	if (writing) {
+	    /* Get output file */
+	    if (optind >= argc)
+		usage("No output file?");
+	    ofile = argv[optind];
+	    ft->filename = ofile;
+	    /*
+	     * There are two choices here:
+	     *	1) stomp the old file - normal shell "> file" behavior
+	     *	2) fail if the old file already exists - csh mode
+	     */
+	    if (! strcmp(ofile, "-"))
+	    {
+		ft->fp = stdout;
+
+		/* stdout tends to be line-buffered.  Override this */
+		/* to be Full Buffering. */
+		if (setvbuf (ft->fp,NULL,_IOFBF,sizeof(char)*BUFSIZ))
+		    st_fail("Can't set write buffer");
+	    }
+	    else {
+
+		ft->fp = fopen(ofile, WRITEBINARY);
+
+		if (ft->fp == NULL)
+		    st_fail("Can't open output file '%s': %s", 
+			 ofile, strerror(errno));
+
+		/* stdout tends to be line-buffered.  Override this */
+		/* to be Full Buffering. */
+		if (setvbuf (ft->fp,NULL,_IOFBF,sizeof(char)*BUFSIZ))
+		    st_fail("Can't set write buffer");
+
+	    } /* end of else != stdout */
+	    
+	    /* Move past filename */
+	    optind++;
+	} /* end if writing */
+
+	/* Check global arguments */
+	if (volume <= 0.0)
+		st_fail("Volume must be greater than 0.0");
+	
+#if	defined(DUMB_FILESYSETM)
+	informat.seekable  = 0;
+	mixformat.seekable  = 0;
+	outformat.seekable = 0;
+#else
+	informat.seekable  = (filetype(fileno(informat.fp)) == S_IFREG);
+	mixformat.seekable  = (filetype(fileno(mixformat.fp)) == S_IFREG);
+	outformat.seekable = (filetype(fileno(outformat.fp)) == S_IFREG); 
+#endif
+
+	/* If file types have not been set with -t, set from file names. */
+	if (! informat.filetype) {
+		if ((informat.filetype = strrchr(ifile, LASTCHAR)) != NULL)
+			informat.filetype++;
+		else
+			informat.filetype = ifile;
+		if ((informat.filetype = strrchr(informat.filetype, '.')) != NULL)
+			informat.filetype++;
+		else /* Default to "auto" */
+			informat.filetype = "auto";
+	}
+	if (! mixformat.filetype) {
+		if ((mixformat.filetype = strrchr(mfile, LASTCHAR)) != NULL)
+			mixformat.filetype++;
+		else
+			mixformat.filetype = mfile;
+		if ((mixformat.filetype = strrchr(mixformat.filetype, '.')) != NULL)
+			mixformat.filetype++;
+		else /* Default to "auto" */
+			mixformat.filetype = "auto";
+	}
+	if (writing && ! outformat.filetype) {
+		if ((outformat.filetype = strrchr(ofile, LASTCHAR)) != NULL)
+			outformat.filetype++;
+		else
+			outformat.filetype = ofile;
+		if ((outformat.filetype = strrchr(outformat.filetype, '.')) != NULL)
+			outformat.filetype++;
+	}
+	/* Default the input and mix comments to filenames. 
+	 * The output comment will be assigned when the informat 
+	 * structure is copied to the outformat. 
+	 */
+	informat.comment = informat.filename;
+	mixformat.comment = mixformat.filename;
+
+	process();
+	statistics();
+	return(0);
+}
+
+#ifdef HAVE_GETOPT_H
+char *getoptstr = "+r:v:t:c:phsuUAagbwlfdDxV";
+#else
+char *getoptstr = "r:v:t:c:phsuUAagbwlfdDxV";
+#endif
+
+void doopts(int argc, char **argv)
+{
+	int c;
+	char *str;
+
+	while ((c = getopt(argc, argv, getoptstr)) != -1) {
+		switch(c) {
+		case 'h':
+			usage((char *)0);
+			/* no return from above */
+
+		case 't':
+			if (! ft) usage("-t");
+			ft->filetype = optarg;
+			if (ft->filetype[0] == '.')
+				ft->filetype++;
+			break;
+
+		case 'r':
+			if (! ft) usage("-r");
+			str = optarg;
+#ifdef __alpha__
+			if ((! sscanf(str, "%u", &ft->info.rate)) ||
+					(ft->info.rate <= 0))
+#else
+			if ((! sscanf(str, "%lu", &ft->info.rate)) ||
+					(ft->info.rate <= 0))
+#endif
+				st_fail("-r must be given a positive integer");
+			break;
+		case 'v':
+			if (! ft) usage("-v");
+			str = optarg;
+			if ((! sscanf(str, "%lf", &volume)) ||
+					(volume <= 0))
+				st_fail("Volume value '%s' is not a number",
+					optarg);
+			dovolume = 1;
+			break;
+
+		case 'c':
+			if (! ft) usage("-c");
+			str = optarg;
+			if (! sscanf(str, "%d", &ft->info.channels))
+				st_fail("-c must be given a number");
+			break;
+		case 'b':
+			if (! ft) usage("-b");
+			ft->info.size = ST_SIZE_BYTE;
+			break;
+		case 'w':
+			if (! ft) usage("-w");
+			ft->info.size = ST_SIZE_WORD;
+			break;
+		case 'l':
+			if (! ft) usage("-l");
+			ft->info.size = ST_SIZE_DWORD;
+			break;
+		case 'f':
+			if (! ft) usage("-f");
+			ft->info.size = ST_SIZE_FLOAT;
+			break;
+		case 'd':
+			if (! ft) usage("-d");
+			ft->info.size = ST_SIZE_DOUBLE;
+			break;
+		case 'D':
+			if (! ft) usage("-D");
+			ft->info.size = ST_SIZE_IEEE;
+			break;
+
+		case 's':
+			if (! ft) usage("-s");
+			ft->info.encoding = ST_ENCODING_SIGN2;
+			break;
+		case 'u':
+			if (! ft) usage("-u");
+			ft->info.encoding = ST_ENCODING_UNSIGNED;
+			break;
+		case 'U':
+			if (! ft) usage("-U");
+			ft->info.encoding = ST_ENCODING_ULAW;
+			break;
+		case 'A':
+			if (! ft) usage("-A");
+			ft->info.encoding = ST_ENCODING_ALAW;
+			break;
+		case 'a':
+			if (! ft) usage("-a");
+			ft->info.encoding = ST_ENCODING_ADPCM;
+			break;
+		case 'g':
+			if (! ft) usage("-g");
+			ft->info.encoding = ST_ENCODING_GSM;
+			break;
+		
+		case 'x':
+			if (! ft) usage("-x");
+			ft->swap = 1;
+			break;
+		
+		case 'V':
+			verbose = 1;
+			break;
+		}
+	}
+}
+
+void init() {
+
+	/* init files */
+	informat.info.rate      = mixformat.info.rate = outformat.info.rate  = 0;
+	informat.info.size      = mixformat.info.size = outformat.info.size  = -1;
+	informat.info.encoding  = mixformat.info.encoding = outformat.info.encoding = -1;
+	informat.info.channels  = mixformat.info.channels = outformat.info.channels = -1;
+	informat.comment   = mixformat.comment = outformat.comment = NULL;
+	informat.swap      = mixformat.swap = 0;
+	informat.filetype  = mixformat.filetype = outformat.filetype  = (char *) 0;
+	informat.fp        = stdin;
+	mixformat.fp       = NULL;
+	outformat.fp       = stdout;
+	informat.filename  = "input";
+	mixformat.filename  = "mix";
+	outformat.filename = "output";
+}
+
+/* 
+ * Process input file -> mix with mixfile -> output file
+ *	one buffer at a time
+ */
+
+void process() {
+    LONG result, i, *ibuf, *mbuf, *obuf, ilen=0, mlen=0, olen=0;
+
+    st_gettype(&informat);
+    st_gettype(&mixformat);
+    if (writing)
+	st_gettype(&outformat);
+    
+    /* Read and write starters can change their formats. */
+    (* informat.h->startread)(&informat);
+    st_checkformat(&informat);
+    
+    (* mixformat.h->startread)(&mixformat);
+    st_checkformat(&mixformat);
+    
+    if (dovolume)
+	st_report("Volume factor: %f\n", volume);
+    
+    st_report("Input file: using sample rate %lu\n\tsize %s, encoding %s, %d %s",
+	   informat.info.rate, st_sizes_str[informat.info.size], 
+	   st_encodings_str[informat.info.encoding], informat.info.channels, 
+	   (informat.info.channels > 1) ? "channels" : "channel");
+    if (informat.comment)
+	st_report("Input file: comment \"%s\"\n", informat.comment);
+	
+    st_report("Mix file: using sample rate %lu\n\tsize %s, encoding %s, %d %s",
+	   mixformat.info.rate, st_sizes_str[mixformat.info.size], 
+	   st_encodings_str[mixformat.info.encoding], mixformat.info.channels, 
+	   (mixformat.info.channels > 1) ? "channels" : "channel");
+    if (mixformat.comment)
+	st_report("Mix file: comment \"%s\"\n", mixformat.comment);
+
+/*
+	Expect the formats of the input and mix files to be compatible.
+	Although it's true that I could fix it up on the fly, it's easier
+	for me to tell the user to use sox to fix it first and it's also
+	more reliable; better to use the code that Chris, Lance & Co have
+	written well than for me to rewrite the same stuff badly!
+
+	Sample rates are a cause for failure.
+*/
+    if(informat.info.rate != mixformat.info.rate)
+        st_fail("fail: Input and mix files have different sample rates.\nUse sox to resample one of them.\n");
+
+    /* need to check EFF_REPORT */
+    if (writing) {
+	st_copyformat(&informat, &outformat);
+	(* outformat.h->startwrite)(&outformat);
+	st_checkformat(&outformat);
+	st_report("Output file: using sample rate %lu\n\tsize %s, encoding %s, %d %s",
+	       outformat.info.rate, st_sizes_str[outformat.info.size], 
+	       st_encodings_str[outformat.info.encoding], outformat.info.channels, 
+	       (outformat.info.channels > 1) ? "channels" : "channel");
+	if (outformat.comment)
+	    st_report("Output file: comment \"%s\"\n", outformat.comment);
+    }
+
+    /* Allocate buffers */
+    ibuf = (LONG *) malloc(BUFSIZ * sizeof(LONG));
+    mbuf = (LONG *) malloc(BUFSIZ * sizeof(LONG));
+    obuf = (LONG *) malloc(BUFSIZ * sizeof(LONG));
+    if((!ibuf) || (!mbuf) || (!obuf))
+    {
+	fprintf(stderr, "Can't allocate memory for buffer (%s)\n", 
+		strerror(errno));
+	return;
+    }
+
+    /* Read initial chunks of input data. */
+    /* Do the input file first */
+    ilen = (*informat.h->read)(&informat, ibuf, (LONG) BUFSIZ);
+    /* Change the volume of this data if needed. */
+    if(dovolume && ilen)
+	for (i = 0; i < ilen; i++)
+	    ibuf[i] = volumechange(ibuf[i]);
+
+    /* Now do the mixfile */
+    mlen = (*mixformat.h->read)(&mixformat, mbuf, (LONG) BUFSIZ);
+    /* Change the volume of this data if needed. */
+    if(dovolume && mlen)
+	for (i = 0; i < mlen; i++)
+	    mbuf[i] = volumechange(mbuf[i]);
+
+    /* mix until both input files are done */
+    while (ilen || mlen) {
+	/* Do the mixing from ibuf and mbuf into obuf. */
+	olen = (ilen > mlen) ? ilen : mlen;
+	for (i=0; i<olen; i++)
+	{
+	    /* initial crude mix that might lose a bit of accuracy */
+	    result = (i<ilen) ? (ibuf[i]/2) : 0;
+	    if (i<mlen) result += mbuf[i]/2;
+	    obuf[i] = result;
+	}
+
+	if (writing && olen)
+	    (* outformat.h->write)(&outformat, obuf, (LONG) olen);
+
+	/* Read another chunk of input data. */
+	ilen = (*informat.h->read)(&informat, 
+		ibuf, (LONG) BUFSIZ);
+
+	/* Change volume of these samples if needed. */
+	if(dovolume && ilen)
+	    for (i = 0; i < ilen; i++)
+		ibuf[i] = volumechange(ibuf[i]);
+
+	/* Read another chunk of mix data. */
+	mlen = (*mixformat.h->read)(&mixformat, 
+		mbuf, (LONG) BUFSIZ);
+
+	/* Change volume of these samples if needed. */
+	if(dovolume && mlen)
+	    for (i = 0; i < mlen; i++)
+		mbuf[i] = volumechange(mbuf[i]);
+    }
+
+    (* informat.h->stopread)(&informat);
+    fclose(informat.fp);
+
+    (* mixformat.h->stopread)(&mixformat);
+    fclose(mixformat.fp);
+
+    if (writing)
+        (* outformat.h->stopwrite)(&outformat);
+    if (writing)
+        fclose(outformat.fp);
+}
+
+/* Guido Van Rossum fix */
+void statistics() {
+	if (dovolume && clipped > 0)
+		st_report("Volume change clipped %d samples", clipped);
+}
+
+LONG volumechange(y)
+LONG y;
+{
+	double y1;
+
+	y1 = y * volume;
+	if (y1 < -2147483647.0) {
+		y1 = -2147483647.0;
+		clipped++;
+	}
+	else if (y1 > 2147483647.0) {
+		y1 = 2147483647.0;
+		clipped++;
+	}
+
+	return y1;
+}
+
+int filetype(fd)
+int fd;
+{
+	struct stat st;
+
+	fstat(fd, &st);
+
+	return st.st_mode & S_IFMT;
+}
+
+char *usagestr = 
+"[ gopts ] [ fopts ] ifile [ fopts ] mixfile [ fopts ] ofile";
+
+void usage(opt)
+char *opt;
+{
+    int i;
+    
+	fprintf(stderr, "%s: ", myname);
+	if (verbose || !opt)
+		fprintf(stderr, "%s\n\n", st_version());
+	fprintf(stderr, "Usage: %s\n\n", usagestr);
+	if (opt)
+		fprintf(stderr, "Failed at: %s\n", opt);
+	else {
+	    fprintf(stderr,"gopts: -e -h -p -v volume -V\n\n");
+	    fprintf(stderr,"fopts: -r rate -c channels -s/-u/-U/-A/-a/-g -b/-w/-l/-f/-d/-D -x\n\n");
+	    fprintf(stderr, "Supported file s: ");
+	    for (i = 0; st_formats[i].names != NULL; i++) {
+		/* only print the first name */
+		fprintf(stderr, "%s ", st_formats[i].names[0]);
+	    }
+	    fputc('\n', stderr);
+	}
+	exit(1);
+}
+
+
+/* called from util.c:fail */
+void cleanup() {
+	/* Close the input file and outputfile before exiting*/
+	if (informat.fp)
+		fclose(informat.fp);
+	if (mixformat.fp)
+		fclose(mixformat.fp);
+	if (outformat.fp) {
+		fclose(outformat.fp);
+		/* remove the output file because we failed, if it's ours. */
+		/* Don't if its not a regular file. */
+		if (filetype(fileno(outformat.fp)) == S_IFREG)
+		    REMOVE(outformat.filename);
+	}
+}
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -43,14 +43,14 @@
 # Objects.
 
 FOBJ	= 8svx.o adpcm.o aiff.o au.o auto.o avr.o cdr.o cvsd.o dat.o g721.o \
-	  g723_24.o g723_40.o g72x.o gsm.o hcom.o ima_rw.o maud.o raw.o \
-	  sf.o smp.o sndrtool.o sphere.o tx16w.o voc.o wav.o wve.o
+	  g723_24.o g723_40.o g72x.o gsm.o hcom.o ima_rw.o maud.o nul.o \
+	  raw.o sf.o smp.o sndrtool.o sphere.o tx16w.o voc.o wav.o wve.o
 
 EOBJ	= avg.o band.o bandpass.o breject.o btrworth.o chorus.o compand.o \
 	  copy.o cut.o deemphas.o earwax.o echo.o echos.o fade.o filter.o \
 	  flanger.o highp.o highpass.o lowp.o lowpass.o map.o mask.o pan.o \
 	  phaser.o pick.o pitch.o polyphas.o rate.o resample.o reverb.o \
-	  reverse.o speed.o split.o stat.o stretch.o swap.o trim.o \
+	  reverse.o speed.o split.o stat.o stretch.o swap.o synth.o trim.o \
 	  vibro.o vol.o
 
 OSSOBJ_0    =
@@ -68,10 +68,13 @@
 PLAY_0    =
 PLAY_1    = play
 
-all: sox $(PLAY_$(PLAY_SUPPORT))
+all: sox mix $(PLAY_$(PLAY_SUPPORT))
 
 sox: libst.a sox.o
 	$(CC) $(LDFLAGS) -o sox sox.o $(LIBS)
+
+mix: libst.a mix.o
+	$(CC) $(LDFLAGS) -o mix mix.o $(LIBS)
 
 play: play.in
 	$(SED) -e 's|@PREFIX@|$(BINDIR)|g' < play.in > play
--- a/src/handlers.c
+++ b/src/handlers.c
@@ -161,6 +161,18 @@
 extern int  st_maudstartwrite();
 extern int  st_maudstopwrite();
 
+static char *nulnames[] = {
+        "nul",
+        (char *) 0,
+};
+
+extern int  st_nulstartread();
+extern LONG st_nulread();
+extern int  st_nulstopread();
+extern LONG st_nulwrite();
+extern int  st_nulstartwrite();
+extern int  st_nulstopwrite();
+
 #if	defined(OSS_PLAYER)
 static char *ossdspnames[] = {
 	"ossdsp",
@@ -394,6 +406,9 @@
         {maudnames, ST_FILE_STEREO,    		/* Amiga MAUD */
 		st_maudstartread, st_maudread, st_maudstopread,
 		st_maudstartwrite, st_maudwrite, st_maudstopwrite, st_nothing},
+        {nulnames, ST_FILE_STEREO,    		/* NUL */
+ 		st_nulstartread, st_nulread, st_nulstopread,
+ 		st_nulstartwrite, st_nulwrite, st_nulstopwrite},
 #if	defined(OSS_PLAYER)
 	{ossdspnames, ST_FILE_STEREO,		/* OSS /dev/dsp player */
 		st_ossdspstartread, st_rawread, st_rawstopread,
@@ -641,6 +656,12 @@
 extern int st_swap_drain();
 extern int st_swap_stop();
 
+extern int st_synth_getopts(); 
+extern int st_synth_start();
+extern int st_synth_flow();
+extern int st_synth_drain();
+extern int st_synth_stop();
+
 extern int st_vibro_getopts();
 extern int st_vibro_start();
 extern int st_vibro_flow();
@@ -772,6 +793,9 @@
 	{"swap", ST_EFF_MCHAN,
 		st_swap_getopts, st_swap_start, st_swap_flow, 
 		st_swap_drain, st_swap_stop},
+        {"synth", ST_EFF_MCHAN, 
+                st_synth_getopts, st_synth_start, st_synth_flow, 
+                st_synth_drain, st_synth_stop},
 	{"vibro", 0, 
 		st_vibro_getopts, st_vibro_start, st_vibro_flow, 
 		st_null_drain, st_nothing},
--- a/src/lowp.c
+++ b/src/lowp.c
@@ -98,7 +98,7 @@
 		    d = -2147483647L;
 		else if (d > 2147483647L)
 		    d = 2147483647L;
-		lowp->outm1 = l;
+		lowp->outm1 = d;
 		*obuf++ = d;
 	}
 	*isamp = len;
--- /dev/null
+++ b/src/synth.c
@@ -1,0 +1,788 @@
+/*
+ * synth - Synthesizer Effect.  
+ *
+ * Written by Carsten Borchardt Jan 2001
+ * Version 0.1
+ *
+ * This source code is freely redistributable and may be used for
+ * any purpose.  This copyright notice must be maintained. 
+ * The authors are not responsible for 
+ * the consequences of using this software.
+ */
+
+#include <signal.h>
+#include <string.h>
+#include <limits.h>
+#include <math.h>
+#include <ctype.h>
+#include "st.h"
+
+#define USSTR ""\
+"Usage:synth [length] type mix [freq[-freq2]] [off] [ph] [p1] [p2] [p3]\n"\
+"   <length> length in sec or hh:mm:ss.frac, 0=inputlength, default=0\n"\
+"   <type>   is sine, square, triangle, sawtooth, trapetz, exp,\n"\
+"               whitenoise, pinknoise, brownnoise, default=sine\n"\
+"   <mix>    is create, mix, amod, default=create\n"\
+"   <freq>   frequency at beginning in Hz, not used  for noise..\n"\
+"   <freq2>  frequency at end in Hz, not used for noise..\n"\
+"            <freq/2> can be given as %%n, where 'n' is the number of\n"\
+"            half notes in respect to A (440Hz)\n"\
+"   <off>    Bias (DC-offset)  of signal in percent, default=0\n"\
+"   <ph>     phase shift 0..100 shift phase 0..2*Pi, not used for noise..\n"\
+"   <p1>     square: Ton/Toff, triangle+trapetz: rising slope time (0..100)\n"\
+"   <p2>     trapetz: ON time (0..100)\n"\
+"   <p3>     trapetz: falling slope position (0..100)"
+
+#define PCOUNT 5
+
+#define SYNTH_SINE       0
+#define SYNTH_SQUARE     1
+#define SYNTH_SAWTOOTH   2
+#define SYNTH_TRIANGLE   3
+#define SYNTH_TRAPETZ    4
+#define SYNTH_WHITENOISE 5
+#define SYNTH_PINKNOISE  6
+#define SYNTH_BROWNNOISE 7
+#define SYNTH_VOICENOISE 8
+#define SYNTH_EXP        9
+
+#define SYNTH_CREATE    0x000
+#define SYNTH_MIX       0x100
+#define SYNTH_AMOD      0x200
+#define SYNTH_FMOD      0x400
+/* do not ask me for the colored noise, i copied the 
+ * algorithm somewhere...
+ */
+#define BROWNNOISE_FAC  (500.0/32768.0)
+#define PINKNOISE_FAC   (5000.0/32768.0)
+#define LOG_10_20     0.1151292546497022842009e0
+
+/*#define TIMERES 1000*/
+#define MAXCHAN 4
+
+
+/******************************************************************************
+ * start of pink noise generator stuff
+ * algorithm stolen from:
+ * Author: Phil Burk, http://www.softsynth.com
+ */
+
+  
+/* Calculate pseudo-random 32 bit number based on linear congruential method. */
+static unsigned long GenerateRandomNumber( void )
+{
+	static unsigned long randSeed = 22222;  /* Change this for different random sequences. */
+	randSeed = (randSeed * 196314165) + 907633515;
+	return randSeed;
+}
+
+#define PINK_MAX_RANDOM_ROWS   (30)
+#define PINK_RANDOM_BITS       (24)
+#define PINK_RANDOM_SHIFT      ((sizeof(long)*8)-PINK_RANDOM_BITS)
+
+typedef struct{
+    long      pink_Rows[PINK_MAX_RANDOM_ROWS];
+    long      pink_RunningSum;   /* Used to optimize summing of generators. */
+    int       pink_Index;        /* Incremented each sample. */
+    int       pink_IndexMask;    /* Index wrapped by ANDing with this mask. */
+    float     pink_Scalar;       /* Used to scale within range of -1.0 to +1.0 */
+} PinkNoise;
+
+/* Setup PinkNoise structure for N rows of generators. */
+void InitializePinkNoise( PinkNoise *pink, int numRows )
+{
+	int i;
+	long pmax;
+	pink->pink_Index = 0;
+	pink->pink_IndexMask = (1<<numRows) - 1;
+/* Calculate maximum possible signed random value. Extra 1 for white noise always added. */
+	pmax = (numRows + 1) * (1<<(PINK_RANDOM_BITS-1));
+	pink->pink_Scalar = 1.0f / pmax;
+/* Initialize rows. */
+	for( i=0; i<numRows; i++ ) pink->pink_Rows[i] = 0;
+	pink->pink_RunningSum = 0;
+}
+
+/* Generate Pink noise values between -1.0 and +1.0 */
+float GeneratePinkNoise( PinkNoise *pink )
+{
+	long newRandom;
+	long sum;
+	float output;
+
+/* Increment and mask index. */
+	pink->pink_Index = (pink->pink_Index + 1) & pink->pink_IndexMask;
+
+/* If index is zero, don't update any random values. */
+	if( pink->pink_Index != 0 )
+	{
+	/* Determine how many trailing zeros in PinkIndex. */
+	/* This algorithm will hang if n==0 so test first. */
+		int numZeros = 0;
+		int n = pink->pink_Index;
+		while( (n & 1) == 0 )
+		{
+			n = n >> 1;
+			numZeros++;
+		}
+
+	/* Replace the indexed ROWS random value.
+	 * Subtract and add back to RunningSum instead of adding all the random
+	 * values together. Only one changes each time.
+	 */
+		pink->pink_RunningSum -= pink->pink_Rows[numZeros];
+		newRandom = ((long)GenerateRandomNumber()) >> PINK_RANDOM_SHIFT;
+		pink->pink_RunningSum += newRandom;
+		pink->pink_Rows[numZeros] = newRandom;
+	}
+	
+/* Add extra white noise value. */
+	newRandom = ((long)GenerateRandomNumber()) >> PINK_RANDOM_SHIFT;
+	sum = pink->pink_RunningSum + newRandom;
+
+/* Scale to range of -1.0 to 0.9999. */
+	output = pink->pink_Scalar * sum;
+
+	return output;
+}
+
+/**************** end of pink noise stuff */
+
+
+
+/* Private data for the synthesizer */
+typedef struct synthstuff {
+    /* options */
+    double time; /* length in sec */
+    int type[MAXCHAN];
+    int mix[MAXCHAN];
+    double freq[MAXCHAN];
+    double freq2[MAXCHAN];
+    double par[MAXCHAN][5];
+
+    /* internal stuff */
+    LONG max;
+    LONG samples_done;
+    int rate;
+    LONG length; /* length in number of samples */
+    double h[MAXCHAN]; /* store values necessary for  creation */
+    PinkNoise pinkn[MAXCHAN];
+} *synth_t;
+
+
+/* a note is given as an int,
+ * 0   => 440 Hz = A
+ * >0  => number of half notes 'up', 
+ * <0  => number of half notes down,
+ * example 12 => A of next octave, 880Hz
+ *
+ * calculated by freq = 440Hz * 2**(note/12)
+ */
+static double calc_note_freq(double note){
+    return (440.0 * pow(2,note/12.0));
+}
+
+
+/* read string 's' and convert to frequency
+ * 's' can be a positive number which is the frequency in Hz
+ * if 's' starts with a hash '%' and a following number the corresponding
+ * note is calculated
+ * return -1 on error
+ */ 
+static double StringToFreq(char *s, char **h){
+    double f;
+
+    if(*s=='%'){
+	f = strtod(s+1,h);
+	if ( *h == s+1 ){ 
+	    /* error*/
+	    return -1.0;
+	}
+	f=calc_note_freq(f);
+    }else{
+	f=strtod(s,h);
+	if(*h==s){
+	    return -1.0;
+	}
+    }
+    if( f < 0.0 )
+	return -1.0;
+    return f;
+}
+
+
+
+static void parmcopy(synth_t sy, int s, int d){
+    int i;
+    sy->freq[d]=sy->freq[s];
+    sy->freq2[d]=sy->freq2[s];
+    sy->type[d]=sy->type[s];
+    sy->mix[d]=sy->mix[s];
+    for(i=0;i<PCOUNT;i++){
+	sy->par[d][i]=sy->par[s][i];
+    }
+}
+
+
+/*
+ * Process options
+ *
+ * Don't do initialization now.
+ * The 'info' fields are not yet filled in.
+ */
+int st_synth_getopts(effp, n, argv) 
+eff_t effp;
+int n;
+char **argv;
+{
+    int argn;
+    char *usstr=USSTR;
+    char *hlp;
+    int i;
+    int c;
+    synth_t synth = (synth_t) effp->priv;
+    
+
+    /* set default parameters */
+    synth->length = 0; /* use length of input file */
+    synth->time = 0.0;
+    synth->max = LONG_MAX;
+    for(c=0;c<MAXCHAN;c++){
+	synth->freq[c] = 440.0;
+	synth->freq2[c] = 440.0;
+	synth->type[c]=SYNTH_SINE; 
+	synth->mix[c] = SYNTH_CREATE;
+    
+	for(i=0;i<PCOUNT;i++)
+	    synth->par[c][i]= -1.0;
+	
+	synth->par[c][0]= 0.0; /* offset */
+	synth->par[c][1]= 0.0; /* phase */;
+    }
+
+    argn=0;
+    if ( n<0){
+	st_fail(usstr);
+	return(ST_EOF);
+    }
+    if(n==0){
+	/* no arg, use default*/
+	return(ST_SUCCESS);
+    }
+    
+
+    /* read length if given ( if first par starts with digit )*/
+    if( isdigit((int)argv[argn][0])) {
+	synth->time = st_parsetime(argv[argn]);
+	if (synth->time < 0.0){
+	    st_warn("synth: illegal time");
+	    st_fail(usstr);
+		return(ST_EOF);
+	}
+	argn++;
+    }
+    /* for one or more channel */
+    /* type [mix] [f1[-f2]] [p0] [p1] [p2] [p3] [p4] */
+    for(c=0;c<MAXCHAN;c++){
+	if(n > argn){
+	    /* next par must be type */
+	    if( strcasecmp(argv[argn],"sine")==0){
+		synth->type[c]=SYNTH_SINE;
+		argn++; /* 1 */
+	    }else if( strcasecmp(argv[argn],"square")==0){
+		synth->type[c]=SYNTH_SQUARE;
+		argn++; /* 1 */
+	    }else if( strcasecmp(argv[argn],"sawtooth")==0){
+		synth->type[c]=SYNTH_SAWTOOTH;
+		argn++; /* 1 */
+	    }else if( strcasecmp(argv[argn],"triangle")==0){
+		synth->type[c]=SYNTH_TRIANGLE;
+		argn++; /* 1 */
+	    }else if( strcasecmp(argv[argn],"exp")==0){
+		synth->type[c]=SYNTH_EXP;
+		argn++; /* 1 */
+	    }else if( strcasecmp(argv[argn],"trapetz")==0){
+		synth->type[c]=SYNTH_TRAPETZ;
+		argn++;
+	    }else if( strcasecmp(argv[argn],"whitenoise")==0){
+		synth->type[c]=SYNTH_WHITENOISE;
+		argn++; /* 1 */
+	    }else if( strcasecmp(argv[argn],"noise")==0){
+		synth->type[c]=SYNTH_WHITENOISE;
+		argn++; /* 1 */
+	    }else if( strcasecmp(argv[argn],"pinknoise")==0){
+		synth->type[c]=SYNTH_PINKNOISE;
+		argn++; /* 1 */
+	    }else if( strcasecmp(argv[argn],"brownnoise")==0){
+		synth->type[c]=SYNTH_BROWNNOISE;
+		argn++; /* 1 */
+	    }else if( strcasecmp(argv[argn],"voicenoise")==0){
+		synth->type[c]=SYNTH_VOICENOISE;
+		argn++; /* 1 */
+	    }else{
+		/* type not given, error */
+		st_warn("synth: no type given");
+		st_fail(usstr);
+		return(ST_EOF);
+	    }
+	    if(n > argn){
+		/* maybe there is a mix-type in next arg */
+		if(strcasecmp(argv[argn],"create")==0){
+		    synth->mix[c]=SYNTH_CREATE;
+		    argn++;
+		}else if(strcasecmp(argv[argn],"mix")==0){
+		    synth->mix[c]=SYNTH_MIX;
+		argn++;
+		}else if(strcasecmp(argv[argn],"amod")==0){
+		    synth->mix[c]=SYNTH_AMOD;
+		    argn++;
+		}else if(strcasecmp(argv[argn],"fmod")==0){
+		    synth->mix[c]=SYNTH_FMOD;
+		    argn++;
+		}
+		if(n > argn){
+		    /* read frequency's if given */
+		    synth->freq[c]= StringToFreq(argv[argn],&hlp);
+		    synth->freq2[c] = synth->freq[c];
+		    if(synth->freq[c] < 0.0){
+			st_warn("synth: illegal freq");
+			st_fail(usstr);
+			return(ST_EOF);
+		    }
+		    if(*hlp=='-') {
+			/* freq2 given ! */
+			char *hlp2;
+			synth->freq2[c]=StringToFreq(hlp+1,&hlp2);
+			if(synth->freq2[c] < 0.0){
+			    st_warn("synth: illegal freq2");
+			    st_fail(usstr);
+			    return(ST_EOF);
+			}
+		    }
+		    argn++;
+		    i=0; 
+		    /* read rest of parameters */
+		    while(n > argn){
+			if( ! isdigit((int)argv[argn][0]) ){
+			    /* not a digit, must be type of next channel */
+			    break;
+			}
+			if( i >= PCOUNT) {
+			    st_warn("synth: too many parameters");
+			    st_fail(usstr);
+			    return(ST_EOF);
+			    
+			}
+			synth->par[c][i]=strtod(argv[argn],&hlp);
+			if(hlp==argv[argn]){
+				/* error in number */
+			    st_warn("synth: parameter error");
+			    st_fail(usstr);
+			    return(ST_EOF);
+			} 
+			i++;
+			argn++;
+		    }/* .. while */
+		    if(n > argn){
+			/* got here by 'break', scan parms for next chan */
+		    }else{
+			
+			break;
+		    }
+		}
+	    }
+	}
+    }/* for .. */
+
+    /* make some intelligent parameter initialization for channels
+     * where no parameters were given
+     *
+     * - of only parms for one channel were given, copy to ther channels
+     * - if parm for 2 channels were given, copy to channel 1->3, 2->4
+     * - if parm for 3 channels were given, copy 2->4
+     */
+    if(c == 0){
+	for(c=1;c<MAXCHAN;c++)
+	    parmcopy(synth,0,c);
+    }else if(c == 1){
+	parmcopy(synth,0,2);
+	parmcopy(synth,1,3);
+    }else if(c == 2){
+	parmcopy(synth,1,3);
+    }
+
+
+
+
+    return (ST_SUCCESS);
+}
+
+
+/*
+ * Prepare processing.
+ * Do all initializations.
+ */
+int st_synth_start(effp)
+eff_t effp;
+{
+    int i;
+    int c;
+    synth_t synth = (synth_t) effp->priv;
+
+    /* calc length in number of samples... */
+    synth->length = (double)effp->ininfo.rate * synth->time;
+
+    if (synth->length < 0){
+	st_fail("synth: start must be positive");
+	return(ST_EOF);
+    }
+
+    synth->samples_done=0;
+    synth->rate = effp->ininfo.rate;
+    
+    for(i=0;i< MAXCHAN; i++){
+	synth->h[i]=0.0;
+    }
+
+    
+    
+    
+    /* parameter adjustment for all channels */
+    for(c=0;c<MAXCHAN;c++){
+	/* adjust parameter 0 - 100% to 0..1 */
+	for(i=0;i<PCOUNT;i++){
+	    synth->par[c][i] /= 100.0;
+	}
+    
+    
+
+	/* give parameters nice defaults for the different 'type' */
+    
+	switch(synth->type[c]){
+	    case SYNTH_SINE:
+		break;
+	    case SYNTH_SQUARE:
+		/* p2 is pulse width */
+		if(synth->par[c][2] < 0.0){
+		    synth->par[c][2] = 0.5; /* default to 50% duty cycle */
+		}
+		break;
+	    case SYNTH_TRIANGLE:
+		/* p2 is position of maximum*/
+		if(synth->par[c][2] < 0.0){
+		    /* default : 0 */
+		    synth->par[c][2]=0.5;
+		}
+		break;
+	    case SYNTH_SAWTOOTH:
+		/* no parameters, use TRIANGLE to create no-default-sawtooth */
+		break;
+	    case SYNTH_TRAPETZ:
+		/* p2 is length of rising slope,
+		 * p3 position where falling slope begins
+		 * p4 position of end of falling slope
+		 */
+		if(synth->par[c][2] < 0.0 ){
+		    synth->par[c][2]= 0.1;
+		    synth->par[c][3]= 0.5;
+		    synth->par[c][4]= 0.6;
+		}else if(synth->par[c][3] < 0.0){
+		    /* try a symetric waveform
+		     */
+		    if(synth->par[c][2] <= 0.5){
+			synth->par[c][3] = (1.0-2.0*synth->par[c][2])/2.0;
+			synth->par[c][4] = synth->par[c][3] + synth->par[c][2];
+		    }else{
+			/* symetric is not possible, fall back to asymetrical 
+			 * triangle
+			 */
+			synth->par[c][3]=synth->par[c][2];
+			synth->par[c][4]=1.0;
+		    }
+		}else if(synth->par[c][4] < 0.0){
+		    /* simple falling slope to the end */
+		    synth->par[c][4]=1.0;
+		}
+		break;
+	    case SYNTH_PINKNOISE:
+		/* Initialize pink noise signals with different numbers of rows. */
+		InitializePinkNoise( &(synth->pinkn[c]),10+2*c);
+		break;
+	    default:
+		break;
+	}
+	
+	
+#if 0
+	st_warn("synth: type=%d, mix=%d, time=%lf, f1=%lf, f2=%lf",
+		synth->type[c], synth->mix[c], 
+		synth->time, synth->freq[c], synth->freq2[c]);
+	st_warn("synth: p0=%f, p1=%f, p2=%f, p3=%f, p4=%f",synth->par[c][0],
+		synth->par[c][1],synth->par[c][2],synth->par[c][3],synth->par[c][4]);
+	st_warn("synth: inchan =%d, rate=%d",
+		(int)effp->ininfo.channels,synth->rate
+	    );
+#endif
+    }
+    return (ST_SUCCESS);
+}
+
+
+
+static LONG do_synth(LONG iv, synth_t synth, int c){
+    LONG ov=iv;
+    double r=0.0; /* -1 .. +1 */
+    double f;
+    double om;
+    double sd;
+    double move;
+    double t,dt ;
+
+    if(synth->length<=0){
+	/* there is no way to change the freq. without knowing the length
+	 * use startfreq all the time ...
+	 */
+	f = synth->freq[c];
+    }else{
+	f = synth->freq[c] * 
+	    exp( (log(synth->freq2[c])-log(synth->freq[c]))* 
+		 synth->samples_done/synth->length );
+    }
+    om = 1.0 / f; /* periodendauer inn sec */
+    t = synth->samples_done / (double)synth->rate; /* zeit seit start in sec */
+    dt = t - synth->h[c]; /* seit seitdem letzte periode um war. */
+    if( dt < om){
+	/* wir sind noch in der periode.. */
+    }else{
+	/* schon in naechste periode */
+	synth->h[c]+=om;
+	dt=t-synth->h[c];
+    }
+    sd= dt/om; /* position in der aktuellen periode; 0<= sd < 1*/
+    sd = fmod(sd+synth->par[c][1],1.0); /* phase einbauen */
+
+
+// sd = fmod( (synth->samples_done - synth->h[c])/om + synth->par[c][1],1.0);
+    switch(synth->type[c]){
+	case SYNTH_SINE:
+	    r = sin(2.0 * M_PI * sd);
+	    break;
+	case SYNTH_SQUARE:
+	    /* |_______           | +1
+	     * |       |          |
+	     * |_______|__________|  0
+	     * |       |          |
+	     * |       |__________| -1
+	     * |                  |
+	     * 0       p2          1
+             */
+	    if(sd < synth->par[c][2]){
+		r = -1.0;
+	    }else{
+		r = +1.0;
+	    }
+	    break;
+	case SYNTH_SAWTOOTH:
+	    /* |           __| +1
+	     * |        __/  |
+	     * |_______/_____|  0
+	     * |  __/        |
+	     * |_/           | -1
+	     * |             |
+	     * 0             1
+             */
+	    r = -1.0 + 2.0 * sd;
+	    break;
+	case SYNTH_TRIANGLE:
+	    /* |    _    | +1
+	     * |   / \   |
+	     * |__/___\__|  0
+	     * | /     \ |
+	     * |/       \| -1
+	     * |         |
+	     * 0   p2    1
+             */
+
+	    if( sd < synth->par[c][2]){ /* in rising Part of period */
+		r = -1.0 + 2.0 * sd / synth->par[c][2];
+	    }else{    /* falling part */
+		r = 1.0 - 2.0 *
+		    (sd-synth->par[c][2])/(1-synth->par[c][2]);
+	    }
+	    break;
+	case SYNTH_TRAPETZ:
+	    /* |    ______             |+1
+	     * |   /      \            |
+	     * |__/________\___________| 0
+	     * | /          \          |
+	     * |/            \_________|-1
+	     * |                       |
+	     * 0   p2    p3   p4       1
+             */
+	    if( sd < synth->par[c][2]){ /* in rising part of period */
+		r = -1.0 + 2.0 * sd / synth->par[c][2];
+	    }else if( sd < synth->par[c][3]){ /* in constant Part of period */
+		r=1.0;
+	    }else if( sd < synth->par[c][4] ){ /* falling part */
+		r = 1.0 - 2.0 *
+		    (sd - synth->par[c][3])/(synth->par[c][4]-synth->par[c][3]);
+	    }else{
+		r = -1.0;
+	    }
+	    break;
+
+	case SYNTH_EXP:
+	    /* |             |              | +1
+	     * |            | |             |
+	     * |          _|   |_           | 0
+	     * |       __-       -__        |
+	     * |____---             ---____ | f(p3) 
+	     * |                            |
+	     * 0             p2             1
+             */
+	    move=exp( - synth->par[c][3] * LOG_10_20 * 100.0 ); /* 0 ..  1 */
+	    if ( sd < synth->par[c][2] ) {
+		r = move * exp(sd * log(1.0/move)/synth->par[c][2]);
+	    }else{
+		r = move * 
+		    exp( (1-sd)*log(1.0/move)/
+			 (1.0-synth->par[c][2]));
+	    }
+
+	    /* r in 0 .. 1 */
+	    r = r * 2.0 - 1.0; /* -1 .. +1 */
+	    break;
+	case SYNTH_WHITENOISE:
+	    r= 2.0* rand()/(double)RAND_MAX - 1.0;
+	    break;
+	case SYNTH_PINKNOISE:
+	    r = GeneratePinkNoise( &(synth->pinkn[c]) );
+	    break;
+	case SYNTH_BROWNNOISE:
+	    /* no idea if this algorithm is good enough.. */
+	    move = 2.0* rand()/(double)RAND_MAX - 1.0;
+	    move *= BROWNNOISE_FAC;
+	    synth->h[c] += move;
+	    if( fabs(synth->h[c]) > 1.0 ){
+		synth->h[c] -= 2.0*move;
+	    }
+	    r=synth->h[c];
+	    break;
+	default:
+	    st_warn("synth: internal error 1");
+	    break;
+    }
+
+    /* add offset, but prevent clipping */
+    om = fabs(synth->par[c][0]);
+    if( om <= 1.0 ){
+	r *= 1.0 - om; /* reduce amp, prevent clipping */
+	r += om;
+    }
+
+
+    switch(synth->mix[c]){
+	case SYNTH_CREATE:
+	    ov = synth->max * r;
+	    break;
+	case SYNTH_MIX:
+	    ov = iv/2 + r*synth->max/2;
+	    break;
+	case SYNTH_AMOD:
+	    ov = (LONG)(0.5*(r+1.0)*(double)iv);
+	    break;
+	case SYNTH_FMOD:
+	    ov = iv * r ;
+	    break;
+	default:
+	    st_fail("synth: internel error 2");
+	    break;
+    }
+
+    return ov;
+}
+
+
+
+/*
+ * Processed signed long samples from ibuf to obuf.
+ */
+
+int st_synth_flow(effp, ibuf, obuf, isamp, osamp)
+eff_t effp;
+LONG *ibuf, *obuf;
+LONG *isamp, *osamp;
+{
+    synth_t synth = (synth_t) effp->priv;
+    int len; /* number of input samples */
+    int done;
+    int c;
+    int chan=effp->ininfo.channels;
+
+    if(chan > MAXCHAN ){
+	st_fail("synth: can not operate with more than %d channels",MAXCHAN);
+	return(ST_EOF);
+    }
+
+    len = ((*isamp > *osamp) ? *osamp : *isamp) / chan;
+
+    for(done = 0; done < len ; done++){
+	for(c=0;c<chan;c++){
+	    /* each channel is independent, but the algorithm is the same */
+
+	    obuf[c] = do_synth(ibuf[c],synth,c);
+	}
+	ibuf+=chan;
+	obuf+=chan;
+	synth->samples_done++;
+	if(synth->length > 0 ){
+	    if( synth->samples_done > synth->length){
+		/* break 'nul' file fileter when enough samples 
+		 * are produced. the actual number of samples 
+		 * will be a little bigger, depends on when the
+		 * signal gets to the plugin
+		 */
+		raise(SIGINT); /* only once */
+		*osamp = done*chan;
+		break;
+
+	    }
+    }
+	
+    }
+    return (ST_SUCCESS);
+}
+
+/*
+ * Drain out remaining samples if the effect generates any.
+ */
+
+int st_synth_drain(effp, obuf, osamp)
+LONG *obuf;
+LONG *osamp;
+{
+	*osamp = 0;
+	return (ST_SUCCESS);
+}
+
+/*
+ * Do anything required when you stop reading samples.  
+ *	(free allocated memory, etc.)
+ */
+int st_synth_stop(effp)
+eff_t effp;
+{
+    /* nothing to do */
+    return (ST_SUCCESS);
+}
+/*-------------------------------------------------------------- end of file */
+
+
+
+
+
+
+
+
+
+
--- /dev/null
+++ b/src/testcd.sh
@@ -1,0 +1,237 @@
+#!/bin/sh
+# create WAV-Files that can be used for an audio Test CD
+# all files are created in the current directory
+#
+# 
+# length of sample file in seconds
+
+if  [ "$2"  = "" ] ; then
+    LENGTH="30"
+else
+    LENGTH=$2
+fi
+# use 'fade' effect for smooth start and end of tone
+FT="0.05"
+
+# a different default volume
+VOL=""
+
+#output file type
+OFT=".wav"
+
+#our binary
+SOX=./sox  
+
+# filenameprefix
+if  [ "$1" = "" ] ; then
+    PRE="testcd"
+else
+    PRE=$1;
+fi
+# 2 channel 16 bit signed linear int with CD sampling rate
+SOXOPT="-t nul -c 2 -r 44100 -s -w - "
+
+# file with list of filenames
+LST="${PRE}.lst"
+
+
+#summarise seconds
+TC="0"
+#summarize file numbers
+FC="0"
+
+
+newname()
+{
+    FC="$(( $FC + 1 ))"
+    LEN="$2"
+    FADE=" fade $FT $LEN $FT" 
+    
+    TC="$(( $TC + $LEN ))"
+    
+    if [ $FC -lt 10 ] ; then 
+	NAME="${PRE}_0${FC}_${1}${OFT}"
+    else
+	NAME="${PRE}_${FC}_${1}${OFT}"
+    fi
+    echo -n -e  " \t$1"
+    echo "$NAME" >>$LST
+}
+
+#empty / delete list file
+echo "" >$LST
+
+
+#
+# ok, lets start with the actual createion of the files
+#
+
+#
+# fixed frequencies, 
+FREQ="                   19.4  23.1  27.5  32.7  38.9  46.2"
+FREQ="$FREQ  55.0  64.4  77.8  92.5 110.0 130.8 155.6 185.0"
+FREQ="$FREQ 220.0 261.6 311.1 367.0 440.0 523.3 622.3 740.0"
+FREQ="$FREQ 880.0  1046  1245  1480  1760  2093  2489  2960"
+FREQ="$FREQ  3520  4186  4978  5920  7040  8372  9956 11840"
+FREQ="$FREQ 14080 16744 19912"
+#FREQ="5 10 20 50 100 200 500 1000 2000 5000 10000 20000"
+echo -e "\n--- different frequencies"
+for f in $FREQ; do
+    newname "${f}hz" $LENGTH
+$SOX $SOXOPT $NAME synth $LEN sine $f $FADE $VOL
+done
+
+
+
+#
+# frequency sweep
+# need some mark every octave
+#
+FREQ="220-3520"    #4
+FREQ="55-14080"    # 8 oct
+FREQ="13.75-28160" # 10 oct
+OCT=10
+TOCT=10
+TGES="$(( $OCT * $TOCT ))"
+MARKFREQ=622
+echo -e "\n--- frequency sweep range $FREQ"
+newname ${FREQ}hz $TGES 
+$SOX $SOXOPT $NAME synth $LEN sine $MARKFREQ synth square amod 0.1 0 97 94 vol -3 db synth $LEN sine mix $FREQ  $VOL
+
+FREQ="3520-220"
+FREQ="28160-13.75" # 9 oct
+newname ${FREQ}hz $TGES 
+$SOX $SOXOPT $NAME synth $LEN sine $MARKFREQ synth square amod 0.1 0 97 94 vol -3 db synth $LEN sine mix $FREQ  $VOL
+
+# CD frequencies
+FREQ="22050 11025 5512.5 "
+echo -e "\n--- different frequencies $FREQ"
+for f in $FREQ; do
+    newname "cd${f}hz" $LENGTH
+$SOX $SOXOPT $NAME synth $LEN sine $f $FADE $VOL
+done
+
+
+#
+# similar frequencies
+#
+FREQ1="9000"
+FREQ2="10000"
+echo -e "\n--- similar frequencies"
+newname ${FREQ1}_${FREQ2} $LENGTH
+$SOX $SOXOPT $NAME synth $LEN sine $FREQ1 synth sine mix $FREQ2 $FADE $VOL
+FREQ1="440"
+FREQ2="445"
+newname ${FREQ1}_${FREQ2} $LENGTH
+$SOX $SOXOPT $NAME synth $LEN sine $FREQ1 synth sine mix $FREQ2 $FADE $VOL
+
+#
+#noise
+#   
+echo -e "\n--- noise"
+newname whitenoise $LENGTH
+$SOX  $SOXOPT $NAME synth $LEN whitenoise $FADE $VOL
+newname pinknoise $LENGTH
+$SOX  $SOXOPT $NAME synth $LEN pinknoise  $FADE $VOL
+newname brownnoise $LENGTH
+$SOX  $SOXOPT $NAME synth $LEN brownnoise  $FADE $VOL
+
+
+
+#
+# square waves
+#
+FREQ="100 1000 10000"
+echo -e "\n--- square waves at $FREQ"
+for f in $FREQ; do 
+  newname ${f}_square $LENGTH
+$SOX $SOXOPT $NAME synth $LEN square $f vol -12 db $FADE
+done
+
+
+#
+# different volumes at a few frequencies
+#
+FREQ="100 1000 10000" 
+DB="0 12 24 36 48 60 72 84 96"
+echo -e "\n--- different volumes $DB db at Frequencies $FREQ"
+ for f in $FREQ; do
+   for d in $DB; do
+    newname ${f}_${d}db $LENGTH
+    $SOX $SOXOPT $NAME synth $LEN sine $f  vol -$d db $FADE
+  done
+done
+
+# silence
+echo -e "\n-- silence"
+newname silence $LENGTH
+$SOX $SOXOPT $NAME synth $LEN sine 1000 vol 0
+
+
+
+
+#
+# volume sweep at different frequencies
+#
+
+FREQ="100 1000 10000"
+MARKFREQ=662
+DB=100 # 10sec for 10db
+echo -e "\n--- volume sweep 0..100% at Frequencies $FREQ"
+for f in $FREQ; do
+  newname ${f}_dbsweep 200
+$SOX $SOXOPT $NAME synth $LEN sine $f synth exp amod 0.005 0 0 50 $DB 
+done
+
+
+#
+# offset test - a 1K sine with 1Hz square offset of 10%
+#
+echo -e "\n--- offset test, 1KHz tone with 1HZ 10% offset"
+newname offset $LENGTH
+$SOX $SOXOPT $NAME synth $LEN square 1 vol 0.1  synth sine mix 1000 $FADE $VOL
+newname offset1 $LENGTH
+$SOX $SOXOPT $NAME synth $LEN square 1 0 0 square 1 vol 0.1 0 25 $FADE
+
+
+
+# effects for different channels
+#
+# silence on one channel, full power on the other - different frequencies
+FREQ="100 1000 10000"
+echo -e "\n--- single channel"
+for f in $FREQ; do
+ newname ${f}leftchan $LENGTH
+ $SOX $SOXOPT $NAME synth $LEN sine $f synth square amod 0 100 square amod 0   0 $Fade $VOL
+ newname ${f}rightchan $LENGTH
+ $SOX $SOXOPT $NAME synth $LEN sine $f synth square amod 0   0 square amod 0 100 $Fade $VOL
+done
+
+# phase error between channels
+FREQ="100 1000 10000"
+# equal phase/ 24 degrees / 90 degrees / 180 degrees
+PHASE="25"
+echo -e "\n--- phase error test between channels at $FREQ "
+for f in $FREQ; do
+  for p in $PHASE; do
+    newname ${f}hz_phase${p} $LENGTH
+    $SOX $SOXOPT $NAME synth $LEN sine $f 0 0 sine $f 0 $p  $FADE $VOL
+  done
+done
+
+#
+#
+# end - show statistics
+#
+
+echo -e "\n------------------\ncreated $FC files with prefix $PRE type $OFT"
+MIN="$(( $TC / 60 ))"
+echo "total length is $TC sec = $MIN min"
+#---------
+
+
+
+
+
+
+
--- a/src/wav.c
+++ b/src/wav.c
@@ -1367,8 +1367,8 @@
 #ifdef HAVE_LIBGSM
 		    if (wChannels!=1)
 		    {
-			st_fail_errno(ft,ST_EOF,"Channels(%d) must be == 1\n",wChannels);
-			return ST_EOF;
+			st_warn("Overriding GSM audio from %d channel to 1\n",wChannels);
+			wChannels = ft->info.channels = 1;
 		    }
 		    wFormatTag = WAVE_FORMAT_GSM610;
 		    /* wAvgBytesPerSec = 1625*(wSamplesPerSecond/8000.)+0.5; */