/*
 * Audio Handler
 *
 * Copyright (c) 1992,1995 by Martin Brenner, martin@ego.oche.de
 *
 * THIS INFORMATION IS PROVIDED "AS IS"; NO WARRANTIES ARE MADE.
 * ALL USE IS AT YOUR OWN RISK, AND NO LIABILITY OR 
 * RESPONSIBILITY IS ASSUMED.
 *
 * This handler is partially based on port-handler by Andy Finkel.
 * Some insight about DOS and the SAS C Compiler was gained from
 * the Guru Book by Ralph Babel.
 *
 * For each new invocation of this handler a new process is started,
 * just like the console handler. Options after the colon ':' are
 * evaluated and Audio output is done with double buffering.
 *
 * This is an example for a handler where incoming DOS packets are
 * NOT the same length as messages sent to the exec device. Instead,
 * data is buffered and sent to the device au bloc.
 *
 * $Header: Work:Progs/aud/RCS/audio.c,v 1.4 1995/08/01 03:04:48 martin Exp martin $
 *
 * $Log: audio.c,v $
 * Revision 1.4  1995/08/01  03:04:48  martin
 * Bug fixes for version 1.0 beta
 *
 * Revision 1.3  1995/07/19  10:04:04  martin
 * first release version, 1.0 beta
 *
 * Revision 1.2  1995/07/04  20:00:33  martin
 * almost completely rewritten, now finally working
 *
 * Revision 1.1  92/12/22  17:01:47  martin
 * Initial revision
 * 
 *
 * 
 */

#include <exec/memory.h>
#include <exec/lists.h>
#include <exec/errors.h>
#include <dos/dos.h>
#include <dos/dosextens.h>
#include <dos/filehandler.h>
#include <devices/audio.h>
/* Hack, because registerized BeginIO() doesn't seem to exist in small.lib */
#define BeginIO __stdargs BeginIO
#include <proto/exec.h>
#undef BeginIO

#include "audio.h"

/* debugging on the serial port */
#define DEBUG
#define DPRINTF KPrintF

#define QTOUPPER(c)      ((c)>='a'&&(c)<='z'?(c)-'a'+'A':(c))

#undef  BADDR
#define BADDR(x)        ((CPTR)((LONG)x << 2))

/*
 * global vars
 * 
 * since several invocations (processes) of the handler use the SAME
 * global data segment, process specific data MUST be allocated or
 * created on the stack. ExecBase can safely be global, though.
 */
extern struct ExecBase *__far AbsExecBase;
struct ExecBase *SysBase;

/* Prototypes */
BOOL checkReadyQueue(struct AudioState *as);
BOOL checkIOBHandling(struct AudioState *as);
void swapPtrs(struct AudioState *as);
void allocIO(struct IOAudio *iob, struct AudioOptions *ao);
void freeIO(struct IOAudio *iob, struct AudioOptions *ao);
void lockIO(struct IOAudio *iob, struct AudioOptions *ao);
void abortIO(struct IOAudio *iob, struct AudioOptions *ao);
void writeIO(struct IOAudio *iob, struct AudioBuffer *buffer, struct AudioOptions *ao);
void returnpkt(struct DosPacket *packet, LONG res1, LONG res2, struct AudioOptions *ao);
int strncmpi(char *str1, char *str2, int n);
BOOL parseArgs(struct AudioOptions *ao, int len, char *str);
LONG getLong(int *len, char **ptr);

/*
 * quark()
 *
 * entry point for the handler.
 */
void __saveds quark(void)
{
	UBYTE *openstring;			/* BCPL String to Handler Device Name */
	BOOL error = 1;

	struct AudioState state;	/* This struct contains the complete information about Audio IO */

	struct DeviceNode *devnode;	/* our device node is passed in parm pkt Arg3 */
	struct DosPacket *packet;	/* temporary packet handle, as received from the calling process */
	struct MsgPort *devport;	/* MessagePort of handler process */
	struct Process *process;	/* Process ID of handler process */
	struct Message *message;	/* temporary message handle */

	/* First, initialize SysBase, so we may call exec functions */
	SysBase = AbsExecBase;

	process = (struct Process *)FindTask(NULL);
	devport = &process->pr_MsgPort;

	WaitPort(devport);		/* Wait for startup message */
	message = (struct Message *)GetMsg(devport);
	packet = (struct DosPacket *) message->mn_Node.ln_Name;

	openstring = (UBYTE *)BADDR(packet->dp_Arg1);
	devnode = (struct DeviceNode *)BADDR(packet->dp_Arg3);

	/* Set default options, currently any channel */
	state.AS_Options.AO_Priority = 0;
	state.AS_Options.AO_NumChannels = 4;
	state.AS_Options.AO_Channels = "\x1\x2\x4\x8";
	state.AS_Options.AO_BufferSize = AUDIOBUFSIZE;
	state.AS_Options.AO_Period = DEFAULT_PERIOD;
	state.AS_Options.AO_Volume = DEFAULT_VOLUME;
	state.AS_Options.AO_Debug = 0;

	/* Seek for ':' and interpret everything after as options */
	parseArgs(&(state.AS_Options), openstring[0], openstring+1);

#ifdef DEBUG
	if (state.AS_Options.AO_Debug)
		DPRINTF("S%ld", state.AS_Options.AO_Debug);
#endif

	/* 
	 * create several IO blocks, Messageport and open the device
	 * we dont create another port, but use our own port 
	 */
	state.AS_IO1 = (struct IOAudio *)AllocMem(sizeof(struct IOAudio), MEMF_PUBLIC|MEMF_CLEAR);
	if (!state.AS_IO1)
		goto handler_exit;
	state.AS_IO2 = (struct IOAudio *)AllocMem(sizeof(struct IOAudio), MEMF_PUBLIC|MEMF_CLEAR);
	if (!state.AS_IO2)
		goto handler_exit;
	state.AS_AllocIO = (struct IOAudio *)AllocMem(sizeof(struct IOAudio), MEMF_PUBLIC|MEMF_CLEAR);
	if (!state.AS_AllocIO)
		goto handler_exit;
	state.AS_LockIO = (struct IOAudio *)AllocMem(sizeof(struct IOAudio), MEMF_PUBLIC|MEMF_CLEAR);
	if (!state.AS_LockIO)
		goto handler_exit;

	state.AS_CurrentIO = state.AS_IO1;
	state.AS_NextIO = state.AS_IO2;

	state.AS_AllocIO->ioa_Request.io_Message.mn_ReplyPort = devport;

	/* link a dummy packet */
	state.AS_DummyPacket.dp_Type = ACTION_WRITE_RETURN;
	state.AS_AllocIO->ioa_Request.io_Message.mn_Node.ln_Name = (UBYTE *)&(state.AS_DummyPacket);

	state.AS_BytesCopied = 0;	/* A counter for bytes copied from DOS packet to Audio buffer */

	/* allocate chip mem for audio double buffer, initialize all buffer variables */
	state.AS_Buffer1.AB_Data = AllocMem(state.AS_Options.AO_BufferSize, MEMF_CHIP);
	state.AS_Buffer1.AB_Size = state.AS_Options.AO_BufferSize;
	state.AS_Buffer1.AB_End = 0;
	state.AS_Buffer1.AB_InUse = 0;
	if (!state.AS_Buffer1.AB_Data)
		goto handler_exit;
	state.AS_Buffer2.AB_Data = AllocMem(state.AS_Options.AO_BufferSize, MEMF_CHIP);
	state.AS_Buffer2.AB_Size = state.AS_Options.AO_BufferSize;
	state.AS_Buffer2.AB_End = 0;
	state.AS_Buffer2.AB_InUse = 0;
	if (!state.AS_Buffer2.AB_Data)
		goto handler_exit;

	/* Current buffer is buffer currently played */
	/* Next buffer is buffer currently being filled with sound data */
	state.AS_CurrentBuffer = &(state.AS_Buffer1);
	state.AS_NextBuffer = &(state.AS_Buffer2);

	/* Initialize the DOS packet input queue */
	NewList(&(state.AS_ReadyQueue));

	/* Open audio.device, DON'T allocate any channels yet */
	if (error = OpenDevice(AUDIONAME, 0, (struct IORequest *)state.AS_AllocIO, 0)) {
		returnpkt(packet, DOSFALSE, ERROR_OBJECT_IN_USE, &(state.AS_Options));
		goto handler_exit;
	}

	state.AS_AllocState = ALLOCSTATE_UNUSED;
	state.AS_LockState = LOCKSTATE_UNUSED;
	state.AS_OpenForOutput = FALSE;

	/* devnode->dn_Task = devport;  nope, start one task for each invocation */

	/* Finished with startup parameter packet...send back...*/
	returnpkt(packet, DOSTRUE, packet->dp_Res2, &(state.AS_Options));

	/*
	 * The Main Event Loop:
	 *
	 * We have the following incoming messages:
	 *
	 * ACTION_FINDOUTPUT  user process opens our file handle for output
	 * ACTION_WRITE       play sound request coming from the user process
	 * ACTION_END         the file handle is closed -> we will be closed
	 * ACTION_IS_FILESYSTEM is the handler a filesystem? NO
	 * return from ADCMD_ALLOCATE  coming from the audio.device
	 * return from CMD_WRITE       coming from the audio.device
	 * return from ADCMD_FREE      coming from the audio.device
	 * the last three will be labeled with ACTION_WRITE_RETURN on our message port.
	 * Therefore, a dummy packet is linked with the IO-Requests.
	 *
	 * ADCMD_ALLOCATE might block if a channel is not available
	 * ACTION_WRITE may block, if the buffer is full
	 * ACTION_END may block, if there is still sound playing
	 *
	 * Audio return messages have a dummy dos packet in their ln_Name field,
	 * which has a type of ACTION_WRITE_RETURN.
	 */

	do { 
		WaitPort(devport);
		/* evaluate incoming packets */
		while ((message = (struct Message *) GetMsg(devport)) != 0) {
			packet = (struct DosPacket *) message->mn_Node.ln_Name;
			switch (packet->dp_Type) {

				case ACTION_FINDOUTPUT:
					/* The device is opened here. We try to allocate requested channel(s) */
					state.AS_FileHandle = (struct FileHandle *) BADDR(packet->dp_Arg1);
#ifdef DEBUG
					if (state.AS_Options.AO_Debug)
						DPRINTF("F%ld", state.AS_Options.AO_Debug);
#endif
					if (state.AS_OpenForOutput) {
						returnpkt(packet, DOSFALSE, ERROR_OBJECT_IN_USE, &(state.AS_Options));
					}
					else {
						state.AS_OpenForOutput = DOSTRUE;
						state.AS_FileHandle->fh_Port = (struct MsgPort *)DOSTRUE;	/* Interactive */
						state.AS_FileHandle->fh_Arg1 = 42;	/* handler-private info */
						returnpkt(packet, DOSTRUE, 0L, &(state.AS_Options));
					}
					break;

				case ACTION_END:          /* Close request */
					state.AS_OpenForOutput = FALSE;
#ifdef DEBUG
					if (state.AS_Options.AO_Debug)
						DPRINTF("E%ld", state.AS_Options.AO_Debug); 
#endif
					returnpkt(packet, DOSTRUE, 0L, &(state.AS_Options));
					break;

				case ACTION_WRITE:            	/* Write Request */
					/* put new write request in the waiting queue */
					AddTail(&(state.AS_ReadyQueue), (struct Node *)message);
#ifdef DEBUG
					if (state.AS_Options.AO_Debug)
						DPRINTF("W%ld", state.AS_Options.AO_Debug);
#endif
					if (state.AS_AllocState == ALLOCSTATE_UNUSED) {
						/* allocate channel(s) */
						/* request new key */
						state.AS_AllocIO->ioa_AllocKey = 0;
						allocIO(state.AS_AllocIO, &(state.AS_Options));
						state.AS_AllocState = ALLOCSTATE_ALLOCATING;
					}
					break;

				case ACTION_IS_FILESYSTEM:		/* Is handler a filesystem? */
					/* It isn't! */
#ifdef DEBUG
					if (state.AS_Options.AO_Debug)
						DPRINTF("I%ld", state.AS_Options.AO_Debug);
#endif
					returnpkt(packet, DOSFALSE, 0L, &(state.AS_Options));
					break;

				case ACTION_WRITE_RETURN:
					/* IO Request came back - check which one */
					switch (((struct IORequest *)message)->io_Command) {
						case ADCMD_ALLOCATE:
							/* allocation has finished, initialize iobs with key */
							*state.AS_IO1 = *state.AS_AllocIO;
							*state.AS_IO2 = *state.AS_AllocIO;
							*state.AS_LockIO = *state.AS_AllocIO;
							state.AS_AllocState = ALLOCSTATE_ALLOCATED;
#ifdef DEBUG
							if (state.AS_Options.AO_Debug)
								DPRINTF("ra%ld", state.AS_Options.AO_Debug);
#endif
							/* lock the allocated channel */
							lockIO(state.AS_LockIO, &(state.AS_Options));
							state.AS_LockState = LOCKSTATE_LOCKED;
							break;
						case CMD_WRITE:
							/* check, which buffer is free again */
							if ((struct IOAudio *)message == state.AS_IO1) {
								state.AS_Buffer1.AB_InUse = 0;
								if (state.AS_IO1->ioa_Request.io_Error != IOERR_ABORTED)
									state.AS_Buffer1.AB_End = 0;
							}
							else {
								state.AS_Buffer2.AB_InUse = 0;
								if (state.AS_IO2->ioa_Request.io_Error != IOERR_ABORTED)
									state.AS_Buffer2.AB_End = 0;
							}
#ifdef DEBUG
							if (state.AS_Options.AO_Debug)
								DPRINTF("rw%ld", state.AS_Options.AO_Debug);
#endif
							break;
						case ADCMD_LOCK:
							/* LOCK replied */
#ifdef DEBUG
							if (state.AS_Options.AO_Debug)
								DPRINTF("rl%ld", state.AS_Options.AO_Debug);
#endif
							if (state.AS_LockIO->ioa_Request.io_Error == ADIOERR_CHANNELSTOLEN) {
								/* Channel stolen, set allocate flag */
								state.AS_AllocState = ALLOCSTATE_STOLEN;
								/*
								 * If NextBuffer is in use, it is currently played,
								 * and CurrentBuffer is sent, but not yet played by the
								 * audio.device, so it is safe to AbortIO it without
								 * losing sound. Anyway, we wait until both io requests
								 * are inactive before we send the ADCMD_FREE request.
								 */
								if (state.AS_NextBuffer->AB_InUse)
									abortIO(state.AS_CurrentIO, &(state.AS_Options));
								/* 
								 * swap, so Current becomes Next to be sent as next after
								 * channel reallocation.
								 */
								swapPtrs(&state);
							}
							state.AS_LockState = LOCKSTATE_UNUSED;
							break;
						case ADCMD_FREE:
							/* Allocation has ended, set state */
							state.AS_AllocState = ALLOCSTATE_UNUSED;
#ifdef DEBUG
							if (state.AS_Options.AO_Debug)
								DPRINTF("rf%ld", state.AS_Options.AO_Debug);
#endif
							/* Check if we still have data to send, if yes, the FREE */
							/* was a response to free stolen channels */
							if (!IsListEmpty(&(state.AS_ReadyQueue))
									|| state.AS_Buffer1.AB_End || state.AS_Buffer2.AB_End) {
								/* try to allocate the channels again */
								/* use old key */
								allocIO(state.AS_AllocIO, &(state.AS_Options));
								state.AS_AllocState = ALLOCSTATE_ALLOCATING;
							}
							break;
					}
					break;
				default:
#ifdef DEBUG
					if (state.AS_Options.AO_Debug)
						DPRINTF("P%ld?%ld", packet->dp_Type, state.AS_Options.AO_Debug);
#endif
					returnpkt(packet, DOSFALSE, ERROR_ACTION_NOT_KNOWN, &(state.AS_Options));
					break;
			}
		}
			
		/* If there is stealing and audio channel is no longer used, free it */
		if (state.AS_AllocState == ALLOCSTATE_STOLEN
				&& !state.AS_Buffer1.AB_InUse && !state.AS_Buffer2.AB_InUse) {
			/* Free Channel */
			freeIO(state.AS_AllocIO, &(state.AS_Options));
			state.AS_AllocState = ALLOCSTATE_FREEING;
		}

		/* As long as there is activity, try handling input and output */
		while (checkReadyQueue(&state) || checkIOBHandling(&state))
			;

		/* Is device being closed? */
		if (!state.AS_OpenForOutput && IsListEmpty(&(state.AS_ReadyQueue))
				&& state.AS_AllocState == ALLOCSTATE_ALLOCATED
				&& !state.AS_Buffer1.AB_InUse && !state.AS_Buffer2.AB_InUse
				&& !state.AS_Buffer1.AB_End && !state.AS_Buffer2.AB_End) {
			/* Free Channel */
			freeIO(state.AS_AllocIO, &(state.AS_Options));
			state.AS_AllocState = ALLOCSTATE_FREEING;
		}
	} while (state.AS_OpenForOutput || !IsListEmpty(&(state.AS_ReadyQueue))
			|| state.AS_AllocState != ALLOCSTATE_UNUSED
			|| state.AS_LockState != LOCKSTATE_UNUSED);

handler_exit:
	if (!error)
		CloseDevice((struct IORequest *)state.AS_AllocIO);
	if (state.AS_IO1)
		FreeMem(state.AS_IO1, sizeof(struct IOAudio));
	if (state.AS_IO2)
		FreeMem(state.AS_IO2, sizeof(struct IOAudio));
	if (state.AS_AllocIO)
		FreeMem(state.AS_AllocIO, sizeof(struct IOAudio));
	if (state.AS_LockIO)
		FreeMem(state.AS_LockIO, sizeof(struct IOAudio));
	if (state.AS_Buffer1.AB_Data)
		FreeMem(state.AS_Buffer1.AB_Data, state.AS_Buffer1.AB_Size);
	if (state.AS_Buffer2.AB_Data)
		FreeMem(state.AS_Buffer2.AB_Data, state.AS_Buffer2.AB_Size);

	/* end of main handler code */
#ifdef DEBUG
	if (state.AS_Options.AO_Debug)
		DPRINTF("X%ld\n", state.AS_Options.AO_Debug);
#endif
}
/*
 * checkReadyQueue()
 *
 * check, if there are still incoming packets not already handled
 */
BOOL checkReadyQueue(struct AudioState *as)
{
	struct Message *msg = (struct Message *)as->AS_ReadyQueue.lh_Head;
	BOOL active = FALSE;

	if (!as->AS_NextBuffer->AB_InUse && msg->mn_Node.ln_Succ) {
		struct DosPacket *p = (struct DosPacket *) msg->mn_Node.ln_Name;
		UBYTE *src, *dst;
		LONG restlen, packetlen;

		/* try to fit write request into current 'new' buffer */
		restlen = as->AS_NextBuffer->AB_Size - as->AS_NextBuffer->AB_End;
		if (restlen) {	/* Buffer is not yet full */
			packetlen = p->dp_Arg3 - as->AS_BytesCopied;
			src = (UBYTE *)p->dp_Arg2 + as->AS_BytesCopied;
			dst = as->AS_NextBuffer->AB_Data + as->AS_NextBuffer->AB_End;
			if (packetlen <= restlen) {
				/* copy whole packet and reply */
				LONG i;
				for (i = packetlen; i; --i)
					*dst++ = *src++;
				Remove(&(msg->mn_Node));
				returnpkt(p, p->dp_Arg3, 0, &(as->AS_Options));
				as->AS_BytesCopied = 0;
				as->AS_NextBuffer->AB_End += packetlen;
			}
			else {
				/* Buffer is full */
				LONG i;
				for (i = restlen; i; --i)
					*dst++ = *src++;
				as->AS_BytesCopied += restlen;
				as->AS_NextBuffer->AB_End = as->AS_NextBuffer->AB_Size;
			}
			active = TRUE;
		}
	}
	return active;
}


/*
 * checkIOBHandling()
 *
 * check if a buffer can be sent to the audio.device
 */
BOOL checkIOBHandling(struct AudioState *as)
{
	BOOL active = FALSE;

	if (as->AS_AllocState == ALLOCSTATE_ALLOCATED) {
		/* check if NextBuffer can be sent to the audio.device, either */
		/* if the buffer is full, or the ReadyQueue is empty and the device is closing */
		if (!as->AS_NextBuffer->AB_InUse 
				&& (as->AS_NextBuffer->AB_End == as->AS_NextBuffer->AB_Size
					|| as->AS_NextBuffer->AB_End > 0 && (!as->AS_OpenForOutput) 
						&& IsListEmpty(&(as->AS_ReadyQueue)))) {
			/* send the buffer off */
			writeIO(as->AS_NextIO, as->AS_NextBuffer, &(as->AS_Options));
			as->AS_NextBuffer->AB_InUse = 1;
			/* swap buffers */
			swapPtrs(as);
			active = TRUE;
		}
	}
	return active;
}

/*
 * swapPtrs()
 *
 * swap two pointers
 */
void swapPtrs(struct AudioState *as)
{
	struct AudioBuffer *tmpbuf;
	struct IOAudio *tmpio;

	tmpbuf = as->AS_NextBuffer;
	as->AS_NextBuffer = as->AS_CurrentBuffer;
	as->AS_CurrentBuffer = tmpbuf;
	tmpio = as->AS_NextIO;
	as->AS_NextIO = as->AS_CurrentIO;
	as->AS_CurrentIO = tmpio;
}

/*
 * allocIO()
 *
 * allocate one or more audio channels.
 */
void allocIO(struct IOAudio *iob, struct AudioOptions *ao)
{
	iob->ioa_Request.io_Message.mn_Node.ln_Pri = ao->AO_Priority;
	iob->ioa_Request.io_Command = ADCMD_ALLOCATE;
	iob->ioa_Request.io_Flags = 0;
	iob->ioa_Data = ao->AO_Channels;
	iob->ioa_Length = ao->AO_NumChannels;
	BeginIO((struct IORequest *)iob);
#ifdef DEBUG
	if (ao->AO_Debug)
		DPRINTF("a%ld", ao->AO_Debug);
#endif
}

/*
 * lockIO()
 *
 * lock one or more audio channels.
 */
void lockIO(struct IOAudio *iob, struct AudioOptions *ao)
{
	iob->ioa_Request.io_Command = ADCMD_LOCK;
	iob->ioa_Request.io_Flags = 0;
	BeginIO((struct IORequest *)iob);
#ifdef DEBUG
	if (ao->AO_Debug)
		DPRINTF("l%ld", ao->AO_Debug);
#endif
}

/*
 * freeIO()
 *
 * allocate one or more audio channels.
 */
void freeIO(struct IOAudio *iob, struct AudioOptions *ao)
{
	iob->ioa_Request.io_Command = ADCMD_FREE;
	iob->ioa_Request.io_Flags = 0;
	BeginIO((struct IORequest *)iob);
#ifdef DEBUG
	if (ao->AO_Debug)
		DPRINTF("f%ld", ao->AO_Debug);
#endif
}

/*
 * writeIO()
 *
 * allocate one or more audio channels.
 */
void writeIO(struct IOAudio *iob, struct AudioBuffer *buffer, struct AudioOptions *ao)
{
	iob->ioa_Request.io_Message.mn_Node.ln_Pri = ao->AO_Priority;
	iob->ioa_Request.io_Command = CMD_WRITE;
	iob->ioa_Request.io_Flags = ADIOF_PERVOL;
	iob->ioa_Data = buffer->AB_Data;
	iob->ioa_Length = buffer->AB_End;
	iob->ioa_Period = ao->AO_Period;
	iob->ioa_Volume = ao->AO_Volume;
	iob->ioa_Cycles = 1;
	BeginIO((struct IORequest *)iob);
#ifdef DEBUG
	if (ao->AO_Debug)
		DPRINTF("w%ld", ao->AO_Debug);
#endif
}

/*
 * abortIO()
 *
 * Abort a CMD_WRITE request
 */
void abortIO(struct IOAudio *iob, struct AudioOptions *ao)
{
#ifdef DEBUG
	if (ao->AO_Debug)
		DPRINTF("bw%ld", ao->AO_Debug);
#endif
	AbortIO((struct IORequest *)iob);
}

/*
 * returnpkt()
 *
 * return a packet to calling DOS process.
 */
void returnpkt(struct DosPacket *packet, LONG res1, LONG res2, struct AudioOptions *ao)
{
	struct Message *message = packet->dp_Link;
	struct MsgPort *replyport = packet->dp_Port;
	struct Process *process = (struct Process *)FindTask(NULL);
 
	packet->dp_Res1 = res1;
	packet->dp_Res2 = res2; 
	packet->dp_Port = &(process->pr_MsgPort);
	message->mn_Node.ln_Name = (char *) packet;
 
#ifdef DEBUG
	if (ao->AO_Debug)
		DPRINTF("RP%ld", ao->AO_Debug);
#endif
	PutMsg(replyport,message); 
}

/*
 * parseArgs()
 *
 * parse arguments of the device string (after the colon ':')
 */
BOOL parseArgs(struct AudioOptions *ao, int len, char *str)
{
	LONG val;
	while (len && *str != ':') {
		len--, str++;
	}
	if (*str != ':')
		return FALSE;
	len--, str++;
	while (len > 0) {
		if (!strncmpi(str, "PERIOD", 6)) {
			len -= 6, str += 6;
			val = getLong(&len, &str);
			if (val >= 124 && val <= 65535)
				ao->AO_Period = val;
		}
		else if (!strncmpi(str, "FREQUENCY", 9)) {
			len -= 9, str += 9;
			val = getLong(&len, &str);
			if (val >= 55 && val <= 28603)
				ao->AO_Period = CLOCK_CONSTANT_PAL / val;
		}
		else if (!strncmpi(str, "VOLUME", 6)) {
			len -= 6, str += 6;
			val = getLong(&len, &str);
			if (val >= 0 && val <= 64)
				ao->AO_Volume = val;
		}
		else if (!strncmpi(str, "PRIORITY", 8)) {
			len -= 8, str += 8;
			val = getLong(&len, &str);
			if (val >= -128 && val <= 127)
				ao->AO_Priority = val;
		}
		else if (!strncmpi(str, "BUFFER", 6)) {
			len -= 6, str += 6;
			val = getLong(&len, &str);
			if (val >= 0x1000 && val <= 0x40000) {
				val = val / 4;						/* 2 buffers */
				ao->AO_BufferSize = val * 2;	/* must be even size */
			}
		}
		else if (!strncmpi(str, "CHANNEL", 7)) {
			len -= 7, str += 7;
			val = getLong(&len, &str);
			if (val >= 0 && val <= 3) {
				ao->AO_NumChannels = 1;
				ao->AO_Channels = "\x1\x2\x4\x8" + val;
			}
		}
		else if (!strncmpi(str, "LEFT", 4)) {
			len -= 4, str += 4;
			ao->AO_NumChannels = 2;
			ao->AO_Channels = "\x1\x8";
		}
		else if (!strncmpi(str, "RIGHT", 5)) {
			len -= 5, str += 5;
			ao->AO_NumChannels = 2;
			ao->AO_Channels = "\x2\x4";
		}
		else if (!strncmpi(str, "STEREO", 6)) {
			len -= 6, str += 6;
			ao->AO_NumChannels = 4;
			ao->AO_Channels = "\x3\x5\xA\xC";
			/* but not yet fully implemented! */
		}
		else if (!strncmpi(str, "16BIT", 5)) {
			len -= 5, str += 5;
			/* not implemented! */
		}
		else if (!strncmpi(str, "DEBUG", 5)) {
			len -= 5, str += 5;
			val = getLong(&len, &str);
			ao->AO_Debug = val;
		}
		while (len && *str != '/') /* skip rest (as CON: does, too) */
			len--, str++;
		if (*str == '/')
			len--, str++;
	}
	return TRUE;
}

/*
 * getLong()
 *
 * convert string to integer, supports decimal, octal and hex
 */
LONG getLong(int *len, char **ptr)
{
	LONG val = 0;
	int l = *len;
	char *str = *ptr;
	int sign = 1;

	if (l == 0)
		return val;

	while (*str == ' ' || *str == '\t')
		str++;

	if (*str == '-') {
		sign = -1;
		str++;
	}
	if (*str == '0') {
		l--, str++;
		if (l == 0) {
			*len = l, *ptr = str;
			return val;
		}
		if (QTOUPPER(*str) == 'X') {
			l--, str++;
			while (len > 0) {
				if (*str >= '0' && *str <= '9')
					val = val * 16 + *str - '0';
				else if (QTOUPPER(*str) >= 'A' && QTOUPPER(*str) <= 'F')
					val = val * 16 + QTOUPPER(*str) - 'A';
				else
					break;
				l--, str++;
			}
		}
		else {
			while (len > 0 && *str >= '0' && *str <= '7') {
				val = val * 8 + *str - '0';
				l--, str++;
			}
		}
	}
	else {
		while (len > 0 && *str >= '0' && *str <= '9') {
			val = val * 10 + *str - '0';
			l--, str++;
		}
	}
	*len = l, *ptr = str;

	return val * sign;
}

/*
 * strncmpi()
 *
 * compare two strings, length n, ignorecase
 */
int strncmpi(char *str1, char *str2, int n)
{
	UBYTE *astr = str1;
	UBYTE *bstr = str2;
	UBYTE c;

	while ((c = QTOUPPER(*astr)) && (c == QTOUPPER(*bstr)) && n) 
		astr++, bstr++, n--;
	if (!c || !n)
		return 0;
	if (c < *bstr)
		return -1;
	return 1;
}
