/* player - sound playing portion of karl's smus player */
/* hacked to be an effects player */


/* note - "channels", throughout the code below, are a structure I have
 * invented, a logical channel sort of thing that is useful for what
 * I'm trying to do.  There is not a 1-1 correspondence between these
 * channels and the hardware channels, which hardware channel is used
 * for a given channel is determined by audio.device when we make a
 * request to allocate a channel.
 */

#include <exec/types.h>
#include <functions.h>
#include <exec/memory.h>
#include <devices/audio.h>
#include <stdio.h>

#include "assert.h"
#include "8svx.h"

#include "sample.h"
#include "channels.h"

#define YES 1
#define NO 0

short audio_up = NO, audio_down = NO;

extern int debug;

#ifdef DEBUG
#define WHERE fprintf(stderr,"%s %s %d\n",__FILE__,__FUNC__,__LINE__); fflush(stderr);
#define WHAT(x) fprintf(stderr,"%s %s %d %s\n",__FILE__,__FUNC__,__LINE__,x->name);
#else
#define WHERE
#define WHAT(x)
#endif

#define CLOCKFREQ 3579545

/* define the channel array - this is the top level of data for an audio
 channel */
Channel Channels[MAX_CHANNELS];

struct Device *AudioDevice;
struct MsgPort *sfx_port;
struct SMUS_IOAudio *openIOB;

short MasterVolume = 127;

/* allocation maps for requesting channel assignments from audio.device:
 * I use LeftMap and RightMap to insist on a stereo side - if it can't
 * allocate (at play time) that side it will blow off the note.
 * I use TryLeftMap and TryRightMap to indicate a preference for a
 * stereo side, but if I can't have a channel on that side, it'll
 * give me one from the other side if one is available there
 */
static UBYTE LeftMap[] = {LEFT0, LEFT1};
static UBYTE RightMap[] = {RIGHT0, RIGHT1};
static UBYTE TryLeftMap[] = {LEFT1, LEFT0, RIGHT1, RIGHT0};
static UBYTE TryRightMap[] = {RIGHT1, RIGHT0, LEFT1, LEFT0};

/* SMUS_IOBallocate - allocates a SMUS player IOB
 * It shouldn't panic when the allocate fails, and will be eventually
 * made to back out properly.
 * Not much to the routine at all...
 */
struct SMUS_IOAudio *SMUS_IOBallocate()
{
	struct SMUS_IOAudio *IOB;
	extern UBYTE *MyAllocMem();

	if ((IOB = (struct SMUS_IOAudio *)MyAllocMem(sizeof(struct SMUS_IOAudio), (long)MEMF_PUBLIC | MEMF_CLEAR)) == (struct SMUS_IOAudio *) NULL)
		panic("SMUS_IOBallocate failed"); 

	return(IOB);
}

/* free an IOB created by SMUS_IOBallocate */
SMUS_IOBfree(IOBp)
struct SMUS_IOAudio *IOBp;
{
	if (IOBp != NULL)
		FreeMem(IOBp, sizeof(struct SMUS_IOAudio));
}

/* Init the IOB for the oneshot portion of the sound */
SMUS_InitShotIOB(channel)
int channel;
{
	struct SMUS_IOAudio *IOB = Channels[channel].IOBs[ONESHOT_IOB];

	IOB->smusIOB.ioa_Request.io_Command = CMD_WRITE;
	IOB->smusIOB.ioa_Request.io_Flags = ADIOF_PERVOL;

	/* NEEDS WORK - needs to dynamically set volume based on volume
	 * commands we receive from the track, and the overall volume
	 * from the song structure */
	IOB->smusIOB.ioa_Volume = 127;

	/* cycles to repeat is 1, this is a oneshot sound */
	IOB->smusIOB.ioa_Cycles = 1;
}

/* Init an IOB that will stop the playing sound */
SMUS_InitStopIOB(channel)
int channel;
{
	struct SMUS_IOAudio *IOB = Channels[channel].IOBs[STOP_IOB];

	/* I switched from ADCMD_FINISH to CMD_FLUSH because I
	 * can have up to three things queued on a channel
	 * and I want to kill them all - ADCMD_FINISH would only
	 * kill the one in progress
	 * IOB->smusIOB.ioa_Request.io_Command = ADCMD_FINISH;
	 */
	IOB->smusIOB.ioa_Request.io_Command = CMD_FLUSH;
	IOB->smusIOB.ioa_Request.io_Flags = IOF_QUICK;
}


/* Init an IOB that will allocate a channel */
SMUS_InitAllocIOB(channel)
int channel;
{
	struct SMUS_IOAudio *allocIOB = Channels[channel].IOBs[ALLOC_IOB];

	static BYTE attack_precedences[] = {50, 40, 30, 20};

	allocIOB->smusIOB.ioa_Request.io_Command = ADCMD_ALLOCATE;

	allocIOB->smusIOB.ioa_Request.io_Message.mn_Node.ln_Pri = attack_precedences[channel];

	/* The IFF SMUS spec says priority is to be highest for track 1 
	 * and descending with progressively higher tracks.
	 *
	 * The audio.device doc in the ROM Kernel Manual says that
	 * higher priorities should be given for the attack portions
	 * of each note and that priorities for notes in a music
	 * program range from -50 (lowest) to 50 (highest)
	 *
	 * Consequently, since I'm orienting it toward four tracks on the
	 * Amiga, and this code is totally Amiga dependent anyway, I
	 * am assigning attack priorities of 50 for the first track, and
	 * each successive track minus 10, with the sustain portion of
	 * the note having its precedence lowered effectively by 50
	 *
	 */

	/* for now, channel 1 and 3 prefer the left channel, while 2 and 4
	 * prefer the right
	 */
	allocIOB->smusIOB.ioa_Data = (channel) ? TryLeftMap : TryRightMap;
	allocIOB->smusIOB.ioa_Length = 4;

	allocIOB->smusIOB.ioa_Request.io_Flags = (ADIOF_NOWAIT | IOF_QUICK);
}

/* Init an IOB that will deallocate the channel */
SMUS_InitDeallocIOB(channel)
int channel;
{
	struct SMUS_IOAudio *IOB = Channels[channel].IOBs[FREE_IOB];

	IOB->smusIOB.ioa_Request.io_Command = ADCMD_FREE;
	IOB->smusIOB.ioa_Request.io_Flags = IOF_QUICK;
}

/* init a channel
 *
 * this guy takes care of allocating and initting all the different IOBs 
 */
InitChannel(channelindex)
int channelindex;
{
	int i;
	struct SMUS_IOAudio *IOB;
	Channel *channelp = &Channels[channelindex];

	/* for all the IOBs,
	 *   allocate the IOB and set the channel's IOB pointer to it
	 *   set the channel pointer field that I tack onto the end
	 *     of IOBs to point back to the channel (so that we can 
	 *     tell to what channel an IOB we receive on a message 
	 *     port belongs, if we need to)
	 */
	for (i = 0; i < MAX_CHANNEL_IOBS; i++)
	{
		IOB = channelp->IOBs[i] = SMUS_IOBallocate();

		IOB->channelp = channelp;	/* set pointer to IOB's Channel parent */

		/* set replyport and device */
		/* see someday if having the reply port is really neccessary */
		IOB->smusIOB.ioa_Request.io_Message.mn_ReplyPort = sfx_port;

		/* set the device in the request to the audio device */
		IOB->smusIOB.ioa_Request.io_Device = AudioDevice;

		/* let audio.device assign the alloc keys first time through */
		IOB->smusIOB.ioa_AllocKey = 0;
	}

	/* initialize the different kinds of IOBs we use */
	SMUS_InitShotIOB(channelindex);
	SMUS_InitStopIOB(channelindex);
	SMUS_InitAllocIOB(channelindex);
	SMUS_InitDeallocIOB(channelindex);

	channelp->samptr = NULL;
}

InitAudio()
{
	int EndAudio();
	int i;

	openIOB = NULL;
	AudioDevice = NULL;

	add_cleanup(EndAudio);

	openIOB = SMUS_IOBallocate();

	if (OpenDevice(AUDIONAME, 0, &openIOB->smusIOB, 0) != 0)
		panic("can't open audio device");

	AudioDevice = openIOB->smusIOB.ioa_Request.io_Device;

	if ((sfx_port = CreatePort((char *)NULL, 0)) == (struct MsgPort *)NULL)
		panic("can't create sfx_port");

	for (i = 0; i < MAX_CHANNELS; i++)
		InitChannel(i);

	audio_up = YES;
}

EndAudio()
{
	int i, j;
	Channel *channelp;

#ifdef DEBUG
	fprintf(stderr,"endaudio cleanup routine executing\n");
#endif
	if(audio_down)
		return;
	audio_down = YES;

	if (audio_up)
	{
		for (i = 0; i < MAX_CHANNELS; i++)
			StopChannelNote(&Channels[i]);
	}
	audio_up = NO;

	if (sfx_port)
		DeletePort(sfx_port,sizeof(struct MsgPort));

	for (i = 0; i < MAX_CHANNELS; i++)
	{
		channelp = &Channels[i];
		for (j = 0; j < MAX_CHANNEL_IOBS; j++) 
		{
			SMUS_IOBfree(channelp->IOBs[j]);
			channelp->IOBs[j] = NULL;
		}
	}
	if(openIOB) 
	{
		if(AudioDevice) 
		{
			CloseDevice(openIOB);
			AudioDevice = NULL;
		}
		SMUS_IOBfree(openIOB);
		openIOB = NULL;
	}
#ifdef DEBUG
	fprintf(stderr,"endaudio cleanup routine complete\n");
#endif
}

/* the guts of note playing, right here */

PlayChannelSample(samptr,velocity,stereo_side)
Sample *samptr;
int velocity;
int stereo_side;
{
	int i;
	struct SMUS_IOAudio *iobptr;
	Channel *channelp;
	struct OctaveStruct *octave_ptr;
	int octave, noctaves;
	short shift_up_n_octaves = 0;
	short shift_down_n_octaves = 0;
	short period;
	short volume;
	int channel = -1;
	struct SMUS_IOAudio *allocIOB;
	struct SMUS_IOAudio *shotIOB;

	/* make relatively certain samptr points to a good sample */
	ASSERT_SAMPLE_MAGIC(samptr);

	/* gimme one of my logical channels */
	for (channel = 0; channel < 4; channel++)
	{
		channelp = &Channels[channel];
		if (!(channelp->flags & (LOOP_PLAYING | SHOT_PLAYING)))
			goto got_channel;
	}

#ifdef DEBUG
		printf("failed to get a channel, they're all in use\n");
#endif
	return;

	got_channel:
	/* cons up some convenient pointers */
	/* these assignments could be moved to where they're needed to prevent
	 * their being assigned but unused because of alloc failure,
	 * no need for oneshot, etc. */
	allocIOB = channelp->IOBs[ALLOC_IOB];
	shotIOB = channelp->IOBs[ONESHOT_IOB];


	/* zero the alloc key so we'll get a fresh one - it's the only way
	 * to be sure we won't conflict with someone else */
	/* allocIOB->smusIOB.ioa_AllocKey = 0; */
	/* I think we're OK if we alloc it once */
	allocIOB->smusIOB.ioa_Data = (stereo_side) ? TryLeftMap : TryRightMap;
	BeginIO(&allocIOB->smusIOB);

	/* the only possible error return is ADIOERR_NOALLOCATION */
	if (allocIOB->smusIOB.ioa_Request.io_Error)
	{
		fprintf(stderr,"alloc failed on channel %d\n",channel);
		return;
	}
#ifdef DEBUG
	fprintf(stderr,"channel %d allockey %d\n",channel,allocIOB->smusIOB.ioa_AllocKey);
#endif

	channelp->samptr = samptr;

	/* copy the Unit and AllocKey into the other IOBs 
	 * This is not the coolest way to go.
	 * It doesn't need to copy it to all of them.
	 * Perhaps it should only copy it into the channel and
	 * the other routines would get it out of the channel and
	 * shove it into the IOB - I don't know, maybe later
	 * also, AllocKey need only be propogated once
	 */
	for (i = 0; i < MAX_CHANNEL_IOBS; i++)
	{
		iobptr = Channels[channel].IOBs[i];
		iobptr->smusIOB.ioa_Request.io_Unit = allocIOB->smusIOB.ioa_Request.io_Unit;
		iobptr->smusIOB.ioa_AllocKey = allocIOB->smusIOB.ioa_AllocKey;
	}


	assert(samptr->sampleheader.samplesPerSec != 0);
	shotIOB->smusIOB.ioa_Period = (CLOCKFREQ / samptr->sampleheader.samplesPerSec);

	if (make_sample_chip(samptr))
	{
		shotIOB->smusIOB.ioa_Data = (UBYTE *)samptr->sampledata;
		shotIOB->smusIOB.ioa_Length = (ULONG)samptr->samplebytes;
		shotIOB->smusIOB.ioa_Volume = 64;
		shotIOB->smusIOB.ioa_Cycles = 1;

		BeginIO(&shotIOB->smusIOB);
		/* possible errors are IOERR_ABORTED and ADIOERR_NOALLOCATION */
		if (shotIOB->smusIOB.ioa_Request.io_Error)
			return;
		channelp->flags |= SHOT_PLAYING;
#ifdef DEBUG
		fprintf(stderr,"queued shot on channel %d\n",channel);
#endif
	}
	else
	{
		fprintf(stderr,"chatterbox: chip RAM allocation failed\n");
	}
}

/* stop playing the channel if it's playing (we may not have been able to
 * allocate a channel you know) and deallocate it 
 */
/* memo to self:  you should always stop all notes that stop in a 
 * certain time interval prior to starting any new ones 
 */
StopChannelNote(channelp)
Channel *channelp;
{

	struct SMUS_IOAudio *stopIOB = channelp->IOBs[STOP_IOB];
	struct SMUS_IOAudio *freeIOB= channelp->IOBs[FREE_IOB];

	if (channelp->flags & (LOOP_PLAYING | SHOT_PLAYING))
	{
		BeginIO(&stopIOB->smusIOB);
		/* only error return is ADIOERR_NOALLOCATION */

		if (channelp->flags & SHOT_PLAYING)
		{
#ifdef DEBUG
			fprintf(stderr,"SHOT playing\n");
#endif
			WaitIO(&channelp->IOBs[ONESHOT_IOB]->smusIOB);
		}
		if (channelp->flags & LOOP_PLAYING)
		{
#ifdef DEBUG
			fprintf(stderr,"LOOP wait\n");
#endif
			WaitIO(&channelp->IOBs[LOOP_IOB]->smusIOB);
		/* error returns can be IOERR_ABORTED and ADIOERR_NOALLOCATION */
		}

		/* there's no point in waiting on precedence, it's synchronous */
		channelp->flags &= ~(SHOT_PLAYING | LOOP_PLAYING);

#ifdef DEBUG
	fprintf(stderr,"free channel %d, alloc key %d\n",channel,freeIOB->smusIOB.ioa_AllocKey);
#endif
		BeginIO(&freeIOB->smusIOB);
		/* only possible error return is ADIOERR_NOALLOCATION */
#ifndef NOAUDIT
		if (freeIOB->smusIOB.ioa_Request.io_Error)
			fprintf(stderr,"dealloc i/o error\n");
#endif

		free_sample_chip(channelp->samptr);
	}
}
/* I have forgone magic numbers on channel structures because of the static
 * nature of the channels and their small number (4)
 */
struct SMUS_IOAudio *SMUS_GetMsg()
{
	return((struct SMUS_IOAudio *)GetMsg(sfx_port));
}

/* end of player.c */
