/**********************************************************************
convert 16 bit .wav from stdin to 16 bit .au on stdout

$Id: wav2au.c,v 1.5 2003/01/19 23:17:09 purnhage Exp $

(c) Heiko Purnhagen 2000-2003
e-mail: purnhage@tnt.uni-hannover.de
WWW:    http://www.tnt.uni-hannover.de/~purnhage/

This program is available under the terms of the GNU General Public
License. See file COPYING for further details. All usual disclaimers
apply!

Authors:
NM    Nikolaus Meine, Uni Hannover <meine@tnt.uni-hannover.de>
HP    Heiko Purnhagen, Uni Hannover <purnhage@tnt.uni-hannover.de>

06-Sep-2000 NM      written by Nikolaus Meine
20-Aug-2001 HP      added this header, included in autools
21-Aug-2001 NM      added multiple input file support
22-Sep-2001 HP      fixed "could not open <>" bug
**********************************************************************/


#include <stdio.h>
#include <stdlib.h>

#define uchar	unsigned char
#define ushort	unsigned short
#define uint	unsigned int
#define ulong	unsigned long

#define buffersize	8192

typedef struct {
	int	samrate;
	int	channels;
} Tconf;

static void be_putuint32(ulong x)
{
	putc( (x>>24) & 255, stdout );
	putc( (x>>16) & 255, stdout );
	putc( (x>> 8) & 255, stdout );
	putc(  x      & 255, stdout );
}

static ulong le_getuint32(FILE *f)
{
	register ulong	r,h;

	r=getc(f);
	h=getc(f);	r|=h<< 8;
	h=getc(f);	r|=h<<16;
	h=getc(f);	r|=h<<24;

	return r;
}

static ulong le_getuint16(FILE *f)
{
	register ulong	r,h;

	r=getc(f);
	h=getc(f);	r|=h<< 8;

	return r;
}


static long convert_header(FILE *f,Tconf *c)
{
	register ulong	samrate,channels;

	if ( le_getuint32(f)!=0x46464952UL ) return -1;	/* "RIFF" */

	le_getuint32(f);				/* filesize except "RIFF" and the size-field itself */

	if ( le_getuint32(f)!=0x45564157UL ) return -1;	/* "WAVE" */
	if ( le_getuint32(f)!=0x20746D66UL ) return -1;	/* "fmt " */
	if ( le_getuint32(f)!=        16UL ) return -1;	/* length of fmt data == 16 ! */
	if ( le_getuint16(f)!=         1UL ) return -1;	/* 1 -> PCM */

	channels = le_getuint16(f);			/* 1 or 2 */
	samrate  = le_getuint32(f);			/* sample rate */

	le_getuint32(f);				/* bytes/second - redundant */
	le_getuint16(f);				/* block align - redundant */

	if ( le_getuint16(f)!=16UL ) return -1;		/* sample size; must be 16 */

	if ( !c->channels )
	{
		c->samrate =samrate;
		c->channels=channels;

		be_putuint32( 0x2e736e64UL);	/* ".snd" */
		be_putuint32(         28UL);	/* header size */
		be_putuint32( 0xFFFFFFFFUL);	/* unlimited data size */
		be_putuint32(          3UL);	/* format code: 3 -> PCM 16 bit	*/
		be_putuint32(    samrate  );
		be_putuint32(    channels );
		be_putuint32(          0UL);	/* optional data	*/
	}
	else
	{
		if ( (c->samrate >=0) && ((int)samrate !=c->samrate)  )
		{
			fprintf(stderr,"Warning: files have different sample rates");
			c->samrate=-1;
		}
		if ( (c->channels>=0) && ((int)channels!=c->channels) )
		{
			fprintf(stderr,"Warning: files have different number of channels");
			c->channels=-1;
		}
	}

	return 0;
}


static ulong find_data_chunk(FILE *f,uchar *buf)
{
	register ulong	type,size,i;

	for (;;)
	{
		type=le_getuint32(f);
		size=le_getuint32(f);

		if ( type==0x61746164UL ) return size;	/* "data" -> data chunk found	*/

		while ( size )				/* skip other chunks	*/
		{
			i=size;
			if ( i>buffersize ) i=buffersize;
			if ( fread(buf,1,i,f)!=i ) return (ulong) (-1);
			size-=i;
		}
	}

	return (ulong) (-1);
}

static void byteswap(register uchar *p,register ulong n)
{
	register ulong	a,b;

	do
	{
		a=p[0]; b=p[1];
		p[0]=(unsigned char)b; p[1]=(unsigned char)a;
		p+=2;
		n-=2;
	}
	while (n);
}

int main(int argc,char **argv)
{
	uchar		buffer[buffersize];
	Tconf		conf;
	register FILE	*f;
	register ulong	dsize;
	register long	i,j,k;

	if ( argc==2 )
	{
		i=argv[1][0]=='-';
		if ( i ) i = i && argv[1][1]=='h';
		if ( i ) i = i && argv[1][2]==0;
		if ( i )
		{
			fprintf(stderr,"usage: %s [wav-file(s)]\n",argv[0]);
			exit(0);
		}
	}

	conf.samrate=0;
	conf.channels=0;

	k=0;
	if ( argc>1 ) k=1;

	for (; k<argc; k++)
	{
		i=(k==0);
		if ( !i )
		{
			i=argv[k][0]=='-';
			if ( i ) i = i && argv[k][1]==0;
		}

		if ( i ) f=stdin; else
		{
			f=fopen(argv[k],"r");
			if ( f==NULL )
			{
				fprintf(stderr,"could not open <%s>\n",argv[k]);
				continue;
			}
		}
			
		if ( convert_header(f,&conf)<0 )
		{
			fprintf(stderr,"invalid or unsupported wave file\n");
			exit(1);
		}

		dsize=find_data_chunk(f,buffer);

		if ( !(~dsize) || (!dsize) )
		{
			fprintf(stderr,"no data chunk found\n");
			exit(1);
		}

		do
		{
			if ( dsize&1 )
			{
				fprintf(stderr,"odd data chunk size: %li\n",dsize);
				exit(1);
			}
			do
			{
				i=dsize;
				if ( i>buffersize ) i=buffersize;
				j=fread(buffer,1,i,f);
				if ( j<i )
				{
					buffer[j]=0;
					j+=(j&1);
					byteswap(buffer,j);
					if ( j>0 ) fwrite(buffer,1,j,stdout);
					fprintf(stderr,"unexpected end of file\n");
					exit(0);
				}
				byteswap(buffer,i);
				fwrite(buffer,1,j,stdout);
				dsize-=i;
			}
			while ( dsize );

			dsize=find_data_chunk(f,buffer);

		}
		while ( (~dsize) && dsize );

		if ( f!=stdin ) fclose(f);
	}
	return 0;
}
