575 lines
16 KiB
C
575 lines
16 KiB
C
/*
|
|
* wav.c
|
|
*
|
|
* Converts pvf <--> wav.
|
|
* it was written by Karlo Gross kg@orion.ddorf.rhein-ruhr.de
|
|
* by using parts from Rick Richardson and Lance Norskog's
|
|
* wav.c, found in sox-11-gamma. Thank you for some funtions.
|
|
* This is the 1. alpha release from 1997/2/14
|
|
*
|
|
* $Id: wav.c,v 1.6 2000/07/22 09:57:46 marcs Exp $
|
|
*/
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include "pvf.h"
|
|
char *sizes[] =
|
|
{
|
|
"NONSENSE!",
|
|
"bytes",
|
|
"shorts",
|
|
"NONSENSE",
|
|
"longs",
|
|
"32-bit floats",
|
|
"64-bit floats",
|
|
"IEEE floats"
|
|
};
|
|
|
|
/* Private data for .wav file */
|
|
|
|
typedef struct wavstuff
|
|
{
|
|
long samples;
|
|
int second_header; /* non-zero on second header write */
|
|
} *wav_t;
|
|
|
|
/* wave file characteristics */
|
|
|
|
unsigned short wFormatTag; /* data format */
|
|
unsigned short wChannels; /* number of channels */
|
|
unsigned long wSamplesPerSecond; /* samples per second per channel */
|
|
unsigned long wAvgBytesPerSec; /* estimate of bytes per second needed */
|
|
unsigned short wBlockAlign; /* byte alignment of a basic sample block */
|
|
unsigned short wBitsPerSample; /* bits per sample */
|
|
unsigned long data_length; /* length of sound data in bytes */
|
|
unsigned long bytespersample; /* bytes per sample (per channel) */
|
|
|
|
static char *wav_format_str();
|
|
|
|
/* Read short, little-endian: little end first. VAX/386 style. */
|
|
|
|
unsigned short rlshort(ft_t ft)
|
|
{
|
|
unsigned char uc, uc2;
|
|
uc = getc(ft->fp);
|
|
uc2 = getc(ft->fp);
|
|
return (uc2 << 8) | uc;
|
|
}
|
|
|
|
/* Read long, little-endian: little end first. VAX/386 style. */
|
|
|
|
unsigned long rllong(ft_t ft)
|
|
{
|
|
unsigned char uc, uc2, uc3, uc4;
|
|
|
|
/* if (feof(ft->fp))
|
|
fprintf(stderr,readerr); No worky! */
|
|
|
|
uc = getc(ft->fp);
|
|
uc2 = getc(ft->fp);
|
|
uc3 = getc(ft->fp);
|
|
uc4 = getc(ft->fp);
|
|
return ((long)uc4 << 24) | ((long)uc3 << 16) | ((long)uc2 << 8) | (long)uc;
|
|
}
|
|
|
|
/* Write long, little-endian: little end first. VAX/386 style. */
|
|
|
|
int wllong(ft_t ft, unsigned long ul)
|
|
{
|
|
int datum;
|
|
|
|
datum = (ul) & 0xff;
|
|
putc(datum, ft->fp);
|
|
datum = (ul >> 8) & 0xff;
|
|
putc(datum, ft->fp);
|
|
datum = (ul >> 16) & 0xff;
|
|
putc(datum, ft->fp);
|
|
datum = (ul >> 24) & 0xff;
|
|
putc(datum, ft->fp);
|
|
|
|
if (ferror(ft->fp))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Write short, little-endian: little end first. VAX/386 style. */
|
|
|
|
int wlshort(ft_t ft, unsigned short us)
|
|
{
|
|
putc(us, ft->fp);
|
|
putc(us >> 8, ft->fp);
|
|
|
|
if (ferror(ft->fp))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int wavstartread(ft_t ft)
|
|
{
|
|
wav_t wav = (wav_t) ft->priv;
|
|
char magic[4];
|
|
unsigned int len;
|
|
int littlendian = 1;
|
|
char *endptr;
|
|
|
|
ft->info.rate = 0;
|
|
ft->info.size = -1;
|
|
ft->info.style = -1;
|
|
ft->info.channels = -1;
|
|
ft->comment = NULL;
|
|
ft->swap = 0;
|
|
|
|
endptr = (char *) &littlendian;
|
|
if (!*endptr) ft->swap = 1;
|
|
|
|
/* If you need to seek around the input file. */
|
|
if (0 && ! ft->seekable)
|
|
fprintf(stderr, "Sorry, .wav input file must be a file, not a pipe");
|
|
|
|
if ( fread(magic, 1, 4, ft->fp) != 4
|
|
|| strncmp("RIFF", magic, 4))
|
|
{
|
|
fprintf(stderr, "Sorry, not a RIFF file");
|
|
return ERROR;
|
|
}
|
|
|
|
len = rllong(ft);
|
|
|
|
if ( fread(magic, 1, 4, ft->fp) != 4
|
|
|| strncmp("WAVE", magic, 4))
|
|
{
|
|
fprintf(stderr, "Sorry, not a WAVE file");
|
|
return ERROR;
|
|
}
|
|
|
|
/* Now look for the format chunk */
|
|
for (;;)
|
|
{
|
|
if ( fread(magic, 1, 4, ft->fp) != 4 )
|
|
{
|
|
fprintf(stderr, "Sorry, missing fmt spec");
|
|
return ERROR;
|
|
}
|
|
len = rllong(ft);
|
|
if (strncmp("fmt ", magic, 4) == 0)
|
|
break; /* Found the format chunk */
|
|
while (len > 0 && !feof(ft->fp)) /* skip to next chunk */
|
|
{
|
|
getc(ft->fp);
|
|
len--;
|
|
}
|
|
}
|
|
|
|
if ( len < 16 )
|
|
fprintf(stderr, "Sorry, fmt chunk is too short");
|
|
|
|
wFormatTag = rlshort(ft);
|
|
switch (wFormatTag)
|
|
{
|
|
case WAVE_FORMAT_UNKNOWN:
|
|
fprintf(stderr, "Sorry, this WAV file is in Microsoft Official Unknown format.");
|
|
return ERROR;
|
|
case WAVE_FORMAT_PCM: /* this one, at least, I can handle */
|
|
break;
|
|
case WAVE_FORMAT_ADPCM:
|
|
fprintf(stderr, "Sorry, this WAV file is in Microsoft ADPCM format.");
|
|
return ERROR;
|
|
case WAVE_FORMAT_ALAW: /* Think I can handle this */
|
|
ft->info.style = ALAW;
|
|
break;
|
|
case WAVE_FORMAT_MULAW: /* Think I can handle this */
|
|
ft->info.style = ULAW;
|
|
break;
|
|
case WAVE_FORMAT_OKI_ADPCM:
|
|
fprintf(stderr, "Sorry, this WAV file is in OKI ADPCM format.");
|
|
return ERROR;
|
|
case WAVE_FORMAT_DIGISTD:
|
|
fprintf(stderr, "Sorry, this WAV file is in Digistd format.");
|
|
return ERROR;
|
|
case WAVE_FORMAT_DIGIFIX:
|
|
fprintf(stderr, "Sorry, this WAV file is in Digifix format.");
|
|
return ERROR;
|
|
case IBM_FORMAT_MULAW:
|
|
fprintf(stderr, "Sorry, this WAV file is in IBM U-law format.");
|
|
return ERROR;
|
|
case IBM_FORMAT_ALAW:
|
|
fprintf(stderr, "Sorry, this WAV file is in IBM A-law format.");
|
|
return ERROR;
|
|
case IBM_FORMAT_ADPCM:
|
|
fprintf(stderr, "Sorry, this WAV file is in IBM ADPCM format.");
|
|
return ERROR;
|
|
default:
|
|
fprintf(stderr, "Sorry, don't understand format");
|
|
return ERROR;
|
|
}
|
|
|
|
wChannels = rlshort(ft);
|
|
ft->info.channels = wChannels;
|
|
wSamplesPerSecond = rllong(ft);
|
|
ft->info.rate = wSamplesPerSecond;
|
|
wAvgBytesPerSec = rllong(ft); /* Average bytes/second */
|
|
wBlockAlign = rlshort(ft); /* Block align */
|
|
wBitsPerSample = rlshort(ft); /* bits per sample per channel */
|
|
bytespersample = (wBitsPerSample + 7)/8;
|
|
switch (bytespersample)
|
|
{
|
|
case 1:
|
|
ft->info.size = BYTE;
|
|
break;
|
|
case 2:
|
|
ft->info.size = WORD;
|
|
break;
|
|
case 4:
|
|
ft->info.size = LONG;
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Sorry, don't understand .wav size");
|
|
return ERROR;
|
|
}
|
|
len -= 16;
|
|
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 )
|
|
{
|
|
fprintf(stderr, "Sorry, missing data chunk");
|
|
return ERROR;
|
|
}
|
|
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;
|
|
wav->samples = data_length/ft->info.size; /* total samples */
|
|
|
|
fprintf(stderr, "Reading Wave file: %s format, %d channel%s, %ld samp/sec\n",
|
|
wav_format_str(wFormatTag), wChannels,
|
|
wChannels == 1 ? "" : "s", wSamplesPerSecond);
|
|
|
|
fprintf(stderr, "%ld byte/sec, %d block align, %d bits/samp, %lu data bytes\n",
|
|
wAvgBytesPerSec, wBlockAlign, wBitsPerSample, data_length);
|
|
return OK;
|
|
}
|
|
|
|
int wavwritehdr(ft_t ft,long data_size)
|
|
{
|
|
|
|
switch (ft->info.size)
|
|
{
|
|
case BYTE:
|
|
wBitsPerSample = 8;
|
|
if (ft->info.style == -1 || ft->info.style == UNSIGNED)
|
|
ft->info.style = UNSIGNED;
|
|
else if (ft->info.style != ALAW && ft->info.style != ULAW)
|
|
fprintf(stderr, "User options overiding style written to .wav header");
|
|
break;
|
|
case WORD:
|
|
wBitsPerSample = 16;
|
|
if (ft->info.style == -1 || ft->info.style == SIGN2)
|
|
ft->info.style = SIGN2;
|
|
break;
|
|
case LONG:
|
|
wBitsPerSample = 32;
|
|
if (ft->info.style == -1 || ft->info.style == SIGN2)
|
|
ft->info.style = SIGN2;
|
|
break;
|
|
default:
|
|
wBitsPerSample = 32;
|
|
if (ft->info.style == -1)
|
|
ft->info.style = SIGN2;
|
|
break;
|
|
}
|
|
|
|
switch (ft->info.style)
|
|
{
|
|
case UNSIGNED:
|
|
wFormatTag = WAVE_FORMAT_PCM;
|
|
if (wBitsPerSample != 8 )
|
|
fprintf(stderr, "Warning - writing bad .wav file using unsigned data and %d bits/sample",wBitsPerSample);
|
|
break;
|
|
case SIGN2:
|
|
wFormatTag = WAVE_FORMAT_PCM;
|
|
if (wBitsPerSample == 8 )
|
|
fprintf(stderr, "Warning - writing bad .wav file using signed data and %d bits/sample",wBitsPerSample);
|
|
break;
|
|
case ALAW:
|
|
wFormatTag = WAVE_FORMAT_ALAW;
|
|
if (wBitsPerSample != 8 )
|
|
fprintf(stderr, "Warning - writing bad .wav file using A-law data and %d bits/sample",wBitsPerSample);
|
|
break;
|
|
case ULAW:
|
|
wFormatTag = WAVE_FORMAT_MULAW;
|
|
if (wBitsPerSample != 8 )
|
|
fprintf(stderr, "Warning - writing bad .wav file using U-law data 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;
|
|
data_length = data_size;
|
|
|
|
/* figured out header info, so write it */
|
|
fputs("RIFF", ft->fp);
|
|
wllong(ft, data_length + 8+16+12+1); /* Waveform chunk size: FIXUP(4) */
|
|
/* die 1 ist von mir karlo */
|
|
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) */
|
|
|
|
|
|
fprintf(stderr, "Writing Wave file: %s format, %d channel%s, %ld samp/sec",
|
|
wav_format_str(wFormatTag), wChannels,
|
|
wChannels == 1 ? "" : "s", wSamplesPerSecond);
|
|
fprintf(stderr, " %ld byte/sec, %d block align, %d bits/samp\n",
|
|
wAvgBytesPerSec, wBlockAlign, wBitsPerSample);
|
|
|
|
return OK;
|
|
}
|
|
|
|
int wavstartwrite(ft_t ft,long data_size)
|
|
{
|
|
wav_t wav = (wav_t) ft->priv;
|
|
int littlendian = 1;
|
|
char *endptr;
|
|
|
|
endptr = (char *) &littlendian;
|
|
if (!*endptr) ft->swap = 1;
|
|
|
|
wav->samples = 0;
|
|
wav->second_header = 0;
|
|
|
|
if (wavwritehdr(ft,data_size) != OK)
|
|
return ERROR;
|
|
return OK;
|
|
}
|
|
|
|
/*
|
|
* Return a string corresponding to the wave format type.
|
|
*/
|
|
|
|
static char * wav_format_str(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_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";
|
|
}
|
|
|
|
}
|
|
|
|
int pvftowav (FILE *fd_in, FILE *fd_out, pvf_header *header_in, int wav_bits)
|
|
{
|
|
int bytespersample;
|
|
long data_size = 0;
|
|
int buffer_size = 0;
|
|
int *buffer = NULL;
|
|
int data,*ptr;
|
|
int voice_samples = 0;
|
|
struct soundstream s;
|
|
|
|
bytespersample = (wav_bits + 7) / 8;
|
|
|
|
switch (bytespersample)
|
|
{
|
|
case 1:
|
|
s.info.size = BYTE;
|
|
break;
|
|
case 2:
|
|
s.info.size = WORD;
|
|
break;
|
|
case 4:
|
|
s.info.size = LONG;
|
|
break;
|
|
default:
|
|
fprintf(stderr, "sorry, don't understand .wav size");
|
|
return(ERROR);
|
|
}
|
|
|
|
s.info.rate = header_in->speed;
|
|
s.info.style = -1;
|
|
s.info.channels = header_in->channels;
|
|
s.comment = NULL;
|
|
s.swap = 0;
|
|
s.filetype = (char *) 0;
|
|
s.fp = fd_out;
|
|
s.seekable = 0;
|
|
|
|
while(!feof(fd_in))
|
|
{
|
|
data = header_in->read_pvf_data(fd_in);
|
|
|
|
if (voice_samples >= buffer_size)
|
|
{
|
|
buffer_size += BLOCK_SIZE;
|
|
buffer = (int *) realloc(buffer, buffer_size * sizeof(int));
|
|
|
|
if (buffer == NULL)
|
|
{
|
|
fprintf(stderr, "%s: out of memory in pvftowav", program_name);
|
|
free(buffer);
|
|
exit(99);
|
|
};
|
|
|
|
}
|
|
|
|
buffer[voice_samples++] = data;
|
|
data_size++;
|
|
}
|
|
|
|
if (wavstartwrite(&s,data_size) != OK)
|
|
{
|
|
free(buffer);
|
|
return ERROR;
|
|
}
|
|
|
|
ptr = buffer;
|
|
|
|
switch (s.info.size)
|
|
{
|
|
case BYTE:
|
|
|
|
while (data_size--)
|
|
{
|
|
*ptr >>=16;
|
|
|
|
if (*ptr > 0x7f)
|
|
*ptr = 0x7f;
|
|
|
|
if (*ptr < -0x80)
|
|
*ptr = -0x80;
|
|
|
|
putc(*ptr+0x80,fd_out);
|
|
ptr++;
|
|
};
|
|
|
|
break;
|
|
case WORD:
|
|
|
|
while (data_size--)
|
|
{
|
|
*ptr >>=8;
|
|
|
|
if (*ptr > 0x7fff)
|
|
*ptr = 0x7fff;
|
|
|
|
if (*ptr < -0x8000)
|
|
*ptr = -0x8000;
|
|
|
|
putc(*ptr, fd_out);
|
|
putc(*ptr++ >> 8, fd_out);
|
|
};
|
|
|
|
break;
|
|
case LONG:
|
|
|
|
while (data_size--)
|
|
{
|
|
putc(*ptr, fd_out);
|
|
putc(*ptr >> 8, fd_out);
|
|
putc(*ptr >> 16, fd_out);
|
|
putc(*ptr++ >> 24, fd_out);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
free(buffer);
|
|
return(OK);
|
|
}
|
|
|
|
int wavtopvf (FILE *fd_in, FILE *fd_out, pvf_header *header_out)
|
|
{
|
|
struct soundstream s;
|
|
int d;
|
|
|
|
s.fp = fd_in;
|
|
|
|
if (wavstartread(&s) != OK)
|
|
return ERROR;
|
|
|
|
header_out->channels = (int) wChannels;
|
|
header_out->speed = (int) wSamplesPerSecond;
|
|
write_pvf_header(fd_out, header_out);
|
|
|
|
while (data_length--)
|
|
{
|
|
|
|
if (feof(fd_in))
|
|
return(OK);
|
|
|
|
switch (wBitsPerSample)
|
|
{
|
|
case 8:
|
|
d = getc(fd_in) - 0x80;
|
|
d <<= 16;
|
|
break;
|
|
case 16:
|
|
d = getc(fd_in) & 0xFF;
|
|
d += (getc(fd_in) << 8);
|
|
|
|
if (d & 0x8000)
|
|
d |= 0xFFFF0000;
|
|
|
|
d <<= 8;
|
|
break;
|
|
default:
|
|
fprintf(stderr,
|
|
"%s: unsupported number of bits per sample (%d)\n",
|
|
program_name, wBitsPerSample);
|
|
return(ERROR);
|
|
};
|
|
|
|
header_out->write_pvf_data(fd_out, d);
|
|
};
|
|
|
|
return(OK);
|
|
}
|
|
|