/*
 * OmniPlay, v0.98
 * by David Champion
 *
 * formats handlers
 * 29 Aug 1992
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <dos/dos.h>
#include <proto/powerpacker.h>
#include <libraries/ppbase.h>
#include <stdio.h>
#include "forms.h"
#include "oplay.h"

/* These make endianism conversions inline */
#define LitEnd2BigEnd_uword(b)	( (UWORD)(b)<<8 | (UWORD)(b)>>8 )
#define LitEnd2BigEnd_ulong(l)	( (ULONG)(l)<<24 | ((ULONG)(l)<<8)&0x00ff0000\
	| ((ULONG)(l)>>8)&0x0000ff00 | (ULONG)(l)>>24 )

/* guido() types */
#define TYP_UNKN	0
#define TYP_TEXT	1<<0
#define TYP_ULAW	1<<1
#define TYP_SIGN	1<<2
#define TYP_UNSN	1<<3

/* prototypes */
ULONG	readfile(char *);
void	propinfo(char *, char *, ULONG);
UWORD	FromIEEE(UBYTE *);
//void	LitEnd2BigEnd_uword(UBYTE *);
//void	LitEnd2BigEnd_ulong(UBYTE *);
ULONG	readwavprop(char *, struct FileHandle *);
BOOL	guido(char *);
int	ulaw2linear(unsigned char);
void	mymaketable(signed char *, int);
int	mygetscale(char *);
ULONG	Read8SVX(struct FileHandle *);
ULONG	ReadAIFF(struct FileHandle *);
ULONG	ReadRIFF(struct FileHandle *);
ULONG	ReadVOC (struct FileHandle *);
ULONG	Read_SND(struct FileHandle *);
ULONG	ReadPP20(struct FileHandle *);
ULONG	ReadRaw(void);
ULONG	_ReadRaw(void);

/* global externs */
extern	struct FileHandle	*fp;
extern	char	utype;
extern	UWORD	uvol;
extern	UWORD	urate;
extern	UWORD	vol;
extern	UWORD	rate;
extern	char	*myname;
extern	char	cvmode;
extern	BOOL	showprops;
extern	BOOL	genulawtab;
extern	int	max;
extern	signed char	logs[];
extern	ulaw_tab[];
extern struct Library	*PPBase;

/* global private */
BOOL	ppuse;
char	prop[256];
struct ChunkHeader	ck;
char	*fn;
char	pptmp[L_tmpnam];

ULONG readfile(char *ifn)
{
ULONG	len;

	if ( !ppuse ) fn = ifn;
	tmpnam(pptmp);
	ppuse = 0;

	if ( !(fp=(struct FileHandle *)Open(ifn, MODE_OLDFILE)) ) {
		info("Can't open file", ifn);
		return(0L);
	}

	switch ( utype ) {
	case	RAWU:
		cvmode = CV_FLIP;
		len = _ReadRaw();
		break;
	case	RAWS:
		cvmode = CV_NONE;
		len = _ReadRaw();
		break;
	case	RAWL:
		cvmode = CV_ULAW;
		len = _ReadRaw();
		break;
	default:
		Read(fp, &ck, sizeof(struct ChunkHeader));
		switch ( ck.ckID ) {
		case	ID_FORM:
			Read(fp, &ck, 4);	/* just 4, mind you! */
			if ( ck.ckID == ID_8SVX ) {
				len = Read8SVX(fp);
			}
			else if ( ck.ckID == ID_AIFF ) {
				len = ReadAIFF(fp);
			}
			else {
				info("Unrecognized IFF FORM", ifn);
				Close(fp);
				return(0L);
			}
			break;
		case	ID_RIFF:
			len = ReadRIFF(fp);
			break;
		case	ID_VOCH:
			len = ReadVOC(fp);
			break;
		case	ID__SND:
			len = Read_SND(fp);
			break;
		case	ID_PP20:
			len = ReadPP20(fp);
			break;
		default:
			len = ReadRaw();
			break;
		}
		break;
	}

	return(len);
}

void propinfo(char *tag, char *prop, ULONG size)
{
char	space[256];

	if ( !showprops ) return;
	strncpy(space, prop, size);
	space[size] = '\0';
	printf(COL3"%s"COL1": %s\n", tag, space);
	return;
}

/*
 * The next function, FromIEEE(), is a major condensation of Malcolm Slaney's
 * and Ken Turkowski's (Apple Computer) ConvertFromIeeeExtended().  I opt-
 * imized it to ignore all precision we'll never need, and to avoid floats.
 * ConvertFromIeeeExtended() is available from, among other places, Guido
 * van Rossum's aiff handler in the SOX conversion package.
 */

UWORD FromIEEE(UBYTE *bytes)
{
	signed int	exp;
	UWORD		hiMant;
	UWORD		ret;

	exp = bytes[1];
	hiMant = (ULONG)(bytes[2] << 8) | (ULONG)(bytes[3]);

	if (exp == 0 && hiMant == 0 ) ret = 0;
	else {
		exp = 1 << (14-exp);
		ret = hiMant / exp;
	}
	return ret;
}

//void LitEnd2BigEnd_uword(UBYTE operand[2])
//{
//UBYTE	i;
//
//	i = operand[0];
//	operand[0] = operand[1];
//	operand[1] = i;
//	return;
//}

//void LitEnd2BigEnd_ulong(UBYTE operand[4])
//{
//UBYTE	i;
//
//	i = operand[0];
//	operand[0] = operand[3];
//	operand[3] = i;
//	i = operand[1];
//	operand[1] = operand[2];
//	operand[2] = i;
//	return;
//}

ULONG readwavprop(char prop[], struct FileHandle *mfp)
{
int c, i=0;

	while ( i != 256 && (c=FGetC(mfp)) != '\0' ) prop[i++] = c;
	if ( i == 256 ) while( FGetC(mfp) != '\0' );
	prop[i] = '\0';
	return((ULONG)--i);
}

/*
 * guido() is derived from a function posted to Usenet several months ago by
 * Guido van Rossum.  Its purpose is to determine what sort of sound a given
 * file is; possible results are text (not sound), unknown, signed, unsigned,
 * and ulaw.  I've modified this routine to use Amiga ROM functions to read
 * from an Amiga filehandle, and to use ints instead of floats to analyze the
 * data (this is so that the code will compile on the several compilers that
 * don't implement float support).  Here's the Usenet header and intro from
 * Guido's post:
 */

/*
Path: hydra!news.funet.fi!fuug!mcsun!sun4nl!cwi.nl!guido
From: guido@cwi.nl (Guido van Rossum)
Newsgroups: alt.binaries.sounds.d,alt.sources
Subject: Program to classify headerless sound files
Message-ID: <5438@charon.cwi.nl>
Date: 8 Feb 92 20:53:54 GMT
Sender: news@cwi.nl
Followup-To: alt.binaries.sounds.d
Lines: 124
Xref: hydra alt.binaries.sounds.d:477 alt.sources:4616
*/

/* whatsound -- What type of sound file is that?

   This C program guesses the type of a *headerless* sound file.
   It can recognize the three types of files posted most often to
   alt.binaries.sounds.misc with more than 95% accuracy (probably
   much better).  These formats are: unsigned bytes, where zero
   amplitude corresponds to a value of 0200; signed bytes, where
   zero amplitude corresponds to 0; and U-LAW, which is sort of a
   logarithmic encoding.

   The trick is the following.   First, note that all three formats
   use one byte per sample (this obviously helps a LOT).  So we can
   hope to make good use of a histogram of the sample values.

   For the linear samples, we assume that most of the sample values
   are closer to zero than to the maximum amplitude.  Although the
   reverse is true for a sine wave recorded at maximum amplitude,
   most sound clips contain sound sampled at less than the maximum
   amplitude, silences, etc.  A quick survey of a number of available
   sound files from various sources told me that at least 3/4th of
   all sampled bytes are closer to zero.  For signed bytes, this
   means that 3 out of 4 bytes fall in the range -64...63; for
   unsigned bytes 3/4 are in the range 64...191.

   The logarithmic nature of U-LAW encoding means that the values
   are much more evenly spread; in practice the ratio between the
   numbers of large values and small values is between 0.5 and 2.0.

   The program can thus be extremely simple.  It puts each byte of
   input in one of four bins: 0-63, 64-127, 128-191, 192-255, and
   at the end of the input checks whether there is a significant
   difference between some bins.  If no bytes fall in the range
   128-255 it assumes the file is actually a text file.  In this
   case, or if it can't draw a firm conclusion, it writes an error
   message to stderr and exits with nonozero status; otherwise it
   reports its conclusion in the form of an argument list for SOX:
   "-t raw -b -s filename" means a file with signed bytes;
   -u instead of -s means unsigned bytes; -U means U-LAW.

   Note that it can't guess the sampling rate.  Also note that it
   reads the entire file, to avoid being fooled by long initial
   silences: the first blocks of a U-LAW file beginning with
   a long silence resemble a file of signed bytes.

   A totally *different* program would know about the various sound
   file formats that can be recognized by a magic word in their header,
   but this is more a job for the file(1) program (on SYSV adding
   some lines to /etc/magic would be all that's needed).

   I have some thoughts on applying fft to guess the sampling rate,
   assuming it is one from a small set of rages (8, 11, 16, 22 kHz),
   but I'm not so sure that's doable -- even by *listening* to a
   sample I sometimes am not sure unless I happen to know the
   original (I'm so glad I own those HHGG tapes :-).
*/

BOOL guido(char *mfn)
{
FILE	*gfp;
ULONG	bin[4];
int	i, c;
ULONG	len;

	max = 0;

	/* init all bins */
	for (i = 0; i < 4; i++) bin[i] = 0;

	gfp = fopen(mfn, "rb");

	/* put samples into a bin. */
	if ( genulawtab ) {
		while ( (c = fgetc(gfp)) != EOF ) {
			bin[c/64]++;
			max = MAX(max, ABS(ulaw_tab[c]));
		}
	}
	else while ( (c = fgetc(gfp)) != EOF ) bin[c/64]++;

	fclose(gfp);

	/* analyze */
	if (bin[2] == 0 && bin[3] == 0) return(0);	/* text */

	i = ((bin[0] + bin[3]) * 6) / (bin[1] + bin[2]);
	if (i >= 18)			cvmode = CV_NONE;
	else if (i <= 2)		cvmode = CV_FLIP;
	else if (i >= 3 && i <= 12)	cvmode = CV_ULAW;
	else 				return(0);	/* unknown */

	return(1);
}

/*--------------------------------------------------------------------------*/
/* The following routine was extracted from posting by Brian Foley.         */
/* Brian Foley		email: bfoley@greatlakes.Central.Sun.COM            */
/* Systems Engineer	smail:	1000 Town Center                            */
/* Sun Microsystems		Suite 1700                                  */
/* GreatLakes Region		Southfield, MI 48075   (313) 352-7070       */
/*--------------------------------------------------------------------------*/

//int ulaw2linear(unsigned char ulawbyte)
//{
//	static int exp_lut[8] = { 0, 132, 396, 924, 1980, 4092, 8316, 16764 };
//	       int sign, exponent, mantissa, sample;
//
//	ulawbyte	= ~ulawbyte;
//	sign		=  ulawbyte & 0x80;
//	exponent	= (ulawbyte >> 4) & 0x07;
//	mantissa	=  ulawbyte & 0x0F;
//	sample		= exp_lut[exponent] + (mantissa << (exponent + 3));
//	if ( sign ) sample = -sample;
//	return sample;
//}

int mygetscale(char *mfn)
{
int c;
FILE *gsfp;

	max = 0;

	if ( genulawtab ) {
		gsfp = fopen(mfn, "rb");
		fseek(gsfp, 28, SEEK_SET);
		while( (c = fgetc(gsfp)) != EOF) max = MAX(ABS(ulaw_tab[c]), max);
		fclose(gsfp);
	}
	else max = 32124;

	return max;
}

void mymaketable(signed char *logs, int maximum)
{
	register int	i, c;
	int 		d;

	for ( i = 0; i < 256; i++ ) {
		c = ( ulaw_tab[i] * ulaw_tab[0] ) / maximum;
		d = ABS(c) & 0xFF;
		if ( d > 0x7F ) {
			if ( c > 0 ) {
				logs[i] = (signed char) ( c / 256 + 1 );
			}
			else {
				logs[i] = (signed char) ( c / 256 - 1 );
			}
		}
		else {
			logs[i] = (signed char) ( c / 256 );
		}
	}
}

ULONG Read8SVX(struct FileHandle *mfp)
{
ULONG	len;
char	done = 0;
struct Voice8Header	vhdr = {0, 0, 0, 10000, 1, 0, 64<<10};

	cvmode = CV_NONE|CV_BEND;
	while ( !done && Read(mfp, &ck, sizeof(struct ChunkHeader)) == sizeof(struct ChunkHeader) ) {
		switch ( ck.ckID ) {
		case	ID_VHDR:
			if ( ck.ckSize != 20L ) info("Nonstandard VHDR; will attempt to recover", fn);
			Read(mfp, &vhdr, ck.ckSize);
//			len = vhdr.oneShotHiSamples;
			vol = uvol ? uvol : vhdr.volume>>10;
			vol = vol ? vol : 64;
			rate = urate ? urate : vhdr.samplesPerSec;
			rate = rate ? rate : 10000;
			break;
		case	ID_NAME:
			Read(mfp, prop, WordAlign(ck.ckSize));
			propinfo("Sound Name", prop, ck.ckSize);
			break;
		case	ID_AUTH:
			Read(mfp, prop, WordAlign(ck.ckSize));
			propinfo("Author    ", prop, ck.ckSize);
			break;
		case	ID_ANNO:
			Read(mfp, prop, WordAlign(ck.ckSize));
			propinfo("Annotation", prop, ck.ckSize);
			break;
		case	ID_COPY:
			Read(mfp, prop, WordAlign(ck.ckSize));
			propinfo("Copyright ", prop, ck.ckSize);
			break;
		case	ID_BODY:
			len = ck.ckSize;
			done++;
			break;
		default:	/* others not supported */
			Read(mfp, prop, WordAlign(ck.ckSize));
			break;
		}
	}
	return(len);
}

ULONG ReadAIFF(struct FileHandle *mfp)
{
struct CommonChunk	comm = {1, 0, 8, {0x40, 0x0c, 0xb2, 0x0c, 0, 0, 0, 0, 0, 0} };
ULONG	i, numcomments;
UWORD	count;
int	done = 0, done2 = 0;
ULONG	len;

	cvmode = CV_NONE|CV_BEND;
	while ( Read(mfp, &ck, sizeof(struct ChunkHeader)) == sizeof(struct ChunkHeader) && !done ) {
		switch ( ck.ckID ) {
		case	ID_COMM:
			if ( ck.ckSize != 18L ) info("Nonstandard COMM; will attempt to recover", fn);
			Read(mfp, &comm, ck.ckSize);
			if ( comm.numChannels > 1 ) {
				info("Can't play multi-channel sounds", fn);
				Close(mfp);
				return(0L);
			}
			if ( comm.sampleSize == 8 );
			else if ( comm.sampleSize == 16) cvmode |= CV_DOWN;
			else {
				info("Can't play non-8-bit, non-16-bit sounds", fn);
				Close(mfp);
				return(0L);
			}
			len = comm.numSampleFrames;
			vol = uvol ? uvol : 64;
			rate = urate ? urate : FromIEEE(comm.sampleRate);
			rate = rate ? rate : 11395;
			break;
		case	ID_COMT:
			Read(mfp, &numcomments, 4);
			for(i=0; i<numcomments; i++) {
				Read(mfp, prop, 8);	/* how big is a MarkerID?
							 * see AIFF specs v1.3 */
				Read(mfp, &count, 8);
				Read(mfp, prop, MIN(256, count));	/* we only print the
									 * first 256 bytes */
				if ( count > 256 ) Seek(mfp, count-256, OFFSET_CURRENT);
				propinfo("   Comment", prop, MIN(256, count));
			}
			break;
		case	ID_NAME:
			Read(mfp, prop, WordAlign(ck.ckSize));
			propinfo("Sound Name", prop, ck.ckSize);
			break;
		case	ID_AUTH:
			Read(mfp, prop, WordAlign(ck.ckSize));
			propinfo("Author    ", prop, ck.ckSize);
			break;
		case	ID_ANNO:
			Read(mfp, prop, WordAlign(ck.ckSize));
			propinfo("Annotation", prop, ck.ckSize);
			break;
		case	ID_COPY:
			Read(mfp, prop, WordAlign(ck.ckSize));
			propinfo("Copyright ", prop, ck.ckSize);
			break;
		case	ID_SSND:
			Read(mfp, &ck, 8);	/* ignore 2 longs */
			done++;
			break;
		default:	/* others not supported */
			Read(mfp, prop, WordAlign(ck.ckSize));
			break;
		}
	}
	return(len);
}

ULONG ReadRIFF(struct FileHandle *mfp)
{
ULONG	len;
UWORD	bits;
int	done = 0, done2 = 0;
struct WaveCommon	wavcommon = {1, 1, 10000, 10000, 0};

	cvmode = CV_FLIP|CV_LEND;
	Read(mfp, &ck, 4);
	if ( ck.ckID != ID_WAVE ) {
		info("RIFF is not a WAVE format", fn);
		Close(mfp);
		return(NULL);
	}
	while( !done && (Read(mfp, &ck, sizeof(struct ChunkHeader)) == sizeof(struct ChunkHeader)) ) {
		switch( ck.ckID ) {		
		case	ID_fmt_:
			Read(mfp, &wavcommon, sizeof(struct WaveCommon));
//			LitEnd2BigEnd_uword(&wavcommon.wFormatTag);
//			LitEnd2BigEnd_uword(&wavcommon.wChannels);
//			LitEnd2BigEnd_ulong(&wavcommon.dwSamplesPerSec);
			wavcommon.wFormatTag = LitEnd2BigEnd_uword(wavcommon.wFormatTag);
			wavcommon.wChannels = LitEnd2BigEnd_uword(wavcommon.wChannels);
			wavcommon.dwSamplesPerSec = LitEnd2BigEnd_ulong(wavcommon.dwSamplesPerSec);
			if ( wavcommon.wFormatTag != 0x0001 ) {
				info("Can't read non-PCM WAVEs", fn);
				Close(mfp);
				return(NULL);
			}
			if ( wavcommon.wChannels != 0x0001 ) {
				info("Can't read multi-channel WAVEs", fn);
				Close(mfp);
				return(NULL);
			}
			rate = urate ? urate : wavcommon.dwSamplesPerSec;
			rate = rate ? rate : 10000;
			vol = uvol ? uvol : 64;
			Read(mfp, &bits, 2);
			bits = LitEnd2BigEnd_uword(bits);
			if ( bits == 8 );
			else if ( bits == 16 ) cvmode |= CV_DOWN;
			else {
				info("Can't play non-8-bit, non-16-bit sounds", fn);
				Close(mfp);
				return(NULL);
			}
			break;
		case	ID_INFO:
			while ( !done2 ) {
				Read(mfp, &ck, 4);
				switch( ck.ckID ) {
				case	ID_IARL:
					propinfo("Archived @", prop, readwavprop(prop, mfp));
					break;
				case	ID_IART:
					propinfo("Orig. Art.", prop, readwavprop(prop, mfp));
					break;
				case	ID_ICMT:
					propinfo("Comment   ", prop, readwavprop(prop, mfp));
					break;
				case	ID_ICOP:
					propinfo("Copyright ", prop, readwavprop(prop, mfp));
					break;
				case	ID_IENG:
					propinfo("Engineer  ", prop, readwavprop(prop, mfp));
					break;
				case	ID_IGNR:
					propinfo("Genre     ", prop, readwavprop(prop, mfp));
					break;
				case	ID_INAM:
					propinfo("Sound Name", prop, readwavprop(prop, mfp));
					break;
				case	ID_IPRD:
					propinfo("Prod. For ", prop, readwavprop(prop, mfp));
					break;
				case	ID_ISBJ:
					propinfo("Subject   ", prop, readwavprop(prop, mfp));
					break;
				case	ID_ISFT:
					propinfo("Prod. By  ", prop, readwavprop(prop, mfp));
					break;
				case	ID_ITCH:
					propinfo("Technician", prop, readwavprop(prop, mfp));
					break;
				default:
					if ( ck.ckID != ID_data && ck.ckID != ID_fmt_ ) readwavprop(prop, mfp);
					else {
						Seek(mfp, -4L, OFFSET_CURRENT);
						done2++;
					}
					break;
				}
			}
			break;
		case	ID_data:
			len = ck.ckSize;
			len = LitEnd2BigEnd_ulong(len);
			done++;
			break;
		default:
			break;
		}
	}
	return(len);
}

ULONG ReadVOC(struct FileHandle *mfp)
{
ULONG	len;
UBYTE	vocrate, dummy;

	Seek(fp, 30L, OFFSET_BEGINNING);
	Read(fp, &vocrate, 1);
	Read(fp, &dummy, 1);
	Seek(fp, 32L, OFFSET_BEGINNING);
	if ( dummy != 0 ) {
		info("VOC is too funky; I'll have to pass");
		return(0);
	}
	len = _ReadRaw();
	rate = urate ? urate : (1000000/(256-vocrate));
	rate = rate ? rate : 10000;
	vol = uvol ? uvol : 64;
	cvmode = CV_FLIP;
	return(len);
}

ULONG Read_SND(struct FileHandle *mfp)
{
ULONG	len;
struct SNDSoundStruct	sndstruct = {ID__SND, 28L, 0L, 1L, 8012L, 1L, 0L};

	Seek(mfp, 0L, OFFSET_BEGINNING);
	Read(mfp, &sndstruct, 28L);
	rate = urate ? urate : sndstruct.samplingRate;
	rate = rate ? rate : 8012;
	len = sndstruct.dataSize;
	vol = uvol ? uvol : 64;
	if ( sndstruct.channelCount != 1L ) {
		info("Can't read multi-channel sounds", fn);
		Close(mfp);
		return(NULL);
	}
	switch ( sndstruct.dataFormat ) {
	case	SND_FORMAT_MULAW_8:
		cvmode = CV_ULAW;
		mymaketable(logs, mygetscale(fn));
		break;
	case	SND_FORMAT_LINEAR_8:
		cvmode = CV_NONE;
		break;
	case	SND_FORMAT_LINEAR_16:
		cvmode = CV_DOWN;
		len /= 2;
		break;
	default:
		info("Can't read non-ulaw, non-linear-8, non-linear-16 NeXT files", fn);
		Close(mfp);
		return(NULL);
	}
	return(len);
}

ULONG ReadPP20(struct FileHandle *mfp)
{
ULONG	len;
UBYTE	*filestart = NULL;
ULONG	filelen;
int	err;

	DeleteFile(pptmp);

	if ( !PPBase ) {
		info("No powerpacker.library to decrunch", fn);
		return(0);
	}
	err = ppLoadData (fn, DECR_NONE, MEMF_PUBLIC, &filestart, &filelen, NULL);
	if ( err ) {
		info(ppErrorMessage(err), fn);
		return(0);
	}
	Close(fp);
	if ( fp = (struct FileHandle *)Open(pptmp, MODE_NEWFILE) ) {
		Write(fp, filestart, filelen);
		Close(fp);
		FreeMem(filestart, filelen);
		len = readfile(pptmp);
		ppuse = 1;
		return(len);
	}
	FreeMem(filestart, filelen);
	info("Can't read decrunched data", fn);
	fp = (struct FileHandle *)Open(fn, MODE_OLDFILE);
	return(0);
}

ULONG ReadRaw(void)
{
ULONG	len;

	if ( !guido(fn) ) info("Can't guess type; use -f to force playback", fn);
	len = _ReadRaw();
	if ( cvmode & CV_ULAW ) {
		mymaketable(logs, max);
		rate = urate ? urate : 8000;
	}
	return(len);
}

ULONG _ReadRaw(void)
{
struct FileInfoBlock	*fib;
ULONG	len = 0;

	rate = urate ? urate : 10000;
	vol = uvol ? uvol : 64;

	if ( fib = (struct FileInfoBlock *)AllocMem(sizeof(struct FileInfoBlock), MEMF_PUBLIC) ) {
		ExamineFH(fp, fib);
		len = fib->fib_Size;
		FreeMem(fib, sizeof(struct FileInfoBlock));
	}
	else info("Can't get sound file size", fn);

	return(len);
}
