/*
 * April 15, 1992
 * Copyright 1992 Rick Richardson
 * Copyright 1991 Lance Norskog And Sundry Contributors
 * 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.
 */

/*
 * Windows 3.0 .wav format driver
 */

/*
 * Fixed by various contributors:
 * 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 overide 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 "st.h"
#include "wav.h"

/* Private data for .wav file */
typedef struct wavstuff {
	long	samples;
	int	second_header;	/* non-zero on second header write */
} *wav_t;

static char *wav_format_str(); 

/*
 * Do anything required before you start reading samples.
 * Read file header. 
 *	Find out sampling rate, 
 *	size and style of samples, 
 *	mono/stereo/quad.
 */
int wavstartread(ft_t ft) 
{
	wav_t	wav = (wav_t) ft->priv;
	char	magic[4];
        unsigned len;  
	int	littlendian = 1;
	char	*endptr;
	char	c;

        /* 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) */

	endptr = (char *) &littlendian;
	if (!*endptr) ft->swap = 1;

	/* If you need to seek around the input file. */
	if (0 && ! ft->seekable)
		return 0;
//		fail("Sorry, .wav input file must be a file, not a pipe");

	if (   fread(magic, 1, 4, ft->fp) != 4
	    || strncmp("RIFF", magic, 4))
		return 0;
//		fail("Sorry, not a RIFF file");

	len = rllong(ft);

	if (   fread(magic, 1, 4, ft->fp) != 4
	    || strncmp("WAVE", magic, 4))
		return 0;
//		fail("Sorry, not a WAVE file");

	/* Now look for the format chunk */
	for (;;)
	{
		if ( fread(magic, 1, 4, ft->fp) != 4 )
			return 0;
//			fail("Sorry, missing fmt spec");
		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 )
		return 0;
//		fail("Sorry, fmt chunk is too short");

	wFormatTag = rlshort(ft);
	switch (wFormatTag)
	{
		case WAVE_FORMAT_UNKNOWN:
			return 0;
//		fail("Sorry, this WAV file is in Microsoft Official Unknown format.");
		case WAVE_FORMAT_PCM: 	/* this one, at least, I can handle */
//			if (ft->info.style != -1 && ft->info.style != UNSIGNED && ft->info.style != SIGN2)
//				warn("User options overiding style read in .wav header");
			break;
		case WAVE_FORMAT_ADPCM:
			return 0;
//		fail("Sorry, this WAV file is in Microsoft ADPCM format.");
		case WAVE_FORMAT_ALAW:	/* Think I can handle this */
			if (ft->info.style == -1 || ft->info.style == ALAW)
				ft->info.style = ALAW;
//			else
//				warn("User options overiding style read in .wav header");
			break;
		case WAVE_FORMAT_MULAW:	/* Think I can handle this */
			if (ft->info.style == -1 || ft->info.style == ULAW)
				ft->info.style = ULAW;
//			else
//				warn("User options overiding style read in .wav header");
			break;
		case WAVE_FORMAT_OKI_ADPCM:
			return 0;
//		fail("Sorry, this WAV file is in OKI ADPCM format.");
		case WAVE_FORMAT_DIGISTD:
			return 0;
//		fail("Sorry, this WAV file is in Digistd format.");
		case WAVE_FORMAT_DIGIFIX:
			return 0;
//		fail("Sorry, this WAV file is in Digifix format.");
		case IBM_FORMAT_MULAW:
			return 0;
//		fail("Sorry, this WAV file is in IBM U-law format.");
		case IBM_FORMAT_ALAW:
			return 0;
//		fail("Sorry, this WAV file is in IBM A-law format.");
		case IBM_FORMAT_ADPCM:
			return 0;
//		fail("Sorry, this WAV file is in IBM ADPCM format.");
	default:	
			return 0;
//		fail("Sorry, don't understand format");
	}
	wChannels = rlshort(ft);
	/* User options take precedence */
	if (ft->info.channels == -1 || ft->info.channels == wChannels)
		ft->info.channels = wChannels;
//	else
//		warn("User options overiding channels read in .wav header");
	wSamplesPerSecond = rllong(ft);
	if (ft->info.rate == 0 || ft->info.rate == wSamplesPerSecond)
		ft->info.rate = wSamplesPerSecond;
//	else
//		warn("User options overiding rate read in .wav header");
	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:
			/* User options take precedence */
			if (ft->info.size == -1 || ft->info.size == BYTE)
				ft->info.size = BYTE;
//			else
//				warn("User options overiding size read in .wav header");
			if (ft->info.style == -1 || ft->info.style == UNSIGNED)
				ft->info.style = UNSIGNED;
//			else if (ft->info.style != ALAW && ft->info.style != ULAW) 
//				warn("User options overiding style read in .wav header");
			break;
		case 2:
			if (ft->info.size == -1 || ft->info.size == WORD)
				ft->info.size = WORD;
//			else
//				warn("User options overiding size read in .wav header");
			if (ft->info.style == -1 || ft->info.style == SIGN2)
				ft->info.style = SIGN2;
//			else
//				warn("User options overiding style read in .wav header");
			break;
		case 4:
			if (ft->info.size == -1 || ft->info.size == LONG)
				ft->info.size = LONG;
//			else
//				warn("User options overiding size read in .wav header");
			if (ft->info.style == -1 || ft->info.style == SIGN2)
				ft->info.style = SIGN2;
//			else
//				warn("User options overiding style read in .wav header");
			break;
		default:
			return 0;
//			fail("Sorry, don't understand .wav size");
	}
	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 )
			return 0;
//			fail("Sorry, 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;
	wav->samples = data_length/ft->info.size;	/* total samples */

//	report("Reading 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, %u data bytes\n",
//                wAvgBytesPerSec, wBlockAlign, wBitsPerSample, data_length);
	
	return 1;
}

/*
 * Read up to len samples from file.
 * Convert to signed longs.
 * Place in buf[].
 * Return number of samples read.
 */

int wavread(ft_t ft, long *buf, int len) 
{
	wav_t	wav = (wav_t) ft->priv;
	int	done;

	if (len > wav->samples) len = wav->samples;
	if (len == 0)
		return 0;
	done = rawread(ft, buf, len);
//	if (done == 0)
//		warn("Premature EOF on .wav input file");
	wav->samples -= done;
	return done;
}

/*
 * Do anything required when you stop reading samples.  
 * Don't close input file! 
 */
wavstopread(ft_t ft) 
{
}

/*
 * 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";
	}
}
