ref: fa894e6a3fc1f13f494aaae3089b457eda96dfba
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: * * 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 */ #include <string.h> /* Included for strncmp */ #include <stdlib.h> /* Included for malloc and free */ #include "st.h" #include "wav.h" /* Private data for .wav file */ typedef struct wavstuff { LONG numSamples; int second_header; /* non-zero on second header write */ 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; short *samples[2]; /* Left and Right sample buffers */ short *samplePtr[2]; /* Pointers to current samples */ unsigned short blockSamplesRemaining;/* Samples remaining in each channel */ unsigned char *packet; /* Temporary buffer for packets */ } *wav_t; static char *wav_format_str(); LONG rawread(P3(ft_t, LONG *, LONG)); void rawwrite(P3(ft_t, LONG *, LONG)); void wavwritehdr(P1(ft_t)); /* * * Lookup tables for MS ADPCM format * */ static LONG gaiP4[] = { 230, 230, 230, 230, 307, 409, 512, 614, 768, 614, 512, 409, 307, 230, 230, 230 }; /* TODO : The first 7 coef's are are always hardcode and must appear in the actual WAVE file. They should be read in in case a sound program added extras to the list. */ static LONG gaiCoef1[] = { 256, 512, 0, 192, 240, 460, 392 }; static LONG gaiCoef2[] = { 0, -256, 0, 64, 0,-208, -232}; /* * * Lookup tables for IMA ADPCM format * */ static int imaIndexAdjustTable[16] = { -1, -1, -1, -1, /* +0 - +3, decrease the step size */ 2, 4, 6, 8, /* +4 - +7, increase the step size */ -1, -1, -1, -1, /* -0 - -3, decrease the step size */ 2, 4, 6, 8, /* -4 - -7, increase the step size */ }; static int imaStepSizeTable[89] = { 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 }; /****************************************************************************/ /* IMA ADPCM Support Functions Section */ /****************************************************************************/ /* * * MsAdpcmDecode - Decode a given sample and update state tables * */ short ImaAdpcmDecode(deltaCode, state) unsigned char deltaCode; ImaState_t *state; { /* Get the current step size */ int step; int difference; step = imaStepSizeTable[state->index]; /* Construct the difference by scaling the current step size */ /* This is approximately: difference = (deltaCode+.5)*step/4 */ difference = step>>3; if ( deltaCode & 1 ) difference += step>>2; if ( deltaCode & 2 ) difference += step>>1; if ( deltaCode & 4 ) difference += step; if ( deltaCode & 8 ) difference = -difference; /* Build the new sample */ state->previousValue += difference; if (state->previousValue > 32767) state->previousValue = 32767; else if (state->previousValue < -32768) state->previousValue = -32768; /* Update the step for the next sample */ state->index += imaIndexAdjustTable[deltaCode]; if (state->index < 0) state->index = 0; else if (state->index > 88) state->index = 88; return state->previousValue; } /* * * ImaAdpcmNextBlock - Grab and decode complete block of samples * */ unsigned short ImaAdpcmNextBlock(ft) ft_t ft; { wav_t wav = (wav_t) ft->priv; /* Pull in the packet and check the header */ unsigned short bytesRead; unsigned char *bytePtr; ImaState_t state[2]; /* One decompressor state for each channel */ int ch; unsigned short remaining; unsigned short samplesThisBlock; int i; unsigned char b; 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 >= (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; bytePtr = wav->packet; /* Read the four-byte header for each channel */ /* Reset the decompressor */ for(ch=0;ch < ft->info.channels; ch++) { /* Got this from xanim */ state[ch].previousValue = ((int)bytePtr[1]<<8) + (int)bytePtr[0]; if (state[ch].previousValue & 0x8000) state[ch].previousValue -= 0x10000; if (bytePtr[2] > 88) { warn("IMA ADPCM Format Error (bad index value) in wav file"); state[ch].index = 88; } else state[ch].index = bytePtr[2]; if (bytePtr[3]) warn("IMA ADPCM Format Error (synchronization error) in wav file"); bytePtr+=4; /* Skip this header */ wav->samplePtr[ch] = wav->samples[ch]; /* Decode one sample for the header */ *(wav->samplePtr[ch]++) = state[ch].previousValue; } /* Decompress nybbles. Remainging is bytes in block minus header */ /* Subtract the one sample taken from header */ remaining = samplesThisBlock-1; while (remaining) { /* Always decode 8 samples */ remaining -= 8; /* Decode 8 left samples */ for (i=0;i<4;i++) { b = *bytePtr++; *(wav->samplePtr[0]++) = ImaAdpcmDecode(b & 0x0f,&state[0]); *(wav->samplePtr[0]++) = ImaAdpcmDecode((b>>4) & 0x0f,&state[0]); } if (ft->info.channels < 2) continue; /* If mono, skip rest of loop */ /* Decode 8 right samples */ for (i=0;i<4;i++) { b = *bytePtr++; *(wav->samplePtr[1]++) = ImaAdpcmDecode(b & 0x0f,&state[1]); *(wav->samplePtr[1]++) = ImaAdpcmDecode((b>>4) & 0x0f,&state[1]); } } /* For a full block, the following should be true: */ /* wav->samplesPerBlock = blockAlign - 8byte header + 1 sample in header */ return wav->samplesPerBlock; } /****************************************************************************/ /* MS ADPCM Support Functions Section */ /****************************************************************************/ /* * * MsAdpcmDecode - Decode a given sample and update state tables * */ LONG MsAdpcmDecode(deltaCode, state) LONG deltaCode; MsState_t *state; { LONG predict; LONG sample; LONG idelta; /** Compute next Adaptive Scale Factor (ASF) **/ idelta = state->index; state->index = (gaiP4[deltaCode] * idelta) >> 8; if (state->index < 16) state->index = 16; if (deltaCode & 0x08) deltaCode = deltaCode - 0x10; /** Predict next sample **/ predict = ((state->sample1 * gaiCoef1[state->bpred]) + (state->sample2 * gaiCoef2[state->bpred])) >> 8; /** reconstruct original PCM **/ sample = (deltaCode * idelta) + predict; if (sample > 32767) sample = 32767; else if (sample < -32768) sample = -32768; state->sample2 = state->sample1; state->sample1 = sample; return (sample); } /* * * MsAdpcmNextBlock - Grab and decode complete block of samples * */ unsigned short MsAdpcmNextBlock(ft) ft_t ft; { wav_t wav = (wav_t) ft->priv; unsigned short bytesRead; unsigned char *bytePtr; MsState_t state[2]; /* One decompressor state for each channel */ unsigned short samplesThisBlock; unsigned short remaining; unsigned char b; /* 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; bytePtr = wav->packet; /* Read the four-byte header for each channel */ /* Reset the decompressor */ state[0].bpred = *bytePtr++; /* Left */ if (ft->info.channels > 1) state[1].bpred = *bytePtr++; /* Right */ else state[1].bpred = 0; /* 7 should be variable from AVI/WAV header */ if (state[0].bpred >= 7) { warn("MSADPCM bpred %x and should be less than 7\n",state[0].bpred); return(0); } if (state[1].bpred >= 7) { warn("MSADPCM bpred %x and should be less than 7\n",state[1].bpred); return(0); } state[0].index = *bytePtr++; state[0].index |= (*bytePtr++)<<8; if (state[0].index & 0x8000) state[0].index -= 0x10000; if (ft->info.channels > 1) { state[1].index = *bytePtr++; state[1].index |= (*bytePtr++)<<8; if (state[1].index & 0x8000) state[1].index -= 0x10000; } state[0].sample1 = *bytePtr++; state[0].sample1 |= (*bytePtr++)<<8; if (state[0].sample1 & 0x8000) state[0].sample1 -= 0x10000; if (ft->info.channels > 1) { state[1].sample1 = *bytePtr++; state[1].sample1 |= (*bytePtr++)<<8; if (state[1].sample1 & 0x8000) state[1].sample1 -= 0x10000; } state[0].sample2 = *bytePtr++; state[0].sample2 |= (*bytePtr++)<<8; if (state[0].sample2 & 0x8000) state[0].sample2 -= 0x10000; if (ft->info.channels > 1) { state[1].sample2 = *bytePtr++; state[1].sample2 |= (*bytePtr++)<<8; if (state[1].sample2 & 0x8000) state[1].sample2 -= 0x10000; } wav->samplePtr[0] = wav->samples[0]; wav->samplePtr[1] = wav->samples[1]; /* Decode two samples for the header */ *(wav->samplePtr[0]++) = state[0].sample2; *(wav->samplePtr[0]++) = state[0].sample1; if (ft->info.channels > 1) { *(wav->samplePtr[1]++) = state[1].sample2; *(wav->samplePtr[1]++) = state[1].sample1; } /* Decompress nybbles. Minus 2 included in header */ remaining = samplesThisBlock-2; while (remaining) { b = *bytePtr++; *(wav->samplePtr[0]++) = MsAdpcmDecode((b>>4) & 0x0f, &state[0]); remaining--; if (ft->info.channels == 1) { *(wav->samplePtr[0]++) = MsAdpcmDecode(b & 0x0f, &state[0]); remaining--; } else { *(wav->samplePtr[1]++) = MsAdpcmDecode(b & 0x0f, &state[1]); } } 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 */ unsigned short wNumCoefs = 0; /* Related to IMA ADPCM */ ULONG data_length; /* length of sound data in bytes */ ULONG bytespersample; /* bytes per sample (per channel */ endptr = (char *) &littlendian; if (!*endptr) ft->swap = 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 = rllong(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 = rllong(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--; } } if ( len < 16 ) fail("WAVE file fmt chunk is too short"); wav->formatTag = rlshort(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_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 IBM_FORMAT_MULAW: fail("Sorry, this WAV file is in IBM U-law format."); case IBM_FORMAT_ALAW: fail("Sorry, this WAV file is in IBM A-law format."); case IBM_FORMAT_ADPCM: fail("Sorry, this WAV file is in IBM ADPCM format."); default: fail("WAV file has unknown format type"); } wChannels = rlshort(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 = rllong(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 = rllong(ft); /* Average bytes/second */ wav->blockAlign = rlshort(ft); /* Block align */ len -= 6; /* bits per sample per channel */ wBitsPerSample = rlshort(ft); len -= 2; /* ADPCM formats have extended fmt chunk. Check for those cases. */ if (wav->formatTag == WAVE_FORMAT_ADPCM) { if (wBitsPerSample != 4) fail("Can only handle 4-bit MS ADPCM in wav files"); wExtSize = rlshort(ft); wav->samplesPerBlock = rlshort(ft); wav->bytesPerBlock = (wav->samplesPerBlock + 7)/2 * ft->info.channels; wNumCoefs = rlshort(ft); wav->packet = (unsigned char *)malloc(wav->blockAlign); len -= 6; wav->samples[1] = wav->samples[0] = 0; /* Use ft->info.channels after this becuase wChannels is now bad */ while (wChannels-- > 0) wav->samples[wChannels] = (short *)malloc(wav->samplesPerBlock*sizeof(short)); /* Here we are setting the bytespersample AFTER de-compression */ bytespersample = WORD; } else if (wav->formatTag == WAVE_FORMAT_IMA_ADPCM) { if (wBitsPerSample != 4) fail("Can only handle 4-bit IMA ADPCM in wav files"); wExtSize = rlshort(ft); wav->samplesPerBlock = rlshort(ft); wav->bytesPerBlock = (wav->samplesPerBlock + 7)/2 * ft->info.channels; wav->packet = (unsigned char *)malloc(wav->blockAlign); len -= 4; wav->samples[1] = wav->samples[0] = 0; /* Use ft->info.channels after this becuase wChannels is now bad */ while (wChannels-- > 0) wav->samples[wChannels] = (short *)malloc(wav->samplesPerBlock*sizeof(short)); /* Here we are setting the bytespersample AFTER de-compression */ bytespersample = WORD; } else { 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--; } /* Now look for the wave data chunk */ for (;;) { if ( fread(magic, 1, 4, ft->fp) != 4 ) fail("WAVE file has missing data chunk"); len = rllong(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; if (wav->formatTag == 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, substract 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 */ } else if (wav->formatTag == 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 */ } else 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 exteded 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,wNumCoefs); 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. */ if (ft->info.style == 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 = ImaAdpcmNextBlock(ft); else wav->blockSamplesRemaining = MsAdpcmNextBlock(ft); if (wav->blockSamplesRemaining == 0) { /* Don't try to read any more samples */ wav->numSamples = 0; return done; } wav->samplePtr[0] = wav->samples[0]; wav->samplePtr[1] = wav->samples[1]; } switch(ft->info.channels) { /* Copy data into buf */ case 1: /* Mono: Just copy left channel data */ while ((wav->blockSamplesRemaining > 0) && (done < len)) { /* Output is already signed */ *buf++ = LEFT(*(wav->samplePtr[0]++), 16); done++; wav->blockSamplesRemaining--; } break; case 2: /* Stereo: Interleave samples */ while ((wav->blockSamplesRemaining > 0) && (done < len)) { /* Output is already signed */ *buf++ = LEFT(*(wav->samplePtr[0]++),16); /* Left */ *buf++ = LEFT(*(wav->samplePtr[1]++),16); /* Right */ done += 2; wav->blockSamplesRemaining--; } break; default: fail ("Can only handle stereo or mono files"); } } } else /* else not ADPCM style */ { done = rawread(ft, buf, len); /* If software thinks there are more samples but I/O */ /* says otherwise, let the user no 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[0]) free(wav->samples[0]); if (wav->samples[1]) free(wav->samples[1]); } 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 = 1; wav->numSamples = 0; wav->second_header = 0; if (! ft->seekable) warn("Length in output .wav header will wrong since can't seek to fix it"); wavwritehdr(ft); } void wavwritehdr(ft) ft_t ft; { wav_t wav = (wav_t) ft->priv; /* 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) */ switch (ft->info.size) { case BYTE: wBitsPerSample = 8; break; case WORD: wBitsPerSample = 16; break; case DWORD: wBitsPerSample = 32; break; default: wBitsPerSample = 32; break; } switch (ft->info.style) { case UNSIGNED: wFormatTag = WAVE_FORMAT_PCM; if (wBitsPerSample != 8 && !wav->second_header) warn("Warning - writing bad .wav file using unsigned data and %d bits/sample",wBitsPerSample); break; case SIGN2: wFormatTag = WAVE_FORMAT_PCM; if (wBitsPerSample == 8 && !wav->second_header) warn("Warning - writing bad .wav file using signed data and %d bits/sample",wBitsPerSample); break; case ALAW: wFormatTag = WAVE_FORMAT_ALAW; if (wBitsPerSample != 8 && !wav->second_header) warn("Warning - writing bad .wav file using A-law data and %d bits/sample",wBitsPerSample); break; case ULAW: wFormatTag = WAVE_FORMAT_MULAW; if (wBitsPerSample != 8 && !wav->second_header) warn("Warning - writing bad .wav file using U-law data and %d bits/sample",wBitsPerSample); break; case ADPCM: wFormatTag = WAVE_FORMAT_PCM; warn("Can not support writing ADPCM style. Overriding to Signed Words\n"); ft->info.style = SIGN2; wBitsPerSample = 16; /* wFormatTag = WAVE_FORMAT_IMA_ADPCM; wBitsPerSample = 4; if (wBitsPerSample != 4 && !wav->second_header) warn("Warning - writing bad .wav file using IMA ADPCM and %d bits/sample",wBitsPerSample); break; */ } wSamplesPerSecond = ft->info.rate; bytespersample = (wBitsPerSample + 7)/8; wAvgBytesPerSec = ft->info.rate * ft->info.channels * bytespersample; wChannels = ft->info.channels; wBlockAlign = ft->info.channels * bytespersample; if (!wav->second_header) /* use max length value first time */ data_length = 0x7fffffffL - (8+16+12); else /* fixup with real length */ { if (ft->info.style == ADPCM) data_length = wav->numSamples / 2; else data_length = bytespersample * wav->numSamples; } /* figured out header info, so write it */ fputs("RIFF", ft->fp); wllong(ft, data_length + 8+16+12); /* Waveform chunk size: FIXUP(4) */ fputs("WAVE", ft->fp); fputs("fmt ", ft->fp); wllong(ft, (LONG)16); /* fmt chunk size */ wlshort(ft, wFormatTag); wlshort(ft, wChannels); wllong(ft, wSamplesPerSecond); wllong(ft, wAvgBytesPerSec); wlshort(ft, wBlockAlign); wlshort(ft, wBitsPerSample); fputs("data", ft->fp); wllong(ft, data_length); /* data chunk size: FIXUP(40) */ if (!wav->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; wav->numSamples += len; rawwrite(ft, buf, len); } void wavstopwrite(ft) ft_t ft; { /* 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, 0) != 0) fail("Sorry, can't rewind output file to rewrite .wav header."); ((wav_t) ft->priv)->second_header = 1; wavwritehdr(ft); } /* * 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_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 IBM_FORMAT_MULAW: return "IBM U-law format."; case IBM_FORMAT_ALAW: return "IBM A-law"; case IBM_FORMAT_ADPCM: return "IBM ADPCM"; default: return "Unknown"; } }