/*****************************************************************************
 * PlaySound.c
 * This routine sets up an ioRequest and plays back a waveform through the
 * Amiga audio hardware
 *
 * Usage : PlaySound(buffer,repeat,period,volume)
 * buffer is a pointer to a waveform
 * repeat is the number of times to play the wave (0 == forever)
 * period determines the playback rate (minimum value == 124)
 * volume goes from 0 to 64
 *****************************************************************************/

#include "exec/types.h"
#include "devices/audio.h"
#include "exec/memory.h"

#define LEFT0F	1
#define RIGHT0F 2
#define RIGHT1F 4
#define LEFT1F	8
#define SIGBREAKF_CTRL_C (1<<12)

extern struct MsgPort *CreatePort();
struct IOAudio *AllocMem();

/* Look for a left channel, then a right */
UBYTE allocationMap[] = { LEFT0F, LEFT1F, RIGHT0F, RIGHT1F };

/*****************************************************************************
 * Purpose: To allocate and initialize an IO Request Block.		     *
 *****************************************************************************/

SetIOA(per, vol, repeat, len, ioa)
LONG per, vol, repeat;
ULONG len;
struct IOAudio **ioa;
{
    struct MsgPort *port;

    /* Allocate IOAudio structure */
    (*ioa) = AllocMem(sizeof(struct IOAudio), MEMF_PUBLIC | MEMF_CLEAR);

    /* If Allocation Successful ... */
    if (*ioa)
    {
	/* Set Priority */
	(*ioa)->ioa_Request.io_Message.mn_Node.ln_Pri = 10;
	/* Create Message port for IORequest to talk to Amiga */
	if (!(port = CreatePort(NULL, 0)))
	{
	    FreeMem((*ioa), sizeof(struct IOAudio));
	    *ioa = NULL;
	}
	/* If Creation Successful ... */
	else
	{
	    /* Get a channel */
	    (*ioa)->ioa_Request.io_Message.mn_ReplyPort = port;
	    (*ioa)->ioa_Data = allocationMap;
	    (*ioa)->ioa_Length = sizeof(allocationMap);
	    /* Open Audio Device for output */
	    if(OpenDevice(AUDIONAME, 0, (*ioa), 0))
	    {
		DeletePort(port);
		FreeMem((*ioa), sizeof(struct IOAudio));
		*ioa = NULL;
	    }
	    /* If open worked ... */
	    else
	    {
		/* Set Up Request */
		(*ioa)->ioa_Request.io_Flags = ADIOF_PERVOL;
		(*ioa)->ioa_Request.io_Command = CMD_WRITE;
		(*ioa)->ioa_Period = per;
		(*ioa)->ioa_Volume = vol;

		/* For some reason, the Audio chip can't play samples */
		/* longer than 131k, so we kludge.  Oh, well. */
		if(len < 131000)
		    (*ioa)->ioa_Cycles = repeat;
		else
		    (*ioa)->ioa_Cycles = 1;
	    }
	}
    }
}

/*****************************************************************************
 *		       Purpose: To play back a sound.			     *
 *****************************************************************************/

struct IOAudio *PlaySound(buffer, buflen, repeat, period, volume)
BYTE *buffer;
ULONG buflen;
LONG repeat,period,volume;
{
    struct IOAudio *ioa;
    BYTE *DataPtr;
    ULONG PlayLen;

    DataPtr = buffer;

    /* Set Up IOAudio structure */
    SetIOA(period, volume, repeat, buflen, &ioa);
    if (!ioa)
	return(NULL);

    /* Set up data and length pointers of ioa */
    SetLength(&PlayLen, buflen, &DataPtr, ioa);

    /* Send command to Audio chip */
    BeginIO(ioa);
    repeat--;

    /* If no data remains to play, return ioa pointer */
    if(PlayLen == 0)
	return(ioa);

    /* If there is more (buflen > 131000), continue until there isn't */
    while(TRUE)
    {
	ULONG signals;

	/* Wait until current chunk is done */
	signals = Wait((1 << ioa->ioa_Request.io_Message.mn_ReplyPort->mp_SigBit) | SIGBREAKF_CTRL_C);

	if (signals & SIGBREAKF_CTRL_C)
	{
	    SetSignal(SIGBREAKF_CTRL_C, SIGBREAKF_CTRL_C);
	    return(ioa);
	}

	/* If more remains, continue */
	if(PlayLen > 0)
	{
	    /* update pointers and start next chunk */
	    FixLength(&PlayLen, &DataPtr, ioa);
	    BeginIO(ioa);
	    /* return if playing the last chunk for the last time */
	    if((PlayLen == 0) && (repeat == 0))
		return(ioa);
	}
	/* If not ... */
	else
	{
	    /* Check repeat counter */
	    if(repeat != 0)
	    {
		/* Restart wave */
		DataPtr = buffer;
		SetLength(&PlayLen, buflen, &DataPtr, ioa);
		BeginIO(ioa);
		if(repeat > 0)
		    repeat--;
	    }
	}
    }
}

/*****************************************************************************
 * Purpose: To stop and delete a sound. THIS IS THE CLEAN UP ROUTINE!!	     *
 *****************************************************************************/

StopSound(struct IOAudio *ioa)
{
    AbortIO(ioa);
    if(ioa->ioa_Request.io_Device)
	CloseDevice(ioa);
    if(ioa->ioa_Request.io_Message.mn_ReplyPort)
	DeletePort(ioa->ioa_Request.io_Message.mn_ReplyPort);
    if(ioa)
	FreeMem(ioa,sizeof(struct IOAudio));
}

/*****************************************************************************
 *	   Purpose: To set the length of the sound to be played.	     *
 *****************************************************************************/

SetLength(ULONG *LenPtr, ULONG buflen, BYTE **DataHndl, struct IOAudio *ioa)
{
    if(buflen <= 131000)
	(*LenPtr) = buflen;
    else
	(*LenPtr) = 131000;

    ioa->ioa_Length = (*LenPtr);
    ioa->ioa_Data = (*DataHndl);

    if((*LenPtr) != buflen)
    {
	(*LenPtr) = buflen - 131000;
	(*DataHndl) += 131000;
    }
    else
	(*LenPtr) = 0;
}

/*****************************************************************************
 * Purpose: To Check, if the full sound is to be played (or max 131000 bytes)*
 *****************************************************************************/

FixLength(ULONG *LenPtr, BYTE **DataHndl, struct IOAudio *ioa)
{
    if((*LenPtr) > 131000)
    {
	ioa->ioa_Length = 131000;
	ioa->ioa_Data = (*DataHndl);
	(*LenPtr) -= 131000;
	(*DataHndl) += 131000;
    }
    else
    {
	ioa->ioa_Length = (*LenPtr);
	ioa->ioa_Data = (*DataHndl);
	(*LenPtr) = 0;
    }
}
