/*
 * OmniPlay, v1.23
 * by David Champion
 *
 * command parser & playroutine
 * 19 Nov 92
 */


#include <exec/types.h>
#include <exec/exec.h>

#include <graphics/gfxbase.h>
#include <dos/dos.h>
#include <devices/audio.h>

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

#include "oplay.h"
#include "forms.h"

extern struct library *DOSBase;

/*
 * libraries/mathffp.h from clib/alib_protos.h prototypes abs(),
 * which conflicts with stdlib.h
 */
#ifndef	LIBRARIES_MATHFFP_H
#define	LIBRARIES_MATHFFP_H 1
#endif	/* LIBRARIES_MATHFFP_H */
#include <clib/alib_protos.h>
#include <clib/exec_protos.h>
#include <pragmas/exec_lib.h>
#include <clib/dos_protos.h>
#include <pragmas/dos_lib.h>
#include <pragmas/powerpacker.h>

/* prototypes */
//void	main(int, char **);
void	parsecli(int, char **);
void	play(char *);
void	info(char *, char *);
UWORD	toperiod(const UWORD);
struct IOAudio	*GetIOB(ULONG);
struct IOAudio	*CopyIOB(struct IOAudio *);
void	FreeIOB(struct IOAudio *);
void	FreeCopy(struct IOAudio *);
ULONG	LoadIOB(struct FileHandle *, struct IOAudio *, ULONG, char *);
void	clean(int);
void	Usage(int);
int	TokenizeString(char *, char **);
extern	ULONG	readfile(char *);
extern	void	propinfo(char *, char *, ULONG);
extern	char	*tmpnam(char *);

/* constants */
UBYTE	ch_l[4]	= {1, 8, 1, 8};
UBYTE	ch_r[4]	= {2, 4, 2, 4};
UBYTE	ch_e[4]	= {1, 4, 2, 8};
UBYTE	ch_s[4]	= {3, 5, 10, 12};

/* globals */
char version[] = "$VER: OmniPlay 1.23 (19.11.92)";
struct FileHandle	*fp;
struct FileHandle	*writefp;
UBYTE	*chan		= ch_e;
ULONG	len;
char	utype		= AUTO;
UWORD	uvol		= 0;
UWORD	urate		= 0;
UWORD	vol		= 0;
UWORD	rate		= 0;
ULONG	ubufsiz		= 4000;
ULONG	bufsiz;
BOOL	oneshot		= 0;
char	*myname;
char	cvmode		= CV_NONE;
BOOL	showprops	= 1;
UWORD	displaymode	= NTSC;
LONG	oldpri;
BOOL	genulawtab	= 0;
BOOL	showmax		= 0;
int	max;
int	amplify		= 1;
//int	Amplify		= 1;
BOOL	fileinfo	= 0;
char	*writefn;
int	totallen;
FILE	*mystdout;
signed char		logs[256];
struct Library		*PPBase;

extern	char	pptmp[];
extern	BOOL	ppuse;


void main(int ac, char **av)
{
struct GfxBase	*GfxBase;
char	env[CMDMAXLEN+1];
int	envc;
char	*envv[CMDMAXLEN+1];
char	*envl;

	oldpri = SetTaskPri((struct Task *)FindTask(NULL), -1);

	if ( !ac ) exit(0);		/* No WB usage */

	myname=av[0];

	mystdout = stdout;

	if ( GfxBase = (struct GfxBase *)OpenLibrary("graphics.library", 0L) ) {
		displaymode = GfxBase->DisplayFlags;	/* PAL or NTSC? */
		CloseLibrary((struct Library *)GfxBase);
	}
	else info("Can't get display mode; assuming NTSC", myname);

		/* get pplib */
	PPBase = (struct Library *)OpenLibrary("powerpacker.library", 33L);

 	if ( ac == 1 ) {
		Usage(0);
		clean(0);
	}
	shift;

	if ( envl=getenv("OMNIPLAY") ) {
		strncpy(env, envl, CMDMAXLEN);
		if ( (envc=TokenizeString(env, envv)) != 0 ) parsecli(envc, envv);
	}
	parsecli(ac, av);

	clean(0);
}

void parsecli(int ac, char **av)
{
char	option;
char	*optarg;
LONG	pri;

	while ( ac > 0 ) {
		if ( USERBREAK ) {
			info("User break", myname);
			break;
		}
		if ( av[0][0] ==  '-' ) {
			option	= av[0][1];
			if ( strlen(av[0]) == 2 ) {
				shift;
				optarg=av[0];
			}
			else optarg=&av[0][2];
			switch ( option ) {
			case	'h':
				Usage(1);
				break;
			case	'v':
				uvol = atoi(optarg);
				break;
			case	'r':
				urate = atoi(optarg);
				break;
			case	'c':
				if ( optarg[0] == 'l' ) chan = ch_l;
				else if ( optarg[0] == 'r' ) chan = ch_r;
				else if ( optarg[0] == 'e' ) chan = ch_e;
				else if ( optarg[0] == 's' ) chan = ch_e;
				else {
					info("Unknown channel", optarg);
					clean(10);
				}
				break;
			case	'o':
				if ( (strcmp(optarg, "0")) && (strcmp(optarg, "1")) ) info("Bad usage of -o option", av[0]);
				else oneshot = (BOOL)atoi(optarg);
				break;
			case	'b':
				ubufsiz = atoi(optarg);
				break;
			case	'p':
				pri = atoi(optarg);
				if ( ( pri < -10 ) || (pri > 10) ) {
					info("Can't change priority out of {-10, 10}", av[0]);
					break;
				}
				SetTaskPri((struct Task *)FindTask(NULL), pri);
				break;
			case	'w':
				Delay(atoi(optarg)*50);
				break;
			case	's':
				if ( (strcmp(optarg, "0")) && (strcmp(optarg, "1")) ) info("Bad usage of -s option", av[0]);
				else showprops = (BOOL)atoi(optarg);
				break;
			case	'd':
				if ( !strcmp(optarg, "PAL") ) displaymode = PAL;
				else if ( !strcmp(optarg, "NTSC") ) displaymode = NTSC;
				else info("Bad usage of -d option", av[0]);
				break;
			case	'f':
				if ( strlen(optarg) != 1 ) {
					info("Bad usage of -f option", av[0]);
					break;
				}
				switch( optarg[0] ) {
				case	'u':
					utype = RAWU;
					break;
				case	's':
					utype = RAWS;
					break;
				case	'l':
					utype = RAWL;
					break;
				case	'a':
					utype = AUTO;
					break;
				default:
					info("Bad usage of -f option", av[0]);
					break;
				}
				break;
			case	'g':
				if ( (strcmp(optarg, "0")) && (strcmp(optarg, "1")) ) info("Bad usage of -g option", av[0]);
				else genulawtab = (BOOL)atoi(optarg);
				break;
			case	'm':
				if ( (strcmp(optarg, "0")) && (strcmp(optarg, "1")) ) info("Bad usage of -m option", av[0]);
				else showmax = (BOOL)atoi(optarg);
				break;
			case	'a':
				amplify = atoi(optarg);
				break;
//			case	'A':
//				Amplify = atoi(optarg);
//				break;
			case	'i':
				if ( (strcmp(optarg, "0")) && (strcmp(optarg, "1")) ) info("Bad usage of -i option", av[0]);
				else fileinfo = atoi(optarg);
				break;
			case	'W':
				writefn = optarg;
				break;
			case	'O':
				if ( mystdout != stdout ) {
					info("Output can be changed only once", av[0]);
					break;
				}
				mystdout = fopen(optarg, "w");
				break;
			case	'-':
				{
					char tmp[CMDMAXLEN+1];
					tmp[0] = '-';
					strncpy(&tmp[1], optarg, CMDMAXLEN-1);
					play( tmp );
					if ( writefn ) writefn = NULL;
					if ( ppuse ) {
						DeleteFile(pptmp);
						ppuse=0;
					}
				}
				break;
			default:
				info("Unknown option", av[0]);
				break;
			}
		}
		else {
			play( av[0] );
			if ( writefn ) writefn = NULL;
			if ( ppuse ) {
				DeleteFile(pptmp);
				ppuse=0;
			}
		}
		shift;
	}
	return;
}

void play(char *fn)
{
struct IOAudio	*buf1, *buf2;
register ULONG	size;
register int	b2used=0;

	if ( showprops ) fprintf(mystdout, "\n"COL3"File      : %s"COL1"\n", fn);

	if ( writefn ) {
		totallen = 40;
		if ( !(writefp = (struct FileHandle *)Open(writefn, MODE_NEWFILE)) ) {
			info("Can't open IFF output", "-W");
			writefn = NULL;
		}
	else Write((BPTR)writefp, "FORM----8SVXVHDR\000\000\000\024++++----++++----++++", 40);
	}

	if ( (len = readfile(fn)) == 0L ) return;

//	bufsiz = oneshot ? (len/2)+1 : ubufsiz;
	bufsiz = oneshot ? len : ubufsiz;

	if ( fileinfo ) {
		char	*style_str[] = {
			"8-bit",
			"16-bit",
			"signed",
			"unsigned",
			"u-law"
		};

		fprintf(mystdout, "Rate\t= %d\n", rate);
		fprintf(mystdout, "Volume\t= %d\n", vol);
		fprintf(mystdout, "Length\t= %d\n", len);
		fprintf(mystdout, "Buffers\t= %d\n", bufsiz);
		fprintf(mystdout, "Style\t= %s %s\n", style_str[(cvmode&4)>>2], style_str[(cvmode&3)+2]);
	}
	if ( showmax && (cvmode & CV_ULAW) ) fprintf(mystdout, "Ulaw max= %d.\n", max);

	if ( writefn ) {
	struct Voice8Header	vhdr = {0, 0, 0, 10000, 1, 0, 64<<10};
		vhdr.oneShotHiSamples = len;
		vhdr.samplesPerSec = rate;
		vhdr.volume = vol;

		totallen += len;
		Seek((BPTR)writefp, 4, OFFSET_BEGINNING);
		Write((BPTR)writefp, &totallen, 4);
		Seek((BPTR)writefp, 20, OFFSET_BEGINNING);
		Write((BPTR)writefp, &vhdr, sizeof(struct  Voice8Header));
		Seek((BPTR)writefp, 0, OFFSET_END);
		Write((BPTR)writefp, "BODY", 4);
		Write((BPTR)writefp, &len, 4);
	}

	fflush(mystdout);

	rate = toperiod(rate);

	if ( !(buf1 = (struct IOAudio *)GetIOB(bufsiz)) ) {
		info("Can't allocate buffer one", fn);
		Close((BPTR)fp);
		return;
	}
	buf1->ioa_Period = rate;
	buf1->ioa_Volume = vol;
	buf1->ioa_Cycles = 1;
	if ( len > bufsiz ) {
		if ( !(buf2 = (struct IOAudio *)CopyIOB(buf1)) ) {
			info("Can't allocate buffer two", fn);
			FreeIOB(buf1);
			Close((BPTR)fp);
			return;
		}
	}

	do {
		if ( USERBREAK ) {
			info("User break", fn);
			len = 0;
		}
		if ( len <= 0 ) {
			if ( b2used ) WaitIO((struct IORequest *)buf2);
			break;
		}
		size = MIN(len, bufsiz);
		if ( !LoadIOB(fp, buf1, size, fn) ) {
			if ( b2used ) WaitIO((struct IORequest *)buf2);
			break;
		}
		len -= size;
		buf1->ioa_Length = size;
		BeginIO((struct IORequest *)buf1);

		if ( b2used ) WaitIO((struct IORequest *)buf2);
		if ( USERBREAK ) {
			info("User break", fn);
			len = 0;
		}
		if ( len <= 0 ) {
			WaitIO((struct IORequest *)buf1);
			break;
		}
		size = MIN(len, bufsiz);
		if ( !LoadIOB(fp, buf2, size, fn) ) {
			WaitIO((struct IORequest *)buf1);
			break;
		}
		b2used++;
		len -= size;
		buf2->ioa_Length = size;
		BeginIO((struct IORequest *)buf2);

		WaitIO((struct IORequest *)buf1);
	} while ( TRUE );

	FreeIOB(buf1);
	if ( len > bufsiz ) FreeCopy(buf2);

	Close((BPTR)fp);
	if ( writefn ) Close((BPTR)writefp);

	return;
}

void info(char *msg, char *offender)
{
	fprintf(mystdout, "%s: %s: %s\n", myname, offender, msg);
	return;
}

UWORD toperiod(const UWORD sps)
{
	if ( displaymode == PAL  )	return( (UWORD)((1000000000 / 281)/sps) );
		/* not PAL, assume NTSC (GENLOC not an option here... */
	else				return( (UWORD)((1000000000 / 279)/sps) );
}

struct IOAudio *GetIOB(ULONG size)
{
struct IOAudio	*IOB;
void	*buf;
struct MsgPort	*port;

	if ( !(port = (struct MsgPort *)CreatePort("omniplay", 0)) ) return(NULL);
	if ( !(buf  = (void *)AllocMem(size, MEMF_CHIP)) ) {
		DeletePort(port);
		return(NULL);
	}
	if ( !(IOB = (struct IOAudio *)AllocMem(sizeof(struct IOAudio), MEMF_PUBLIC|MEMF_CLEAR)) ) {
		DeletePort(port);
		FreeMem(buf, size);
		return(NULL);
	}
	IOB->ioa_Request.io_Message.mn_Node.ln_Pri = 0;
	IOB->ioa_Request.io_Message.mn_ReplyPort = port;
	IOB->ioa_Data = chan;
	IOB->ioa_Length = 4;
	IOB->ioa_Request.io_Command = ADCMD_ALLOCATE;
	OpenDevice("audio.device", 0, (struct IORequest *)IOB, 0);
	if ( !IOB->ioa_AllocKey ) {
		DeletePort(port);
		FreeMem(buf, size);
		FreeMem(IOB, sizeof(struct IOAudio));
		return(NULL);
	}
	IOB->ioa_Request.io_Command = CMD_WRITE;
	IOB->ioa_Request.io_Flags = ADIOF_PERVOL;
	IOB->ioa_Data = buf;
	IOB->ioa_Length = size;

	return((struct IOAudio *)IOB);
}

struct IOAudio *CopyIOB(struct IOAudio *oldIOB)
{
struct IOAudio *IOB;
void 	*buf;

	if ( !oldIOB ) return(NULL);
	if ( !(buf = (void *)AllocMem(oldIOB->ioa_Length, MEMF_CHIP)) ) return(NULL);
	if ( !(IOB = (struct IOAudio *)AllocMem(sizeof(struct IOAudio), MEMF_PUBLIC|MEMF_CLEAR)) ) {
		FreeMem(buf, oldIOB->ioa_Length);
		return(NULL);
	}
	CopyMem(oldIOB, IOB, sizeof(struct IOAudio));
	IOB->ioa_Data = buf;
	return((struct IOAudio *)IOB);
}

void FreeIOB(struct IOAudio *IOB)
{
	if ( !IOB ) return;
	if ( IOB->ioa_Data ) FreeMem((UBYTE *)IOB->ioa_Data, bufsiz);
	IOB->ioa_Request.io_Command = ADCMD_FREE;
	BeginIO((struct IORequest *)IOB);
	WaitIO((struct IORequest *)IOB);
	DeletePort(IOB->ioa_Request.io_Message.mn_ReplyPort);
	CloseDevice((struct IORequest *)IOB);
	FreeMem(IOB, sizeof(struct IOAudio));
	return;
}

void FreeCopy(struct IOAudio *IOB)
{
	if ( !IOB ) return;
	if ( IOB->ioa_Data ) FreeMem((UBYTE *)IOB->ioa_Data, bufsiz);
	FreeMem(IOB, sizeof(struct IOAudio));
	return;
}

ULONG LoadIOB(struct FileHandle *lfp, struct IOAudio *IOB, ULONG size, char *fn)
{
register int	i;
register UBYTE	*iodata;
//LONG	j;

	if ( !size ) return(0L);

	iodata = IOB->ioa_Data;

	if ( cvmode & CV_DOWN ) {
		UWORD	tmp;
		if ( cvmode & CV_BEND ) {
			for(i=0; i<size; i++) {
				Read((BPTR)lfp, &tmp, 2);
				iodata[i] = (UBYTE)(tmp >> 8);
			}
//			IOB->ioa_Length = size;
		}
		else if ( cvmode & CV_LEND ) {
			for(i=0; i<size; i++) {
				Read((BPTR)lfp, &tmp, 2);
				iodata[i] = (UBYTE)(tmp & 0xff);
			}
//			IOB->ioa_Length = size;
		}
	}
//	else IOB->ioa_Length = Read((BPTR)lfp, iodata, size);
	else Read((BPTR)lfp, iodata, size);

	if ( cvmode & CV_ULAW ) {
		for(i=0; i<size; i++) iodata[i] = logs[iodata[i]];
	}
	if ( cvmode & CV_FLIP ) {
		for(i=0; i<size; i++) iodata[i] ^= 0x80;
	}

	if ( amplify != 1 ) for(i=0; i<size; i++) iodata[i] = (signed char)(iodata[i] * amplify);
//	if ( Amplify != 1 ) for(i=0; i<size; i++) {
//		j = (LONG)(iodata[i] * Amplify / 100);
//		iodata[i] = (signed char)(j<0 ? MIN(j, -128) : MAX(j, 127));
//	}

	if ( writefn ) Write((BPTR)writefp, iodata, size);

	return(IOB->ioa_Length);
}

void clean(int code)
{
	if ( mystdout != stdout ) {
		fclose(mystdout);
	}
	SetTaskPri((struct Task *)FindTask(NULL), oldpri);
	if ( PPBase ) CloseLibrary((struct Library *)PPBase);
	exit(code);
}

void Usage(int degree)
{
	fprintf(mystdout, "%s: Usage:\n", &version[6]);
	fprintf(mystdout, "%s\t[-h] [-b"COL3"bufsize"COL1"] [-c"COL3"l|r|e"
		COL1"] [-r"COL3"rate"COL1"] [-v"COL3"volume"COL1"] [-f"
		COL3"a|l|s|u"COL1"]\n", myname);
	fprintf(mystdout, "\t[-w"COL3"ticks"COL1"] [-s"COL3"0|1"COL1"] [-p"
		COL3"pri"COL1"] [-d"COL3"PAL|NTSC"COL1"] [-g"COL3"0|1"COL1
		"] [-m"COL3"0|1"COL1"] [-o"COL3"0|1"COL1"]\n");
	fprintf(mystdout, "\t[-O"COL3"filespec"COL1"] [-a"COL3"factor"COL1/*"] [-A"COL3
		"factor"COL1*/"] [-W"COL3"filename"COL1"] [--"COL3"file"COL1
		"] "COL3"file [...]"COL1"\n\n");
	if ( !degree ) fprintf(mystdout, "Use "COL3"oplay -h"COL1" for extended usage information.\n");
	if ( degree ) {
		fprintf(mystdout, "\t-h:\thelp.  This stuff here.\n");
		fprintf(mystdout, "\t-b:\tsize of one audio buffer (there are two).\n");
		fprintf(mystdout, "\t-c:\tchannel: left, right, or either.\n");
		fprintf(mystdout, "\t-r:\toverride sound file's playback rate, in samples per second.\n");
		fprintf(mystdout, "\t-v:\toverride sound file's volume.  0 <= volume <= 64.\n");
		fprintf(mystdout, "\t-f:\tforce playback as Auto, u-Law, Signed raw, or Unsigned raw.\n");
		fprintf(mystdout, "\t-w:\tpause between sounds.  One tick is 1/50 second.\n");
		fprintf(mystdout, "\t-p:\tset %s's task priority.\n", myname);
		fprintf(mystdout, "\t-s:\tshow (1) or don't show (0) optional properties.\n");
		fprintf(mystdout, "\t-d:\toverride default electrical standard.\n");
		fprintf(mystdout, "\t-g:\tgenerate log tables for ulaw files.\n");
		fprintf(mystdout, "\t-m:\tshow maximum encoded value for ulaw files.\n");
		fprintf(mystdout, "\t-o:\toneshot; override buffersize, loading whole file at once.\n");
		fprintf(mystdout, "\t-O:\tspecify output file or window.\n");
		fprintf(mystdout, "\t-a:\tamplify; multiplies loudness by the given integer.\n");
//		fprintf(mystdout, "\t-A:\tamplify; multiplies loudness by the given integer (cropped).\n");
		fprintf(mystdout, "\t-i:\tdisplay arcane sound information\n");
		fprintf(mystdout, "\t-W:\twrite the file as IFF.\n");
		fprintf(mystdout, "\t--:\tplay a file whose name begins with '-'.\n\n");
		fprintf(mystdout, "Any argument can appear anywhere.  Options take effect as processed, after\n");
		fprintf(mystdout, "any preceding files have been played.  You can use this to readjust OmniPlay's\n");
		fprintf(mystdout, "settings on a per-sound basis.  To remove rate or volume override, use\n");
		fprintf(mystdout, "-r0 or -v0.  To remove amplification, use -a1.\n");
	}
	return;
}

int TokenizeString(char *line, char **av)
{
int	ac	= 0;

	av[ac++] = (char *)strtok(line, " ");
	while ( av[ac++] = (char *)strtok(NULL, " ") );
	return(--ac);
}
