shithub: sox

Download patch

ref: f8ce704d16fe11b50067f4397674f703cfa4ae54
parent: 613f50d018d73308428dda8c610066a726e1a95e
author: cbagwell <cbagwell>
date: Tue Nov 23 16:28:31 EST 1999

Adding GSM WAV support plus other bug fixes.

--- a/Changelog
+++ b/Changelog
@@ -6,12 +6,18 @@
 
 sox-12.17
 ---------
+  o Sox can now read and write w98 compatible gsm .wav files,
+    if compiled properly with libgsm.  Thanks go to Stuart
+    Daines <sjd.u-net.com> for the gsm-wav patches.
+    This is new, and relatively untested. See -g format option.
   o Sox can now write IMA_ADPCM and ADPCM compressed .wav,
     this is new, and relatively untested. See -i and -a format
-		flags in manpage.
+    options in manpage.
   o General changes to wav.c for writing additional wav formats.
+    Reading wave headers: more consistency checks.
+    Writing wave headers: fixes for w98.
   o Speedups to adpcm read routines, new codex versions are
-	  now in ima_rw.c and adpcm.c.
+    now in ima_rw.c and adpcm.c.
   o Speedups for raw.c, especially for gcc with glibc.
   o Fixed a segfault problem with ulaw/alaw conversion, where
     an out-of-range index into the tables could occur.
--- a/README
+++ b/README
@@ -183,6 +183,9 @@
 		Stan Brooks		stabro@megsinet.net
 			Rewrite of resample and polyphase code.
 			DSP filter effect.  Some test code/scripts.
+		Stuart Daines <sjd.u-net.com>
+		  Patches for r/w support of gsm-encoded wav files,
+			Cleanup of wav.c.
 		Chris Bagwell		cbagwell@sprynet.com
 			OSS and Sun players, bugfixes, ADPCM support,
 			patch collection and maintance.
--- a/src/adpcm.c
+++ b/src/adpcm.c
@@ -368,7 +368,7 @@
 	obuff[ch] = kmin;
 }
 
-void AdpcmMashI(
+void AdpcmBlockMashI(
 	int chans,          /* total channels */
 	const SAMPL *ip,    /* ip[n*chans] is interleaved input samples */
 	int n,              /* samples to encode PER channel */
--- a/src/adpcm.h
+++ b/src/adpcm.h
@@ -22,7 +22,7 @@
 	int n               /* samples to decode PER channel, REQUIRE n % 8 == 1  */
 );
 
-extern void AdpcmMashI(
+extern void AdpcmBlockMashI(
 	int chans,          /* total channels */
 	const SAMPL *ip,    /* ip[n*chans] is interleaved input samples */
 	int n,              /* samples to encode PER channel, REQUIRE */
--- a/src/ima_rw.c
+++ b/src/ima_rw.c
@@ -258,31 +258,11 @@
 	return (int) sqrt(d2);
 }
 
-#if 0
-static long AvgDelta(int ch, int chans, const SAMPL *ibuff, int n)
-{
-	const SAMPL *ip, *itop;
-	int v0;
-	long d1;
-	
-	ip = ibuff + ch;
-	itop = ip + n*chans;
-	d1 = 0;
-	v0 = *ip;
-	ip += chans;
-	for ( ; ip < itop; ip+=chans) {
-		int v1;
-
-		v1 = *ip;
-		d1 += abs(v1-v0);
-		v0 = v1;
-	}
-	return (d1/(n-1));
-}
-#endif
-
 /* mash one channel... if you want to use opt>0, 9 is a reasonable value */
-void ImaMashChannel(
+#ifdef __GNUC__
+inline
+#endif
+static void ImaMashChannel(
 	int ch,             /* channel number to encode, REQUIRE 0 <= ch < chans  */
 	int chans,          /* total channels */
 	const SAMPL *ip,    /* ip[] is interleaved input samples */
@@ -336,6 +316,21 @@
 	d = ImaMashS(ch, chans, ip[0], ip,n,st, obuff, 0);
 	/* printf("%4d %6d %6d\n", s0-s32, d0, d32-d0); */
 	/* printf("%5d %2d\n", AvgDelta(ch,O.chans,ip,32), s0); */
+}
+
+/* mash one block.  if you want to use opt>0, 9 is a reasonable value */
+void ImaBlockMashI(
+	int chans,          /* total channels */
+	const SAMPL *ip,    /* ip[] is interleaved input samples */
+	int n,              /* samples to encode PER channel, REQUIRE n % 8 == 1 */
+	int *st,            /* input/output state, REQUIRE 0 <= *st <= ISSTMAX */
+	u_char *obuff,      /* output buffer[blockAlign] */
+	int opt             /* non-zero allows some cpu-intensive code to improve output */
+)
+{
+	int ch;
+	for (ch=0; ch<chans; ch++)
+		ImaMashChannel(ch, chans, ip, n, st+ch, obuff, opt);
 }
 
 #if 0
--- a/src/ima_rw.h
+++ b/src/ima_rw.h
@@ -54,12 +54,12 @@
 	int n               /* samples to decode PER channel, REQUIRE n % 8 == 1  */
 );
 
-extern void ImaMashChannel(
-	int ch,             /* channel number to encode, REQUIRE 0 <= ch < chans  */
+/* mash one block.  if you want to use opt>0, 9 is a reasonable value */
+extern void ImaBlockMashI(
 	int chans,          /* total channels */
 	const SAMPL *ip,    /* ip[] is interleaved input samples */
 	int n,              /* samples to encode PER channel, REQUIRE n % 8 == 1 */
-	int *st,            /* input/output state, REQUIRE 0 <= *st <= ISSTMAX */
+	int *st,            /* input/output state[chans], REQUIRE 0 <= st[ch] <= ISSTMAX */
 	u_char *obuff,      /* output buffer[blockAlign] */
 	int opt             /* non-zero allows some cpu-intensive code to improve output */
 );
--- a/src/st.h
+++ b/src/st.h
@@ -27,6 +27,14 @@
 #define ULONG	unsigned long
 #endif
 
+#ifdef __GNUC__
+#define NORET __attribute__((noreturn))
+#define INLINE inline
+#else
+#define NORET
+#define INLINE
+#endif
+
 /*
  * Handler structure for each format.
  */
@@ -256,10 +264,11 @@
 #endif
 double 	       swapd(P1(double d));			/* Swap double */
 
-void report(P2(char *, ...)),  warn(P2(char *, ...)),
-	 fail(P2(char *, ...));
-
 /* util.c */
+void report(P2(char *, ...));
+void warn(P2(char *, ...));
+void fail(P2(char *, ...))NORET;
+
 void geteffect(P1(eff_t));
 void gettype(P1(ft_t));
 void checkformat(P1(ft_t));
--- a/src/wav.c
+++ b/src/wav.c
@@ -8,7 +8,14 @@
  *
  * Change History:
  *
- * November  22, 1999 - Stan Brooks (stabro@megsinet.com)
+ * November  23, 1999 - Stan Brooks (stabro@megsinet.com)
+ *   Merged in gsm support patches from Stuart Daines...
+ *   Since we had simultaneously made similar changes in
+ *   wavwriteheader() and wavstartread(), this was some
+ *   work.  Hopefully the result is cleaner than either
+ *   version, and nothing broke.
+ *
+ * November  20, 1999 - Stan Brooks (stabro@megsinet.com)
  *   Mods for faster adpcm decoding and addition of IMA_ADPCM
  *   and ADPCM  writing... low-level codex functions moved to
  *   external modules ima_rw.c and adpcm.c. Some general cleanup,
@@ -67,7 +74,13 @@
 #include "wav.h"
 #include "ima_rw.h"
 #include "adpcm.h"
+#ifdef HAVE_LIBGSM
+#include "gsm.h"
+#endif
 
+#undef PAD_NSAMPS
+/* #define PAD_NSAMPS */
+
 /* Private data for .wav file */
 typedef struct wavstuff {
     LONG	   numSamples;
@@ -87,8 +100,22 @@
     unsigned short blockSamplesRemaining;/* Samples remaining in each channel */    
     /* state holds step-size info for ADPCM or IMA_ADPCM writes */
     int 	   state[16];       /* last, because maybe longer */
+
+    /* following used by GSM 6.10 wav */
+#ifdef HAVE_LIBGSM
+    gsm  gsmhandle;
+    gsm_signal *gsmsample;
+    int  gsmindex;
+    int gsmbytecount;
+#endif
 } *wav_t;
 
+/*
+#if sizeof(struct wavstuff) > PRIVSIZE
+#	warn "Uh-Oh"
+#endif
+*/
+
 static char *wav_format_str();
 
 LONG rawread(P3(ft_t, LONG *, LONG));
@@ -111,9 +138,9 @@
     { 
 	/* If it looks like a valid header is around then try and */
 	/* work with partial blocks.  Specs say it should be null */
-	/* padded but I guess this is better then trailing quiet. */
+	/* padded but I guess this is better than trailing quiet. */
 	if (bytesRead >= (4 * ft->info.channels))
-	{
+	{   /* SJB: FIXME this is incorrect */
 	    samplesThisBlock = (wav->blockAlign - (3 * ft->info.channels));
 	}
 	else
@@ -135,32 +162,6 @@
 
 }
 
-static void ImaAdpcmWriteBlock(ft)
-ft_t ft;
-{
-    wav_t wav = (wav_t) ft->priv;
-    int chans, ch, ct;
-    short *p;
-
-    chans = ft->info.channels;
-    p = wav->samplePtr;
-    ct = p - wav->samples;
-    if (ct>=chans) { 
-	/* zero-fill samples if needed to complete block */
-	for (p = wav->samplePtr; p < wav->sampleTop; p++) *p=0;
-	/* compress the samples to wav->packet */
-	for (ch=0; ch<chans; ch++)
-	    ImaMashChannel(ch, chans, wav->samples, wav->samplesPerBlock, &wav->state[ch], wav->packet, 9);
-
-	/* write the compressed packet */
-	fwrite(wav->packet, wav->blockAlign, 1, ft->fp); /* FIXME: check return value */
-	/* update lengths and samplePtr */
-	wav->dataLength += wav->blockAlign;
-	wav->numSamples += ct/chans;
-	wav->samplePtr = wav->samples;
-    }
-}
-
 /****************************************************************************/
 /* MS ADPCM Support Functions Section                                       */
 /****************************************************************************/
@@ -183,9 +184,9 @@
     {
 	/* If it looks like a valid header is around then try and */
 	/* work with partial blocks.  Specs say it should be null */
-	/* padded but I guess this is better then trailing quite. */
+	/* padded but I guess this is better than trailing quite. */
 	if (bytesRead >= (7 * ft->info.channels))
-	{
+	{   /* SJB: FIXME this is incorrect */
 	    samplesThisBlock = (wav->blockAlign - (6 * ft->info.channels));
 	}
 	else
@@ -205,7 +206,10 @@
     return samplesThisBlock;
 }
 
-static void AdpcmWriteBlock(ft)
+/****************************************************************************/
+/* Common ADPCM Support Function                                            */
+/****************************************************************************/
+static void xxxAdpcmWriteBlock(ft)
 ft_t ft;
 {
     wav_t wav = (wav_t) ft->priv;
@@ -219,22 +223,198 @@
 	/* zero-fill samples if needed to complete block */
 	for (p = wav->samplePtr; p < wav->sampleTop; p++) *p=0;
 	/* compress the samples to wav->packet */
-
-	AdpcmMashI(chans, wav->samples, wav->samplesPerBlock, wav->state, wav->packet, wav->blockAlign,9);
-
+	if (wav->formatTag == WAVE_FORMAT_ADPCM) {
+	    AdpcmBlockMashI(chans, wav->samples, wav->samplesPerBlock, wav->state, wav->packet, wav->blockAlign,9);
+	}else{ /* WAVE_FORMAT_IMA_ADPCM */
+	    ImaBlockMashI(chans, wav->samples, wav->samplesPerBlock, wav->state, wav->packet, 9);
+	}
 	/* write the compressed packet */
-	fwrite(wav->packet, wav->blockAlign, 1, ft->fp); /* FIXME: check return value */
+	if (fwrite(wav->packet, wav->blockAlign, 1, ft->fp) != 1)
+	    fail("write error");
 	/* update lengths and samplePtr */
 	wav->dataLength += wav->blockAlign;
+#ifndef PAD_NSAMPS
 	wav->numSamples += ct/chans;
+#else
+	wav->numSamples += wav->samplesPerBlock;
+#endif
 	wav->samplePtr = wav->samples;
     }
 }
 
 /****************************************************************************/
+/* WAV GSM6.10 support functions                                            */
+/****************************************************************************/
+#ifdef HAVE_LIBGSM
+/* create the gsm object, malloc buffer for 160*2 samples */
+void wavgsminit(ft)
+ft_t ft;
+{	
+    int valueP=1;
+    wav_t	wav = (wav_t) ft->priv;
+    wav->gsmbytecount=0;
+    wav->gsmhandle=gsm_create();
+    if (!wav->gsmhandle)
+	fail("cannot create GSM object");
+	
+    if(gsm_option(wav->gsmhandle,GSM_OPT_WAV49,&valueP) == -1){
+	fail("error setting gsm_option for WAV49 format. Recompile gsm library with -DWAV49 option and relink sox");
+    }
+
+    wav->gsmsample=malloc(sizeof(gsm_signal)*160*2);
+    if (wav->gsmsample == NULL){
+	fail("error allocating memory for gsm buffer");
+    }
+    wav->gsmindex=0;
+}
+
+/*destroy the gsm object and free the buffer */
+void wavgsmdestroy(ft)
+ft_t ft;
+{	
+    wav_t	wav = (wav_t) ft->priv;
+    gsm_destroy(wav->gsmhandle);
+    free(wav->gsmsample);
+}
+
+LONG wavgsmread(ft, buf, len)
+ft_t ft;
+LONG *buf, len;
+{
+    wav_t	wav = (wav_t) ft->priv;
+    int done=0;
+    int bytes;
+    gsm_frame	frame;
+
+  /* copy out any samples left from the last call */
+    while(wav->gsmindex && (wav->gsmindex<160*2) && (done < len))
+	buf[done++]=LEFT(wav->gsmsample[wav->gsmindex++],16);
+
+  /* read and decode loop, possibly leaving some samples in wav->gsmsample */
+    while (done < len) {
+	wav->gsmindex=0;
+	/*read the long 33 byte half */
+	bytes = fread(frame,1,sizeof(frame),ft->fp);   
+	if (bytes <=0)
+	    return done;
+	if (bytes<sizeof(frame))
+	    fail("invalid wav gsm frame size: %d bytes",bytes);
+	if(gsm_decode(wav->gsmhandle,frame, wav->gsmsample)<0)
+	    fail("error during gsm decode");
+
+	/*read the short 32 byte half */
+	bytes = fread(frame,1,sizeof(frame)-1,ft->fp);   
+	if (bytes <=0)
+	    return done;
+	if (bytes<sizeof(frame)-1)
+	    fail("invalid wav gsm frame size: %d bytes",bytes);
+	if(gsm_decode(wav->gsmhandle,frame, &(wav->gsmsample[160]))<0)
+	    fail("error during gsm decode");
+
+
+	while ((wav->gsmindex <160*2) && (done < len)){
+	    buf[done++]=LEFT(wav->gsmsample[(wav->gsmindex)++],16);
+	}
+    }
+
+    return done;
+}
+
+void wavgsmwrite(ft, buf, len)
+ft_t ft;
+LONG *buf, len;
+{
+    wav_t	wav = (wav_t) ft->priv;
+
+    int done = 0;
+    gsm_frame	frame;
+
+    while (done < len) {
+	while ((wav->gsmindex < 160*2) && (done < len)){
+	    wav->gsmsample[(wav->gsmindex)++] = RIGHT(buf[done++], 16);
+	}
+	if (wav->gsmindex < 160*2){
+	    return;
+	}
+
+	/*encode the even half and write short (32 byte) frame */
+	gsm_encode(wav->gsmhandle, wav->gsmsample, frame);
+	if (fwrite(frame, 1, sizeof(frame)-1, ft->fp) != sizeof(frame)-1)
+	    fail("write error");
+	wav->gsmbytecount += sizeof(frame)-1;
+
+	/*encode the odd half and write long (33 byte) frame */
+	gsm_encode(wav->gsmhandle, &(wav->gsmsample[160]), frame);
+	if (fwrite(frame, 1, sizeof(frame), ft->fp) != sizeof(frame))
+	    fail("write error");
+	wav->gsmbytecount += sizeof(frame);
+	wav->gsmindex = 0;
+    }     
+
+}
+
+void wavgsmstopwrite(ft)
+ft_t ft;
+{
+    gsm_frame frame;
+    wav_t	wav = (wav_t) ft->priv;
+    if (wav->gsmindex){
+	while(wav->gsmindex<160*2){
+	    wav->gsmsample[wav->gsmindex++]=0;
+	}
+
+	/*encode the even half and write short (32 byte) frame */
+	gsm_encode(wav->gsmhandle, wav->gsmsample, frame);
+	if (fwrite(frame, 1, sizeof(frame)-1, ft->fp) != sizeof(frame)-1)
+	    fail("write error");
+	wav->gsmbytecount += sizeof(frame)-1;
+	
+	/*encode the odd half and write long (33 byte) frame */
+	gsm_encode(wav->gsmhandle, &(wav->gsmsample[160]), frame);
+	if (fwrite(frame, 1, sizeof(frame), ft->fp) != sizeof(frame))
+	    fail("write error");
+	wav->gsmbytecount += sizeof(frame);
+
+	/* pad output to an even number of bytes */
+	if (wav->gsmbytecount & 0x1){
+	    if(fputc(0,ft->fp))
+		fail("write error");
+	    wav->gsmbytecount += 1;
+	}
+    }      
+    wavgsmdestroy(ft);
+}
+#endif        /*ifdef out gsm code */
+/****************************************************************************/
 /* General Sox WAV file code                                                */
 /****************************************************************************/
 
+static void fSkip(FILE *fp, ULONG len)
+{   /* FIXME: this should also check ferror(fp) */
+    while (len > 0 && !feof(fp))
+    {
+	getc(fp);
+	len--;
+    }
+}
+
+static ULONG findChunk(ft_t ft, const char *Label)
+{
+    char magic[4];
+    ULONG len;
+    for (;;)
+    {
+	if (fread(magic, 1, 4, ft->fp) != 4)
+	    fail("WAVE file has missing %s chunk", Label);
+	len = rlong(ft);
+	if (strncmp(Label, magic, 4) == 0)
+	    break;		/* Found the data chunk */
+	
+	fSkip(ft->fp, len); 	/* skip to next chunk */
+    }
+    return len;
+}
+
 /*
  * Do anything required before you start reading samples.
  * Read file header. 
@@ -252,13 +432,15 @@
     char	*endptr;
 
     /* wave file characteristics */
+    ULONG    wRiffLength;
     unsigned short wChannels;	    /* number of channels */
     ULONG    wSamplesPerSecond;     /* samples per second per channel */
     ULONG    wAvgBytesPerSec;	    /* estimate of bytes per second needed */
     unsigned short wBitsPerSample;  /* bits per sample */
-    unsigned short wExtSize = 0;    /* extended field for ADPCM */
+    unsigned short wFmtSize;
+    unsigned short wExtSize = 0;    /* extended field for non-PCM */
 
-    ULONG    data_length;	    /* length of sound data in bytes */
+    ULONG    wDataLength;	    /* length of sound data in bytes */
     ULONG    bytespersample;	    /* bytes per sample (per channel */
 
     /* This is needed for rawread() */
@@ -274,33 +456,26 @@
     if ( fread(magic, 1, 4, ft->fp) != 4 || strncmp("RIFF", magic, 4))
 	fail("WAVE: RIFF header not found");
 
-    len = rlong(ft);
+    wRiffLength = rlong(ft);
 
     if ( fread(magic, 1, 4, ft->fp) != 4 || strncmp("WAVE", magic, 4))
 	fail("WAVE header not found");
 
     /* Now look for the format chunk */
-    for (;;)
-    {
-	if ( fread(magic, 1, 4, ft->fp) != 4 )
-	    fail("WAVE file missing fmt spec");
-	len = rlong(ft);
-	if (strncmp("fmt ", magic, 4) == 0)
-	    break;				/* Found the format chunk */
-
-	/* skip to next chunk */	
-	while (len > 0 && !feof(ft->fp))
-	{
-	    getc(ft->fp);
-	    len--;
-	}
-    }
-    /* we only get here if we just read the magic "fmt " */
-    if ( len < 16 )
+    wFmtSize = len = findChunk(ft, "fmt ");
+    /* findChunk() only returns if chunk was found */
+    
+    if (wFmtSize < 16)
 	fail("WAVE file fmt chunk is too short");
 
     wav->formatTag = rshort(ft);
-    len -= 2;
+    wChannels = rshort(ft);
+    wSamplesPerSecond = rlong(ft);
+    wAvgBytesPerSec = rlong(ft);	/* Average bytes/second */
+    wav->blockAlign = rshort(ft);	/* Block align */
+    wBitsPerSample =  rshort(ft);	/* bits per sample per channel */
+    len -= 16;
+
     switch (wav->formatTag)
     {
     case WAVE_FORMAT_UNKNOWN:
@@ -307,14 +482,20 @@
 	fail("WAVE file is in unsupported Microsoft Official Unknown format.");
 	
     case WAVE_FORMAT_PCM:
-        /* Default (-1) depends on sample size.  Set that later on. */
+	/* Default (-1) depends on sample size.  Set that later on. */
 	if (ft->info.style != -1 && ft->info.style != UNSIGNED &&
 	    ft->info.style != SIGN2)
 	    warn("User options overriding style read in .wav header");
 	break;
 	
-    case WAVE_FORMAT_ADPCM:
     case WAVE_FORMAT_IMA_ADPCM:
+	if (ft->info.style == -1 || ft->info.style == IMA_ADPCM)
+	    ft->info.style = IMA_ADPCM;
+	else
+	    warn("User options overriding style read in .wav header");
+	break;
+
+    case WAVE_FORMAT_ADPCM:
 	if (ft->info.style == -1 || ft->info.style == ADPCM)
 	    ft->info.style = ADPCM;
 	else
@@ -347,7 +528,15 @@
     case WAVE_FORMAT_DOLBY_AC2:
 	fail("Sorry, this WAV file is in Dolby AC2 format.");
     case WAVE_FORMAT_GSM610:
-	fail("Sorry, this WAV file is in GSM 6.10 format.");
+#ifdef HAVE_LIBGSM
+	if (ft->info.style == -1 || ft->info.style == GSM )
+	    ft->info.style = GSM;
+	else
+	    warn("User options overriding style read in .wav header");
+	break;
+#else
+	fail("Sorry, this WAV file is in GSM6.10 format and no GSM support present, recompile sox with gsm library");
+#endif
     case WAVE_FORMAT_ROCKWELL_ADPCM:
 	fail("Sorry, this WAV file is in Rockwell ADPCM format.");
     case WAVE_FORMAT_ROCKWELL_DIGITALK:
@@ -367,8 +556,6 @@
     default:	fail("WAV file has unknown format type of %x",wav->formatTag);
     }
 
-    wChannels = rshort(ft);
-    len -= 2;
     /* User options take precedence */
     if (ft->info.channels == -1 || ft->info.channels == wChannels)
 	ft->info.channels = wChannels;
@@ -375,33 +562,39 @@
     else
 	warn("User options overriding channels read in .wav header");
 	
-    wSamplesPerSecond = rlong(ft);
-    len -= 4;
     if (ft->info.rate == 0 || ft->info.rate == wSamplesPerSecond)
 	ft->info.rate = wSamplesPerSecond;
     else
 	warn("User options overriding rate read in .wav header");
     
-    wAvgBytesPerSec = rlong(ft);	/* Average bytes/second */
-    wav->blockAlign = rshort(ft);	/* Block align */
-    len -= 6;
 
-    /* bits per sample per channel */	
-    wBitsPerSample =  rshort(ft);
-    len -= 2;
-
     wav->iCoefs = NULL;
     wav->packet = NULL;
     wav->samples = NULL;
 
-    /* ADPCM formats have extended fmt chunk.  Check for those cases. */
+    /* non-PCM formats have extended fmt chunk.  Check for those cases. */
+    if (wav->formatTag != WAVE_FORMAT_PCM) {
+	if (len >= 2) {
+	    wExtSize = rshort(ft);
+	    len -= 2;
+	} else {
+	    warn("wave header missing FmtExt chunk");
+	}
+    }
+
+    if (wExtSize > len)
+	fail("wave header error: wExtSize inconsistent with wFmtLen");
+
     switch (wav->formatTag)
     {
     case WAVE_FORMAT_ADPCM:
+	if (wExtSize < 4)
+	    fail("wave header error: format[%s] expects wExtSize >= %d",
+			    wav_format_str(wav->formatTag), 4);
+
 	if (wBitsPerSample != 4)
 	    fail("Can only handle 4-bit MS ADPCM in wav files");
 
-	wExtSize = rshort(ft);
 	wav->samplesPerBlock = rshort(ft);
 	wav->bytesPerBlock = ((wav->samplesPerBlock-2)*ft->info.channels + 1)/2
 		             + 7*ft->info.channels;
@@ -410,8 +603,11 @@
 	    fail("ADPCM file nCoefs (%.4hx) makes no sense\n", wav->nCoefs);
 	}
 	wav->packet = (unsigned char *)malloc(wav->blockAlign);
-	len -= 6;
+	len -= 4;
 
+	if (wExtSize < 4 + 4*wav->nCoefs)
+	    fail("wave header error: wExtSize(%d) too small for nCoefs(%d)", wExtSize, wav->nCoefs);
+
 	wav->samples = (short *)malloc(wChannels*wav->samplesPerBlock*sizeof(short));
 
 	/* SJB: will need iCoefs later for adpcm.c */
@@ -426,22 +622,44 @@
 	}
 
 	bytespersample = WORD;  /* AFTER de-compression */
-        break;
+	break;
 
     case WAVE_FORMAT_IMA_ADPCM:
+	if (wExtSize < 2)
+	    fail("wave header error: format[%s] expects wExtSize >= %d",
+		    wav_format_str(wav->formatTag), 2);
+
 	if (wBitsPerSample != 4)
 	    fail("Can only handle 4-bit IMA ADPCM in wav files");
 
-	wExtSize = rshort(ft);
 	wav->samplesPerBlock = rshort(ft);
 	wav->bytesPerBlock = (wav->samplesPerBlock + 7)/2 * ft->info.channels;/* FIXME */
 	wav->packet = (unsigned char *)malloc(wav->blockAlign);
-	len -= 4;
+	len -= 2;
 
 	wav->samples = (short *)malloc(wChannels*wav->samplesPerBlock*sizeof(short));
 
 	bytespersample = WORD;  /* AFTER de-compression */
 	break;
+
+#ifdef HAVE_LIBGSM
+    /* GSM formats have extended fmt chunk.  Check for those cases. */
+    case WAVE_FORMAT_GSM610:
+	if (wExtSize < 2)
+	    fail("wave header error: format[%s] expects wExtSize >= %d",
+		    wav_format_str(wav->formatTag), 2);
+	wav->samplesPerBlock = rshort(ft);
+	if (wav->blockAlign != 65)
+	    fail("wave header error: format[%s] expects blockAlign(%d) = %d",
+		    wav_format_str(wav->formatTag), wav->blockAlign, 65);
+	if (wav->samplesPerBlock != 320)
+	    fail("wave header error: format[%s] expects samplesPerBlock(%d) = %d",
+		    wav_format_str(wav->formatTag), wav->samplesPerBlock, 320);
+	bytespersample = WORD;  /* AFTER de-compression */
+	len -= 2;
+	break;
+#endif
+
     default:
       bytespersample = (wBitsPerSample + 7)/8;
 
@@ -488,33 +706,15 @@
 	fail("Sorry, don't understand .wav size");
     }
 
-    /* Skip past the rest of any left over fmt chunk */
-    while (len > 0 && !feof(ft->fp))
-    {
-	getc(ft->fp);
-	len--;
-    }
+    /* Skip anything left over from fmt chunk */
+    fSkip(ft->fp, len);
 
-    /* for ADPCM formats, there's a 'fact' chunk before
+    /* for non-PCM formats, there's a 'fact' chunk before
      * the upcoming 'data' chunk */
 
     /* Now look for the wave data chunk */
-    for (;;)
-    {
-	if ( fread(magic, 1, 4, ft->fp) != 4 )
-	    fail("WAVE file has missing data chunk");
-	len = rlong(ft);
-	if (strncmp("data", magic, 4) == 0)
-	    break;				/* Found the data chunk */
-	
-	while (len > 0 && !feof(ft->fp)) 	/* skip to next chunk */
-	{
-	    getc(ft->fp);
-	    len--;
-	}
-    }
-    
-    data_length = len;
+    wDataLength = len = findChunk(ft, "data");
+    /* findChunk() only returns if chunk was found */
 
     switch (wav->formatTag)
     {
@@ -522,12 +722,12 @@
     case WAVE_FORMAT_ADPCM:
 	/* Compute easiest part of number of samples.  For every block, there
 	   are samplesPerBlock samples to read. */
-	wav->numSamples = (((data_length / wav->blockAlign) * wav->samplesPerBlock) * ft->info.channels);
+	wav->numSamples = (((wDataLength / wav->blockAlign) * wav->samplesPerBlock) * ft->info.channels);
 	/* Next, for any partial blocks, subtract overhead from it and it
 	   will leave # of samples to read. */
 	wav->numSamples += 
-		((data_length % wav->blockAlign) - (6 * ft->info.channels)) * ft->info.channels;
-	/*report("datalen %d, numSamples %d",data_length, wav->numSamples);*/
+		((wDataLength % wav->blockAlign) - (6 * ft->info.channels)) * ft->info.channels;
+	/*report("datalen %d, numSamples %d",wDataLength, wav->numSamples);*/
 	wav->blockSamplesRemaining = 0;	       /* Samples left in buffer */
 	break;
 
@@ -534,18 +734,24 @@
     case WAVE_FORMAT_IMA_ADPCM:
 	/* Compute easiest part of number of samples.  For every block, there
 	   are samplesPerBlock samples to read. */
-	wav->numSamples = (((data_length / wav->blockAlign) * wav->samplesPerBlock) * ft->info.channels);
+	wav->numSamples = (((wDataLength / wav->blockAlign) * wav->samplesPerBlock) * ft->info.channels);
 	/* Next, for any partial blocks, substract overhead from it and it
 	   will leave # of samples to read. */
-	wav->numSamples += ((data_length - ((data_length/wav->blockAlign)
-					    *wav->blockAlign))
-			    - (3 * ft->info.channels)) * ft->info.channels;
+	wav->numSamples +=
+		((wDataLength % wav->blockAlign) - (3 * ft->info.channels)) * ft->info.channels;
 	wav->blockSamplesRemaining = 0;	       /* Samples left in buffer */
 	initImaTable();
 	break;
 
+#ifdef HAVE_LIBGSM
+    case WAVE_FORMAT_GSM610:
+	wav->numSamples = (((wDataLength / wav->blockAlign) * wav->samplesPerBlock) * ft->info.channels);
+	wavgsminit(ft);
+	break;
+#endif
+
     default:
-	wav->numSamples = data_length/ft->info.size;	/* total samples */
+	wav->numSamples = wDataLength/ft->info.size;	/* total samples */
 
     }
 
@@ -553,17 +759,34 @@
 	   wav_format_str(wav->formatTag), ft->info.channels,
 	   wChannels == 1 ? "" : "s", wSamplesPerSecond);
     report("        %d byte/sec, %d block align, %d bits/samp, %u data bytes",
-	   wAvgBytesPerSec, wav->blockAlign, wBitsPerSample, data_length);
+	   wAvgBytesPerSec, wav->blockAlign, wBitsPerSample, wDataLength);
 
     /* Can also report extended fmt information */
-    if (wav->formatTag == WAVE_FORMAT_ADPCM)
-	report("        %d Extsize, %d Samps/block, %d bytes/block %d Num Coefs\n",
+    switch (wav->formatTag)
+    {
+    case WAVE_FORMAT_ADPCM:
+	report("        %d Extsize, %d Samps/block, %d bytes/block %d Num Coefs",
 		wExtSize,wav->samplesPerBlock,wav->bytesPerBlock,wav->nCoefs);
-    else if (wav->formatTag == WAVE_FORMAT_IMA_ADPCM)
-	report("        %d Extsize, %d Samps/block, %d bytes/block\n",
+	break;
+
+    case WAVE_FORMAT_IMA_ADPCM:
+	report("        %d Extsize, %d Samps/block, %d bytes/block",
 		wExtSize,wav->samplesPerBlock,wav->bytesPerBlock);
+	break;
+
+#ifdef HAVE_LIBGSM
+    case WAVE_FORMAT_GSM610:
+	report("GSM .wav: %d Extsize, %d Samps/block,  %d samples",
+		wExtSize,wav->samplesPerBlock,wav->numSamples);
+	break;
+#endif
+
+    default:
+    }
+
 }
 
+
 /*
  * Read up to len samples from file.
  * Convert to signed longs.
@@ -584,6 +807,7 @@
 	/* read as much as possible and return quickly. */
 	switch (ft->info.style)
 	{
+	case IMA_ADPCM:
 	case ADPCM:
 	    done = 0;
 	    while (done < len) { /* Still want data? */
@@ -625,6 +849,13 @@
 	    }
 	    break;
 
+#ifdef HAVE_LIBGSM
+	case GSM:
+	    done = wavgsmread(ft, buf, len);
+	    if (done == 0 && wav->numSamples != 0)
+		warn("Premature EOF on .wav input file");
+	break;
+#endif
 	default: /* not ADPCM style */
 	    done = rawread(ft, buf, len);
 	    /* If software thinks there are more samples but I/O */
@@ -650,8 +881,20 @@
     if (wav->samples) free(wav->samples);
     if (wav->iCoefs) free(wav->iCoefs);
 
-    /* Needed for rawread() */
-    rawstopread(ft);
+    switch (ft->info.style)
+    {
+#ifdef HAVE_LIBGSM
+    case GSM:
+	wavgsmdestroy(ft);
+	break;
+#endif
+    case IMA_ADPCM:
+    case ADPCM:
+	break;
+    default:
+	/* Needed for rawread() */
+	rawstopread(ft);
+    }
 }
 
 void wavstartwrite(ft) 
@@ -672,10 +915,13 @@
 	wav->packet = NULL;
 	wav->samples = NULL;
 	wav->iCoefs = NULL;
-	if (wav->formatTag == WAVE_FORMAT_IMA_ADPCM ||
-	   wav->formatTag == WAVE_FORMAT_ADPCM)
+	switch (wav->formatTag)
 	{
-	    int ch, sbsize;
+	int ch, sbsize;
+	case WAVE_FORMAT_IMA_ADPCM:
+	    initImaTable();
+	/* intentional case fallthru! */
+	case WAVE_FORMAT_ADPCM:
 	    /* #channels already range-checked for overflow in wavwritehdr() */
 	    for (ch=0; ch<ft->info.channels; ch++)
 	    	wav->state[ch] = 0;
@@ -684,35 +930,119 @@
 	    wav->samples = (short *)malloc(sbsize*sizeof(short));
 	    wav->sampleTop = wav->samples + sbsize;
 	    wav->samplePtr = wav->samples;
-	    initImaTable();
+	    break;
+
+#ifdef HAVE_LIBGSM
+	case WAVE_FORMAT_GSM610:
+	    wavgsminit(ft);
+	    break;
+#endif
+	default:
 	}
 }
 
-#define WHDRSIZ1 8 /* "RIFF",(long)len,"WAVE" */
-#define FMTSIZ1 24 /* "fmt ",(long)len,... fmt chunk (without any Ext data) */
-#define DATASIZ1 8 /* "data",(long)len        */
+/* wavwritehdr:  write .wav headers as follows:
+ 
+bytes      variable      description
+0  - 3     'RIFF'
+4  - 7     wRiffLength   length of file minus the 8 byte riff header
+8  - 11    'WAVE'
+12 - 15    'fmt '
+16 - 19    wFmtSize       length of format chunk minus 8 byte header 
+20 - 21    wFormatTag     identifies PCM, ULAW etc
+22 - 23    wChannels      
+24 - 27    wSamplesPerSecond   samples per second per channel
+28 - 31    wAvgBytesPerSec     non-trivial for compressed formats
+32 - 33    wBlockAlign         basic block size
+34 - 35    wBitsPerSample      non-trivial for compressed formats
+
+PCM formats then go straight to the data chunk:
+36 - 39    'data'
+40 - 43     wDataLength   length of data chunk minus 8 byte header
+44 - (wDataLength + 43)    the data
+
+non-PCM formats must write an extended format chunk and a fact chunk:
+
+ULAW, ALAW formats:
+36 - 37    wExtSize = 0  the length of the format extension
+38 - 41    'fact'
+42 - 45    wFactSize = 4  length of the fact chunk minus 8 byte header
+46 - 49    wSamplesWritten   actual number of samples written out
+50 - 53    'data'
+54 - 57     wDataLength   length of data chunk minus 8 byte header
+58 - (wDataLength + 57)    the data
+
+
+GSM6.10  format:
+36 - 37    wExtSize = 2 the length in bytes of the format-dependent extension
+38 - 39    320           number of samples per  block 
+40 - 43    'fact'
+44 - 47    wFactSize = 4  length of the fact chunk minus 8 byte header
+48 - 51    wSamplesWritten   actual number of samples written out
+52 - 55    'data'
+56 - 59     wDataLength   length of data chunk minus 8 byte header
+60 - (wDataLength + 59)     the data
+(+ a padding byte if wDataLength is odd) 
+
+
+note that header contains (up to) 3 separate ways of describing the
+length of the file, all derived here from the number of (input)
+samples wav->numSamples in a way that is non-trivial for the blocked 
+and padded compressed formats:
+
+wRiffLength -      (riff header) the length of the file, minus 8 
+wSamplesWritten  -  (fact header) the number of samples written (after padding
+                   to a complete block eg for GSM)
+wDataLength     -   (data chunk header) the number of (valid) data bytes written
+
+*/
+
 void wavwritehdr(ft, second_header) 
 ft_t ft;
 int second_header;
 {
 	wav_t	wav = (wav_t) ft->priv;
-	LONG fmtsize = FMTSIZ1;
-	LONG factsize = 0; /* "fact",(long)len,??? */
 
-        /* wave file characteristics */
-	unsigned short wFormatTag = 0;          /* data format */
-	unsigned short wChannels;               /* number of channels */
-	ULONG  wSamplesPerSecond;       	/* samples per second per channel */
-	ULONG  wAvgBytesPerSec;        		/* estimate of bytes per second needed */
-	unsigned short wBlockAlign;             /* byte alignment of a basic sample block */
-	unsigned short wBitsPerSample;          /* bits per sample */
-	ULONG  data_length;             	/* length of sound data in bytes */
-	ULONG  bytespersample; 			/* bytes per sample (per channel) */
+	/* variables written to wav file header */
+	/* RIFF header */    
+	ULONG wRiffLength ;                 /* length of file after 8 byte riff header */
+	/* fmt chunk */
+	ULONG wFmtSize = 16;                /* size field of the fmt chunk */
+	unsigned short wFormatTag = 0;      /* data format */
+	unsigned short wChannels;           /* number of channels */
+	ULONG  wSamplesPerSecond;           /* samples per second per channel*/
+	ULONG  wAvgBytesPerSec=0;           /* estimate of bytes per second needed */
+	unsigned short wBlockAlign=0;       /* byte alignment of a basic sample block */
+	unsigned short wBitsPerSample=0;    /* bits per sample */
+	/* fmt chunk extension (not PCM) */
+	unsigned short wExtSize=0;          /* extra bytes in the format extension */
+	unsigned short wSamplesPerBlock;    /* samples per channel per block */
+	/* wSamplesPerBlock and other things may go into format extension */
 
-	/* Needed for rawwrite() */
-	if (ft->info.style != ADPCM)
+	/* fact chunk (not PCM) */
+	ULONG wFactSize=4;		/* length of the fact chunk */
+	ULONG wSamplesWritten=0;	/* windows doesnt seem to use this*/
+
+	/* data chunk */
+	ULONG  wDataLength=0x7ffff000L;	/* length of sound data in bytes */
+	/* end of variables written to header */
+
+	/* internal variables, intermediate values etc */
+	ULONG bytespersample; 		/* (uncompressed) bytes per sample (per channel) */
+	ULONG blocksWritten = 0;
+
+	if (ft->info.style != ADPCM &&
+	    ft->info.style != IMA_ADPCM &&
+	    ft->info.style != GSM
+	   )
 		rawstartwrite(ft);
 
+	wSamplesPerSecond = ft->info.rate;
+	wChannels = ft->info.channels;
+
+	if (wChannels == 0 || wChannels>64) /* FIXME: arbitrary upper limit */
+	    fail("Channels(%d) out-of-range\n",wChannels);
+
 	switch (ft->info.size)
 	{
 		case BYTE:
@@ -719,8 +1049,7 @@
 		        wBitsPerSample = 8;
 			if (ft->info.style != UNSIGNED &&
 			    ft->info.style != ULAW &&
-			    ft->info.style != ALAW &&
-			    !second_header)
+			    ft->info.style != ALAW)
 			{
 				warn("Only support unsigned, ulaw, or alaw with 8-bit data.  Forcing to unsigned");
 				ft->info.style = UNSIGNED;
@@ -745,118 +1074,153 @@
 			break;
 	}
 
+	bytespersample = WORD;	/* common default */
+	wSamplesPerBlock = 1;	/* common default */
+
 	switch (ft->info.style)
 	{
 		case UNSIGNED:
-			wFormatTag = WAVE_FORMAT_PCM;
-			break;
 		case SIGN2:
 			wFormatTag = WAVE_FORMAT_PCM;
+	    		bytespersample = (wBitsPerSample + 7)/8;
+	    		wBlockAlign = wChannels * bytespersample;
 			break;
 		case ALAW:
 			wFormatTag = WAVE_FORMAT_ALAW;
+	    		bytespersample = BYTE;
+	    		wBlockAlign = wChannels;
 			break;
 		case ULAW:
 			wFormatTag = WAVE_FORMAT_MULAW;
+	    		bytespersample = BYTE;
+	    		wBlockAlign = wChannels;
 			break;
 		case IMA_ADPCM:
 			/* warn("Experimental support writing IMA_ADPCM style.\n"); */
+			if (wChannels>16)
+			    fail("Channels(%d) must be <= 16\n",wChannels);
 			wFormatTag = WAVE_FORMAT_IMA_ADPCM;
+			wBlockAlign = wChannels * 64; /* reasonable default */
 			wBitsPerSample = 4;
-			fmtsize += 4;  /* plus ExtData */
-			factsize = 12; /* fact chunk   */
+	    		wExtSize = 2;
+			wSamplesPerBlock = ((wBlockAlign - 4*wChannels)/(4*wChannels))*8 + 1;
 			break;
 		case ADPCM:
 			/* warn("Experimental support writing ADPCM style.\n"); */
+			if (wChannels>16)
+			    fail("Channels(%d) must be <= 16\n",wChannels);
 			wFormatTag = WAVE_FORMAT_ADPCM;
+			wBlockAlign = wChannels * 128; /* reasonable default */
 			wBitsPerSample = 4;
-			fmtsize += 2+4+4*7;  /* plus ExtData */
-			factsize = 12;       /* fact chunk   */
+	    		wExtSize = 4+4*7;      /* Ext fmt data length */
+			wSamplesPerBlock = 2*(wBlockAlign - 7*wChannels)/wChannels + 2;
 			break;
+		case GSM:
+#ifdef HAVE_LIBGSM
+		    if (wChannels!=1)
+			fail("Channels(%d) must be == 1\n",wChannels);
+		    wFormatTag = WAVE_FORMAT_GSM610;
+		    /* wAvgBytesPerSec = 1625*(wSamplesPerSecond/8000.)+0.5; */
+		    wBlockAlign=65;
+		    wBitsPerSample=0;  /* not representable as int   */
+		    wExtSize=2;        /* length of format extension */
+		    wSamplesPerBlock = 320;
+#else
+		    fail("sorry, no GSM6.10 support, recompile sox with gsm library");
+#endif
+		    break;
 	}
 	wav->formatTag = wFormatTag;
-	
-	wSamplesPerSecond = ft->info.rate;
-	wChannels = ft->info.channels;
-	switch (wFormatTag)
-	{
-	case WAVE_FORMAT_IMA_ADPCM:
-	    if (wChannels>16)
-	    	fail("Channels(%d) must be <= 16\n",wChannels);
-	    bytespersample = 2;
-	    wBlockAlign = wChannels * 64; /* reasonable default */
-	    break;
-	case WAVE_FORMAT_ADPCM:
-	    if (wChannels>16)
-	    	fail("Channels(%d) must be <= 16\n",wChannels);
-	    bytespersample = 2;
-	    wBlockAlign = wChannels * 128; /* reasonable default */
-	    break;
-	default:
-	    bytespersample = (wBitsPerSample + 7)/8;
-	    wBlockAlign = wChannels * bytespersample;
-	}
 	wav->blockAlign = wBlockAlign;
-	wAvgBytesPerSec = ft->info.rate * wChannels * bytespersample;
-	if (!second_header)	/* use max length value first time */
-		data_length = 0x7fffffffL - (WHDRSIZ1+fmtsize+factsize+DATASIZ1);
-	else	/* fixup with real length */
-		switch(wFormatTag)
+	wav->samplesPerBlock = wSamplesPerBlock;
+
+	if (!second_header) { 	/* adjust for blockAlign */
+	    blocksWritten = wDataLength/wBlockAlign;
+	    wDataLength = blocksWritten * wBlockAlign;
+	    wSamplesWritten = blocksWritten * wSamplesPerBlock;
+	} else { 	/* fixup with real length */
+	    wSamplesWritten = wav->numSamples;
+	    switch(wFormatTag)
 		{
 	    	case WAVE_FORMAT_ADPCM:
 	    	case WAVE_FORMAT_IMA_ADPCM:
-		    data_length = wav->dataLength;
+		    wDataLength = wav->dataLength;
 		    break;
+#ifdef HAVE_LIBGSM
+		case WAVE_FORMAT_GSM610:
+		    /* intentional case fallthrough! */
+#endif
 		default:
-		    data_length = bytespersample * wav->numSamples;
+		    wSamplesWritten /= wChannels; /* because how rawwrite()'s work */
+		    blocksWritten = (wSamplesWritten+wSamplesPerBlock-1)/wSamplesPerBlock;
+		    wDataLength = blocksWritten * wBlockAlign;
 		}
+	}
 
+#ifdef HAVE_LIBGSM
+	if (wFormatTag == WAVE_FORMAT_GSM610)
+	    wDataLength = (wDataLength+1) & ~1; /*round up to even */
+#endif
+
+	if (wFormatTag != WAVE_FORMAT_PCM)
+	    wFmtSize += 2+wExtSize; /* plus ExtData */
+
+	wRiffLength = 4 + (8+wFmtSize) + (8+wDataLength); 
+	if (wFormatTag != WAVE_FORMAT_PCM) /* PCM omits the "fact" chunk */
+	    wRiffLength += (8+wFactSize);
+	
+	/* wAvgBytesPerSec <-- this is BEFORE compression, isn't it? */
+	/* if (wFormatTag != WAVE_FORMAT_GSM610)  GSM set this above */
+	wAvgBytesPerSec = ft->info.rate * wChannels * bytespersample;
+
 	/* figured out header info, so write it */
 	fputs("RIFF", ft->fp);
-	wlong(ft, data_length + WHDRSIZ1+fmtsize+factsize+DATASIZ1);/* Waveform chunk size: FIXUP(4) */
+	wlong(ft, wRiffLength);
 	fputs("WAVE", ft->fp);
 	fputs("fmt ", ft->fp);
-	wlong(ft, fmtsize-8);	/* fmt chunk size */
+	wlong(ft, wFmtSize);
 	wshort(ft, wFormatTag);
 	wshort(ft, wChannels);
 	wlong(ft, wSamplesPerSecond);
 	wlong(ft, wAvgBytesPerSec);
 	wshort(ft, wBlockAlign);
-	wshort(ft, wBitsPerSample); /* end of info common to all fmts */
+	wshort(ft, wBitsPerSample); /* end info common to all fmts */
+
+	/* if not PCM, we need to write out wExtSize even if wExtSize=0 */
+	if (wFormatTag != WAVE_FORMAT_PCM)
+	    wshort(ft,wExtSize);
+
 	switch (wFormatTag)
 	{
-	int i,nsamp;
+	int i;
 	case WAVE_FORMAT_IMA_ADPCM:
-	    wshort(ft, 2);		/* Ext fmt data length */
-	    wav->samplesPerBlock = ((wBlockAlign - 4*wChannels)/(4*wChannels))*8 + 1;
-	    wshort(ft, wav->samplesPerBlock);
-	    fputs("fact", ft->fp);
-	    wlong(ft, factsize-8);	/* fact chunk size */
-	    /* use max nsamps value first time */
-	    nsamp = (second_header)? wav->numSamples : 0x7fffffffL;
-	    wlong(ft, nsamp);
+	    wshort(ft, wSamplesPerBlock);
 	    break;
 	case WAVE_FORMAT_ADPCM:
-	    wshort(ft, 4+4*7);		/* Ext fmt data length */
-	    wav->samplesPerBlock = 2*(wBlockAlign - 7*wChannels)/wChannels + 2;
-	    wshort(ft, wav->samplesPerBlock);
+	    wshort(ft, wSamplesPerBlock);
 	    wshort(ft, 7); /* nCoefs */
 	    for (i=0; i<7; i++) {
 	      wshort(ft, iCoef[i][0]);
 	      wshort(ft, iCoef[i][1]);
 	    }
-
-	    fputs("fact", ft->fp);
-	    wlong(ft, factsize-8);	/* fact chunk size */
-	    /* use max nsamps value first time */
-	    nsamp = (second_header)? wav->numSamples : 0x7fffffffL;
-	    wlong(ft, nsamp);
 	    break;
+#ifdef HAVE_LIBGSM
+	case WAVE_FORMAT_GSM610:
+	    wshort(ft, wSamplesPerBlock);
+	    break;
+#endif
 	default:
 	}
 
+	/* if not PCM, write the 'fact' chunk */
+	if (wFormatTag != WAVE_FORMAT_PCM){
+	    fputs("fact", ft->fp);
+	    wlong(ft,wFactSize); 
+	    wlong(ft,wSamplesWritten);
+	}
+
 	fputs("data", ft->fp);
-	wlong(ft, data_length);		/* data chunk size: FIXUP(40) */
+	wlong(ft, wDataLength);		/* data chunk size */
 
 	if (!second_header) {
 		report("Writing Wave file: %s format, %d channel%s, %d samp/sec",
@@ -864,10 +1228,23 @@
 	        	wChannels == 1 ? "" : "s", wSamplesPerSecond);
 		report("        %d byte/sec, %d block align, %d bits/samp",
 	                wAvgBytesPerSec, wBlockAlign, wBitsPerSample);
-	} else
-		report("Finished writing Wave file, %u data bytes\n",data_length);
+	} else {
+		report("Finished writing Wave file, %u data bytes %u samples\n",
+			wDataLength,wav->numSamples);
+#ifdef HAVE_LIBGSM
+		if (wFormatTag == WAVE_FORMAT_GSM610){
+		    report("GSM6.10 format: %u blocks %u padded samples %u padded data bytes\n",
+			blocksWritten, wSamplesWritten, wDataLength);
+		    if (wav->gsmbytecount != wDataLength)
+			warn("help ! internal inconsistency - data_written %u gsmbytecount %u",
+				wDataLength, wav->gsmbytecount);
+
+		}
+#endif
+	}
 }
 
+
 void wavwrite(ft, buf, len) 
 ft_t ft;
 LONG *buf, len;
@@ -888,18 +1265,20 @@
 		   *p++ = ((*buf++) + 0x8000) >> 16;
 
 		wav->samplePtr = p;
-		if (p == wav->sampleTop) {
-		    if (wav->formatTag==WAVE_FORMAT_IMA_ADPCM)
-			ImaAdpcmWriteBlock(ft);
-		    else
-			AdpcmWriteBlock(ft);
-		}
+		if (p == wav->sampleTop)
+		    xxxAdpcmWriteBlock(ft);
 
 	    }
 	    break;
 
-	default:
+#ifdef HAVE_LIBGSM
+	case WAVE_FORMAT_GSM610:
 	    wav->numSamples += len;
+	    wavgsmwrite(ft, buf, len);
+	    break;
+#endif
+	default:
+	    wav->numSamples += len; /* must later be divided by wChannels */
 	    rawwrite(ft, buf, len);
 	}
 }
@@ -912,11 +1291,14 @@
 	switch (wav->formatTag)
 	{
 	case WAVE_FORMAT_IMA_ADPCM:
-	    ImaAdpcmWriteBlock(ft);
-	    break;
 	case WAVE_FORMAT_ADPCM:
-	    AdpcmWriteBlock(ft);
+	    xxxAdpcmWriteBlock(ft);
 	    break;
+#ifdef HAVE_LIBGSM
+	case WAVE_FORMAT_GSM610:
+	    wavgsmstopwrite(ft);
+	    break;
+#endif
 	default:
 	    rawstopwrite(ft);
 	}