ref: da6bd76bcbdbad47ce45df93d3e000b91bc85673
dir: /src/wav.c/
/*
* Microsoft's WAVE sound format driver
*
* This source code is freely redistributable and may be used for
* any purpose. This copyright notice must be maintained.
* Lance Norskog And Sundry Contributors are not responsible for
* the consequences of using this software.
*
* Change History:
*
* November 11, 1999 - Stan Brooks (stabro@megsinet.com)
* Mods for faster adpcm decoding and addition of ima_adpcm
* writing... low-level codex functions moved to external
* modules ima_rw.c and adpcm.c. Some general cleanup, consistent
* with writing *adpcm and other output formats.
* Headers for adpcm include the 'fact' subchunk.
*
* September 11, 1998 - Chris Bagwell (cbagwell@sprynet.com)
* Fixed length bug for IMA and MS ADPCM files.
*
* June 1, 1998 - Chris Bagwell (cbagwell@sprynet.com)
* Fixed some compiler warnings as reported by Kjetil Torgrim Homme
* <kjetilho@ifi.uio.no>.
* Fixed bug that caused crashes when reading mono MS ADPCM files. Patch
* was sent from Michael Brown (mjb@pootle.demon.co.uk).
*
* March 15, 1998 - Chris Bagwell (cbagwell@sprynet.com)
* Added support for Microsoft's ADPCM and IMA (or better known as
* DVI) ADPCM format for wav files. Info on these formats
* was taken from the xanim project, written by
* Mark Podlipec (podlipec@ici.net). For those pieces of code,
* the following copyrights notice applies:
*
* XAnim Copyright (C) 1990-1997 by Mark Podlipec.
* All rights reserved.
*
* This software may be freely copied, modified and redistributed without
* fee for non-commerical purposes provided that this copyright notice is
* preserved intact on all copies and modified copies.
*
* There is no warranty or other guarantee of fitness of this software.
* It is provided solely "as is". The author(s) disclaim(s) all
* responsibility and liability with respect to this software's usage
* or its effect upon hardware or computer systems.
*
* NOTE: Previous maintainers weren't very good at providing contact
* information.
*
* Copyright 1992 Rick Richardson
* Copyright 1991 Lance Norskog And Sundry Contributors
*
* Fixed by various contributors previous to 1998:
* 1) Little-endian handling
* 2) Skip other kinds of file data
* 3) Handle 16-bit formats correctly
* 4) Not go into infinite loop
*
* User options should override file header - we assumed user knows what
* they are doing if they specify options.
* Enhancements and clean up by Graeme W. Gill, 93/5/17
*
* Info for format tags can be found at:
* http://www.microsoft.com/asf/resources/draft-ietf-fleischman-codec-subtree-01.txt
*
*/
#include <string.h> /* Included for strncmp */
#include <stdlib.h> /* Included for malloc and free */
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif
#include <stdio.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h> /* For SEEK_* defines if not found in stdio */
#endif
#include "st.h"
#include "wav.h"
#include "ima_rw.h"
#include "adpcm.h"
/* Private data for .wav file */
typedef struct wavstuff {
LONG numSamples;
LONG dataLength; /* needed for ADPCM writing */
unsigned short formatTag; /* What type of encoding file is using */
/* The following are only needed for ADPCM wav files */
unsigned short samplesPerBlock;
unsigned short bytesPerBlock;
unsigned short blockAlign;
unsigned short nCoefs; /* ADPCM: number of coef sets */
short *iCoefs; /* ADPCM: coef sets */
unsigned char *packet; /* Temporary buffer for packets */
short *samples; /* interleaved samples buffer */
short *samplePtr; /* Pointer to current samples */
short *sampleTop; /* End of samples-buffer */
unsigned short blockSamplesRemaining;/* Samples remaining in each channel */
int state[16]; /* last, because maybe longer */
} *wav_t;
static char *wav_format_str();
LONG rawread(P3(ft_t, LONG *, LONG));
void rawwrite(P3(ft_t, LONG *, LONG));
void wavwritehdr(P2(ft_t, int));
/****************************************************************************/
/* IMA ADPCM Support Functions Section */
/****************************************************************************/
unsigned short ImaAdpcmReadBlock(ft)
ft_t ft;
{
wav_t wav = (wav_t) ft->priv;
int bytesRead;
int samplesThisBlock;
/* Pull in the packet and check the header */
bytesRead = fread(wav->packet,1,wav->blockAlign,ft->fp);
if (bytesRead < wav->blockAlign)
{
/* 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. */
if (bytesRead >= (4 * ft->info.channels))
{
samplesThisBlock = (wav->blockAlign - (3 * ft->info.channels));
}
else
{
warn ("Premature EOF on .wav input file");
return 0;
}
}
else
samplesThisBlock = wav->samplesPerBlock;
wav->samplePtr = wav->samples;
/* For a full block, the following should be true: */
/* wav->samplesPerBlock = blockAlign - 8byte header + 1 sample in header */
ImaBlockExpandI(ft->info.channels, wav->packet, wav->samples, samplesThisBlock);
return samplesThisBlock;
}
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 */
/****************************************************************************/
/*
*
* MsAdpcmReadBlock - Grab and decode complete block of samples
*
*/
unsigned short MsAdpcmReadBlock(ft)
ft_t ft;
{
wav_t wav = (wav_t) ft->priv;
int bytesRead;
int samplesThisBlock;
const char *errmsg;
/* Pull in the packet and check the header */
bytesRead = fread(wav->packet,1,wav->blockAlign,ft->fp);
if (bytesRead < wav->blockAlign)
{
/* 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. */
if (bytesRead >= (7 * ft->info.channels))
{
samplesThisBlock = (wav->blockAlign - (6 * ft->info.channels));
}
else
{
warn ("Premature EOF on .wav input file");
return 0;
}
}
else
samplesThisBlock = wav->samplesPerBlock;
errmsg = AdpcmBlockExpandI(ft->info.channels, wav->packet, wav->samples, samplesThisBlock);
if (errmsg)
warn((char*)errmsg);
return samplesThisBlock;
}
/****************************************************************************/
/* General Sox WAV file code */
/****************************************************************************/
/*
* Do anything required before you start reading samples.
* Read file header.
* Find out sampling rate,
* size and style of samples,
* mono/stereo/quad.
*/
void wavstartread(ft)
ft_t ft;
{
wav_t wav = (wav_t) ft->priv;
char magic[4];
ULONG len;
int littlendian = 1;
char *endptr;
/* wave file characteristics */
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 */
ULONG data_length; /* length of sound data in bytes */
ULONG bytespersample; /* bytes per sample (per channel */
/* This is needed for rawread() */
rawstartread(ft);
endptr = (char *) &littlendian;
if (!*endptr) ft->swap = ft->swap ? 0 : 1;
/* If you need to seek around the input file. */
if (0 && ! ft->seekable)
fail("WAVE input file must be a file, not a pipe");
if ( fread(magic, 1, 4, ft->fp) != 4 || strncmp("RIFF", magic, 4))
fail("WAVE: RIFF header not found");
len = 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 )
fail("WAVE file fmt chunk is too short");
wav->formatTag = rshort(ft);
len -= 2;
switch (wav->formatTag)
{
case WAVE_FORMAT_UNKNOWN:
fail("WAVE file is in unsupported Microsoft Official Unknown format.");
case WAVE_FORMAT_PCM:
/* 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 == ADPCM)
ft->info.style = ADPCM;
else
warn("User options overriding style read in .wav header");
break;
case WAVE_FORMAT_IEEE_FLOAT:
fail("Sorry, this WAV file is in IEEE Float format.");
case WAVE_FORMAT_ALAW:
if (ft->info.style == -1 || ft->info.style == ALAW)
ft->info.style = ALAW;
else
warn("User options overriding style read in .wav header");
break;
case WAVE_FORMAT_MULAW:
if (ft->info.style == -1 || ft->info.style == ULAW)
ft->info.style = ULAW;
else
warn("User options overriding style read in .wav header");
break;
case WAVE_FORMAT_OKI_ADPCM:
fail("Sorry, this WAV file is in OKI ADPCM format.");
case WAVE_FORMAT_DIGISTD:
fail("Sorry, this WAV file is in Digistd format.");
case WAVE_FORMAT_DIGIFIX:
fail("Sorry, this WAV file is in Digifix format.");
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.");
case WAVE_FORMAT_ROCKWELL_ADPCM:
fail("Sorry, this WAV file is in Rockwell ADPCM format.");
case WAVE_FORMAT_ROCKWELL_DIGITALK:
fail("Sorry, this WAV file is in Rockwell DIGITALK format.");
case WAVE_FORMAT_G721_ADPCM:
fail("Sorry, this WAV file is in G.721 ADPCM format.");
case WAVE_FORMAT_G728_CELP:
fail("Sorry, this WAV file is in G.728 CELP format.");
case WAVE_FORMAT_MPEG:
fail("Sorry, this WAV file is in MPEG format.");
case WAVE_FORMAT_MPEGLAYER3:
fail("Sorry, this WAV file is in MPEG Layer 3 format.");
case WAVE_FORMAT_G726_ADPCM:
fail("Sorry, this WAV file is in G.726 ADPCM format.");
case WAVE_FORMAT_G722_ADPCM:
fail("Sorry, this WAV file is in G.722 ADPCM format.");
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;
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. */
switch (wav->formatTag)
{
case WAVE_FORMAT_ADPCM:
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 + 7)/2 * ft->info.channels;
wav->nCoefs = rshort(ft);
if (wav->nCoefs < 7 || wav->nCoefs > 0x100) {
fail("ADPCM file nCoefs (%.4hx) makes no sense\n", wav->nCoefs);
}
wav->packet = (unsigned char *)malloc(wav->blockAlign);
len -= 6;
wav->samples = (short *)malloc(wChannels*wav->samplesPerBlock*sizeof(short));
/* SJB: will need iCoefs later for adpcm.c */
wav->iCoefs = (short *)malloc(wav->nCoefs * 2 * sizeof(short));
{
int i;
for (i=0; len>=2 && i < 2*wav->nCoefs; i++) {
wav->iCoefs[i] = rshort(ft);
/* fprintf(stderr,"iCoefs[%2d] %4d\n",i,wav->iCoefs[i]); */
len -= 2;
}
}
bytespersample = WORD; /* AFTER de-compression */
break;
case WAVE_FORMAT_IMA_ADPCM:
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;
wav->packet = (unsigned char *)malloc(wav->blockAlign);
len -= 4;
wav->samples = (short *)malloc(wChannels*wav->samplesPerBlock*sizeof(short));
bytespersample = WORD; /* AFTER de-compression */
break;
default:
bytespersample = (wBitsPerSample + 7)/8;
}
switch (bytespersample)
{
case BYTE:
/* User options take precedence */
if (ft->info.size == -1 || ft->info.size == BYTE)
ft->info.size = BYTE;
else
warn("User options overriding size read in .wav header");
/* Now we have enough information to set default styles. */
if (ft->info.style == -1)
ft->info.style = UNSIGNED;
break;
case WORD:
if (ft->info.size == -1 || ft->info.size == WORD)
ft->info.size = WORD;
else
warn("User options overriding size read in .wav header");
/* Now we have enough information to set default styles. */
if (ft->info.style == -1)
ft->info.style = SIGN2;
break;
case DWORD:
if (ft->info.size == -1 || ft->info.size == DWORD)
ft->info.size = DWORD;
else
warn("User options overriding size read in .wav header");
/* Now we have enough information to set default styles. */
if (ft->info.style == -1)
ft->info.style = SIGN2;
break;
default:
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--;
}
/* for ADPCM formats, there's a useless(?) '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;
switch (wav->formatTag)
{
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);
/* Next, for any partial blocks, subtract overhead from it and it
will leave # of samples to read. */
wav->numSamples += ((data_length - ((data_length/wav->blockAlign)
*wav->blockAlign))
- (6 * ft->info.channels)) * ft->info.channels;
wav->blockSamplesRemaining = 0; /* Samples left in buffer */
break;
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);
/* 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->blockSamplesRemaining = 0; /* Samples left in buffer */
initImaTable();
break;
default:
wav->numSamples = data_length/ft->info.size; /* total samples */
}
report("Reading Wave file: %s format, %d channel%s, %d samp/sec",
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);
/* 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",
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",
wExtSize,wav->samplesPerBlock,wav->bytesPerBlock);
}
/*
* Read up to len samples from file.
* Convert to signed longs.
* Place in buf[].
* Return number of samples read.
*/
LONG wavread(ft, buf, len)
ft_t ft;
LONG *buf, len;
{
wav_t wav = (wav_t) ft->priv;
LONG done;
if (len > wav->numSamples) len = wav->numSamples;
/* If file is in ADPCM style then read in multiple blocks else */
/* read as much as possible and return quickly. */
switch (ft->info.style)
{
case ADPCM:
done = 0;
while (done < len) { /* Still want data? */
/* See if need to read more from disk */
if (wav->blockSamplesRemaining == 0) {
if (wav->formatTag == WAVE_FORMAT_IMA_ADPCM)
wav->blockSamplesRemaining = ImaAdpcmReadBlock(ft);
else
wav->blockSamplesRemaining = MsAdpcmReadBlock(ft);
if (wav->blockSamplesRemaining == 0)
{
/* Don't try to read any more samples */
wav->numSamples = 0;
return done;
}
wav->blockSamplesRemaining *= ft->info.channels;
wav->samplePtr = wav->samples;
}
/* Copy interleaved data into buf, converting short to LONG */
{
short *p, *top;
int ct;
ct = len-done;
if (ct > wav->blockSamplesRemaining)
ct = wav->blockSamplesRemaining;
done += ct;
wav->blockSamplesRemaining -= ct;
p = wav->samplePtr;
top = p+ct;
/* Output is already signed */
while (p<top)
{
*buf++ = LEFT((*p++), 16);
}
wav->samplePtr = p;
}
}
break;
default: /* not ADPCM style */
done = rawread(ft, buf, len);
/* If software thinks there are more samples but I/O */
/* says otherwise, let the user know about this. */
if (done == 0 && wav->numSamples != 0)
warn("Premature EOF on .wav input file");
}
wav->numSamples -= done;
return done;
}
/*
* Do anything required when you stop reading samples.
* Don't close input file!
*/
void wavstopread(ft)
ft_t ft;
{
wav_t wav = (wav_t) ft->priv;
if (wav->packet) free(wav->packet);
if (wav->samples) free(wav->samples);
if (wav->iCoefs) free(wav->iCoefs);
/* Needed for rawread() */
rawstopread(ft);
}
void wavstartwrite(ft)
ft_t ft;
{
wav_t wav = (wav_t) ft->priv;
int littlendian = 1;
char *endptr;
endptr = (char *) &littlendian;
if (!*endptr) ft->swap = ft->swap ? 0 : 1;
wav->numSamples = 0;
wav->dataLength = 0;
if (!ft->seekable)
warn("Length in output .wav header will be wrong since can't seek to fix it");
wavwritehdr(ft, 0); /* also calculates various wav->* info */
wav->packet = NULL;
wav->samples = NULL;
wav->iCoefs = NULL;
if (wav->formatTag == WAVE_FORMAT_IMA_ADPCM) {
int ch, sbsize;
/* #channels already range-checked for overflow in wavwritehdr() */
for (ch=0; ch<ft->info.channels; ch++)
wav->state[ch] = 0;
sbsize = ft->info.channels * wav->samplesPerBlock;
wav->packet = (unsigned char *)malloc(wav->blockAlign);
wav->samples = (short *)malloc(sbsize*sizeof(short));
wav->sampleTop = wav->samples + sbsize;
wav->samplePtr = wav->samples;
initImaTable();
}
}
#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 */
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) */
/* Needed for rawwrite() */
if (ft->info.style != ADPCM)
rawstartwrite(ft);
switch (ft->info.size)
{
case BYTE:
wBitsPerSample = 8;
if (ft->info.style != UNSIGNED &&
ft->info.style != ULAW &&
ft->info.style != ALAW &&
!second_header)
{
warn("Only support unsigned, ulaw, or alaw with 8-bit data. Forcing to unsigned");
ft->info.style = UNSIGNED;
}
break;
case WORD:
wBitsPerSample = 16;
if ((ft->info.style == UNSIGNED ||
ft->info.style == ULAW ||
ft->info.style == ALAW) &&
!second_header)
{
warn("Do not support Unsigned, ulaw, or alaw with 16 bit data. Forcing to Signed");
ft->info.style = SIGN2;
}
break;
case DWORD:
wBitsPerSample = 32;
break;
default:
wBitsPerSample = 32;
break;
}
switch (ft->info.style)
{
case UNSIGNED:
wFormatTag = WAVE_FORMAT_PCM;
break;
case SIGN2:
wFormatTag = WAVE_FORMAT_PCM;
break;
case ALAW:
wFormatTag = WAVE_FORMAT_ALAW;
break;
case ULAW:
wFormatTag = WAVE_FORMAT_MULAW;
break;
case ADPCM:
warn("Experimental support writing IMA_ADPCM style.\n");
wFormatTag = WAVE_FORMAT_IMA_ADPCM;
wBitsPerSample = 4;
fmtsize += 4; /* plus ExtData */
factsize = 12; /* fact chunk */
break;
}
wav->formatTag = wFormatTag;
wSamplesPerSecond = ft->info.rate;
wChannels = ft->info.channels;
if (wFormatTag == WAVE_FORMAT_IMA_ADPCM) {
if (wChannels>16)
fail("Channels(%d) must be <= 16\n",wChannels);
bytespersample = 2;
wBlockAlign = wChannels * 64; /* reasonable default */
} else {
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 */
{
if (ft->info.style == ADPCM)
data_length = wav->dataLength; /* FIXME: not really */
else
data_length = bytespersample * wav->numSamples;
}
/* figured out header info, so write it */
fputs("RIFF", ft->fp);
wlong(ft, data_length + WHDRSIZ1+fmtsize+factsize+DATASIZ1);/* Waveform chunk size: FIXUP(4) */
fputs("WAVE", ft->fp);
fputs("fmt ", ft->fp);
wlong(ft, fmtsize-8); /* fmt chunk size */
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 */
switch (wFormatTag)
{
int nsamp;
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);
break;
default:
}
fputs("data", ft->fp);
wlong(ft, data_length); /* data chunk size: FIXUP(40) */
if (!second_header) {
report("Writing Wave file: %s format, %d channel%s, %d samp/sec",
wav_format_str(wFormatTag), wChannels,
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);
}
void wavwrite(ft, buf, len)
ft_t ft;
LONG *buf, len;
{
wav_t wav = (wav_t) ft->priv;
switch (wav->formatTag)
{
case WAVE_FORMAT_IMA_ADPCM:
while (len>0) {
short *p = wav->samplePtr;
short *top = wav->sampleTop;
if (top>p+len) top = p+len;
len -= top-p; /* update residual len */
while (p < top)
*p++ = ((*buf++) + 0x8000) >> 16;
wav->samplePtr = p;
if (p == wav->sampleTop)
ImaAdpcmWriteBlock(ft);
}
break;
default:
wav->numSamples += len;
rawwrite(ft, buf, len);
}
}
void wavstopwrite(ft)
ft_t ft;
{
wav_t wav = (wav_t) ft->priv;
/* Call this to flush out any remaining data. */
if (wav->formatTag == WAVE_FORMAT_IMA_ADPCM) {
ImaAdpcmWriteBlock(ft);
} else {
rawstopwrite(ft);
}
if (wav->packet) free(wav->packet);
if (wav->samples) free(wav->samples);
if (wav->iCoefs) free(wav->iCoefs);
/* All samples are already written out. */
/* If file header needs fixing up, for example it needs the */
/* the number of samples in a field, seek back and write them here. */
if (!ft->seekable)
return;
if (fseek(ft->fp, 0L, SEEK_SET) != 0)
fail("Sorry, can't rewind output file to rewrite .wav header.");
wavwritehdr(ft, 1);
}
/*
* Return a string corresponding to the wave format type.
*/
static char *
wav_format_str(wFormatTag)
unsigned wFormatTag;
{
switch (wFormatTag)
{
case WAVE_FORMAT_UNKNOWN:
return "Microsoft Official Unknown";
case WAVE_FORMAT_PCM:
return "Microsoft PCM";
case WAVE_FORMAT_ADPCM:
return "Microsoft ADPCM";
case WAVE_FORMAT_IEEE_FLOAT:
return "IEEE Float";
case WAVE_FORMAT_ALAW:
return "Microsoft A-law";
case WAVE_FORMAT_MULAW:
return "Microsoft U-law";
case WAVE_FORMAT_OKI_ADPCM:
return "OKI ADPCM format.";
case WAVE_FORMAT_IMA_ADPCM:
return "IMA ADPCM";
case WAVE_FORMAT_DIGISTD:
return "Digistd format.";
case WAVE_FORMAT_DIGIFIX:
return "Digifix format.";
case WAVE_FORMAT_DOLBY_AC2:
return "Dolby AC2";
case WAVE_FORMAT_GSM610:
return "GSM 6.10";
case WAVE_FORMAT_ROCKWELL_ADPCM:
return "Rockwell ADPCM";
case WAVE_FORMAT_ROCKWELL_DIGITALK:
return "Rockwell DIGITALK";
case WAVE_FORMAT_G721_ADPCM:
return "G.721 ADPCM";
case WAVE_FORMAT_G728_CELP:
return "G.728 CELP";
case WAVE_FORMAT_MPEG:
return "MPEG";
case WAVE_FORMAT_MPEGLAYER3:
return "MPEG Layer 3";
case WAVE_FORMAT_G726_ADPCM:
return "G.726 ADPCM";
case WAVE_FORMAT_G722_ADPCM:
return "G.722 ADPCM";
default:
return "Unknown";
}
}