/************************************************
 *												*
 *	 		 Music.c - A music driver			*
 *												*
 *	  Started 27-Jan-89 by Ken Jordan for		*
 *												*
 *				  //  /   / / /					*
 *			 	 MicroIllusions					*
 *				/  / /   / //  tm				*
 *												*
 *	  Copyright 1990 MicroIllusions, Inc.		*
 ************************************************/

/* Note: Tab size = 4 for this file	*/

/* ===== #include directives ======	*/

/* #include <stdio.h> */
#include <exec/types.h>
#include <exec/memory.h>
#include <exec/errors.h>
#include <exec/interrupts.h>
#include <exec/execbase.h>
#include <hardware/custom.h>
#include <hardware/intbits.h>
#include <devices/audio.h>
#include <libraries/dos.h>

#define __NO_PRAGMAS
#include <functions.h>

#include "macros.h"				/* Dave's handy C macros		*/
#include "music.h"

/* ====== #define directives ======	 */

/* #define DEBUG					/* #define for kprintf messages */

/* #define COLORFLASH(h)	(*(WORD *)0x00dff180 = h) */
#define COLORFLASH(h)

/* #define PLAYSAMPLE				/* #define for PlaySample() function */
#define SETVOLUME				/* #define for SetVolume() function */
/* #define SEPERATE					/* #define if LoadSeg'able			*/
/* #define SOUNDFX					/* #define for music & sample player */

#define MakeClrPubArray(x,n)	(x = AllocMem((LONG) n*(sizeof *x), MEMF_CLEAR|MEMF_PUBLIC))
#define MakeClrPubStruct(x)		(x = AllocMem((LONG) sizeof (*x), MEMF_CLEAR|MEMF_PUBLIC))
#define BREAK					goto leave

#define read(addr, size)		if (Read(infh, (void *)addr, (LONG)(size)) != size) BREAK
#define write(addr, size)		if (Write(outfh, (void *)addr, (LONG)(size)) != size) BREAK
#define skip(size)				if (Seek(infh, (LONG)(size), 0L) == -1) BREAK

#define NUMAUDIOIOB	 	32				/* size of IOB pool				*/
#define NUMPLAYMSG		32				/* size of message pool			*/

#define NUMCHAN			4				/* number of Amiga voices		*/
#define NUMMIDICHAN	 	16				/* number of midi channels		*/

#define MAXAMIGASAMPLES	32				/* maximum # Amiga samples		*/

#define MAXVOL			64				/* maximum Amiga volume			*/

#define PERIOD_THRESH 288				/* sample period threshold		*/
#define PERIOD_ENTRIES ((288 * 3)+11)	/* size of period table			*/

#define ENV_SUST		1				/* envelope sustained			*/
#define ENV_HALT		2				/* envelope halted				*/
#define ENV_STOPPED	 	4				/* envelope stopped				*/
#define ENV_RELEASE	 	8				/* envelope waiting for release */

#define MSEC_PER_VBLANK 0x10aaabL		/* 16.6667 (fixed point)		*/

#define ATICKS_PER_SEC	3579546			/* Color clocks per sec (Dave's)*/

#define CHIPRAM_THRESH	0x1800L			/* minimum ram left for system	*/

/* ==== external declarations ==== */

extern void			HandleNoteInt();	/* assembly interrupt handler		*/
extern void			MusicTickInt();		/* assembly interrupt server		*/
extern struct MsgPort	*CreatePrivatePort(); /* create PA_IGNORE port		*/
extern void			DeletePrivatePort(); /* delete a PA_IGNORE port			*/
extern WORD			period_table[];		/* midi->period conversion table	*/
extern WORD			EnvelopeStart();	/* setup envelope generator			*/
extern void			TestNote();

extern void			HandleNote();		/* C func called from interrupt		*/

/* ====== global definitions ======	 */

struct MsgPort	*playport	= NULL;		/* port to send playmsgs			*/
struct MsgPort	*poolport	= NULL; 	/* port to keep unused IOAudio's	*/
struct MsgPort	*doneport	= NULL; 	/* port for unused playmsgs			*/
struct MsgPort	*ctrlport	= NULL; 	/* port for control IOB				*/

struct Device	*audiodev	= NULL;		/* ptr to audio device struct		*/
struct IOAudio	*audioctrliob	= NULL; /* ptr to control audio IOB			*/
struct IOAudio	*stopaudioiob	= NULL; /* ptr to stop audio IOB			*/

struct IOAudio	*audioiob		= NULL; /* ptr to array of audio IOB's		*/
struct playmsg	*playmsgs	= NULL;		/* ptr to array of play messages	*/

struct sample	*amiga_samples[MAXAMIGASAMPLES],	/* ptrs to samples */
				*sample_bank[16];		/* ptrs to current MIDI "bank" */

struct sequence sequence;				/* data for sequence (song)			*/

struct channel	channeldata[NUMCHAN],	/* current data for each channel	*/
				*sfxchannel;

WORD			tickserverflag = FALSE;	/* TRUE if tick server int added	*/

WORD			playflag;				/* if TRUE then ints are playing	*/

ULONG			clockinc;				/* $XXXX.XXXX fixed point clock inc	*/
ULONG			clockval;				/* clock counter ($XXXX.XXXX)		*/
ULONG			clocksfx;				/* sound effect clock				*/
LONG			frameval;				/* framerate						*/

struct Task		*sync_task;				/* music synchronization code		*/
ULONG			sync_bit;
UBYTE			sync_val,
				rep_val;

ULONG			clockrep;				/* clock counter at repeat			*/

struct event	*sfxeventptr,			/* play a note as a special effect	*/
				*repeventptr,			/* repeat loop point				*/
				*loopeventptr,			/* loop to this point at END		*/
				*playeventptr;			/* ptr to next event to start		*/

struct stopevent *stopeventptr;			/* ptr to next event to stop		*/

struct stopevent stopevents[32];		/* up to 32 events pending at once	*/

struct Interrupt handlenoteint =		/* start/stop note soft interrupt	*/
{
	NULL, NULL,							/* node pred, node succ	*/
	0,									/* Pri					*/
	NT_INTERRUPT,						/* Type					*/
	NULL,								/* Name					*/
	NULL,								/* Data					*/
	&HandleNoteInt						/* Code					*/
};

struct Interrupt musictickint =			/* music tick interrupt (vblank)	*/
{
	NULL, NULL,							/* node pred, node succ		*/
	1,									/* Pri (before crowd at 0)	*/
	NT_INTERRUPT,						/* Type						*/
	NULL,								/* Name						*/
	NULL,								/* Data						*/
	&MusicTickInt						/* Code						*/
};

#define DEFAULT_ENV_SIZE	2			/* # segs in default env.		*/

struct envseg	std_envelope[] =		/* standard (default) envelope	*/
{
	0L,	0, MAXVOL	, ENV_SUST,			/* go to max & sustain	*/
	0L, 60, MAXVOL/2, ENV_HALT,			/* fade to silence		*/
};

#define FULL_VOLUME		16

UWORD	volume_divide = FULL_VOLUME;	/* volume scale value (0-16)	*/

#define ALL_CHANNELS	0x0f

UBYTE	avail_channels = ALL_CHANNELS;	/* mask to allow locking channels */

/* ===== function definitions =====	 */

/************ Utility Functions *************/

struct MsgPort *CreatePrivatePort(void)
{
	register struct MsgPort *mp;

	if ((mp = AllocMem((long)sizeof(*mp), MEMF_PUBLIC|MEMF_CLEAR)) == 0) {
		return(0);
	}
	mp->mp_Node.ln_Type = NT_MSGPORT;
	NewList(&mp->mp_MsgList);
	return(mp);
}

void DeletePrivatePort(struct MsgPort *mp)
{
	if (mp) FreeMem(mp, (long)sizeof(*mp));
}

/****************************************************/
/*													*/
/* OpenMusic() - Open audio/Setup msgports			*/
/*													*/
/* Entry: None										*/
/* Exit : Returns success code (TRUE or FALSE)		*/
/*													*/
/****************************************************/

WORD OpenMusic(void)
{	WORD	i;
	LONG	error;
	UBYTE	allocdata;

	if (audiodev != NULL) return TRUE;

	for (i = 0; i < NUMCHAN; i++)
	{
		channeldata[i].envelope.status = ENV_STOPPED;
		channeldata[i].priority = 0;
	}

	allocdata = 0x0F;		/* we want all four channels	*/

	audioctrliob->ioa_Request.io_Message.mn_Node.ln_Pri = 127;
	audioctrliob->ioa_Request.io_Message.mn_ReplyPort	= ctrlport;
	audioctrliob->ioa_Data	= &allocdata;
	audioctrliob->ioa_Length = 1;

#ifdef DEBUG
	errmsg = "Music: Can't open audio.device!\n";
#endif

	if ((error = OpenDevice("audio.device", 0L,& audioctrliob->ioa_Request, 0L)) != NULL)
		BREAK;

	audiodev = audioctrliob->ioa_Request.io_Device; /* get device address	*/

/* setup stopaudioiob for copying to audio IOB's	*/

	stopaudioiob->ioa_Request.io_Message.mn_ReplyPort = poolport;
	stopaudioiob->ioa_Request.io_Device	= audiodev;
	stopaudioiob->ioa_Request.io_Unit	= NULL;
	stopaudioiob->ioa_Request.io_Command = CMD_FLUSH;
	stopaudioiob->ioa_Request.io_Flags	= IOF_QUICK;
	stopaudioiob->ioa_Request.io_Error	= 0;
	stopaudioiob->ioa_AllocKey		= audioctrliob->ioa_AllocKey;

/* setup message stuff							*/

	playport->mp_Flags	= PA_SOFTINT;
	playport->mp_SigTask	= (struct Task *) &handlenoteint;

/* clear port lists in case this is a resume */

	NewList(&playport->mp_MsgList);
	NewList(&poolport->mp_MsgList);
	NewList(&doneport->mp_MsgList);

/* setup audioiobs								*/

	for (i = 0; i < NUMAUDIOIOB; i++)
	{
		audioiob[i] = *stopaudioiob;	/* copy structure		*/

		audioiob[i].ioa_Request.io_Command = CMD_WRITE;
		audioiob[i].ioa_Request.io_Flags	= ADIOF_PERVOL;

		ReplyMsg(&audioiob[i].ioa_Request.io_Message);	/* send them to the poolport	*/
	}

	for (i = 0; i < NUMPLAYMSG; i++)
	{
		playmsgs[i].message.mn_Node.ln_Type	 = NT_MESSAGE;
		playmsgs[i].message.mn_ReplyPort	= doneport;

		ReplyMsg(&playmsgs[i].message);		/* send them to the doneport	*/
	}

	playflag = 0;
	return TRUE;

leave:
	return FALSE;
}

/****************************************************/
/*													*/
/* InitMusic() - Init the Music player stuff		*/
/*				 once in the morning				*/
/*													*/
/* Entry: None										*/
/* Exit : Returns success code (TRUE or FALSE)		*/
/*													*/
/****************************************************/

WORD InitMusic(void)
{	extern struct ExecBase *SysBase;
	LONG	error;

#ifdef DEBUG
	char	*errmsg;

	errmsg = "Music: Out of memory!\n";
#endif

/* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */

#if SEPERATE
	SysBase = *(struct Library **)4;
	DOSBase = OpenLibrary("dos.library",0L);
#endif

	if (!MakeClrPubArray(audioiob, NUMAUDIOIOB))
		BREAK;

	if (!MakeClrPubArray(playmsgs, NUMPLAYMSG))
		BREAK;

	if (!MakeClrPubStruct(audioctrliob))
		BREAK;

	if (!MakeClrPubStruct(stopaudioiob))
		BREAK;

#ifdef DEBUG
	errmsg = "Music: Can't create ports!\n";
#endif

	if ((ctrlport = CreatePrivatePort()) == NULL)
		BREAK;

	if ((playport = CreatePrivatePort()) == NULL)
		BREAK;

	if ((poolport	= CreatePrivatePort()) == NULL)
		BREAK;

	if ((doneport	= CreatePrivatePort()) == NULL)
		BREAK;

	if (!OpenMusic()) BREAK;

/* add the tick server (in vblank)				*/

	AddIntServer(INTB_VERTB, &musictickint);

	tickserverflag	= TRUE;			/* remember...				*/

	frameval = 60 * SysBase->VBlankFrequency;	/* prefetch */

	return TRUE;

leave:
	FreeMusic();
	return FALSE;
}

/****************************************************/
/*													*/
/* FreeMusic() - cleanup all music driver sruff		*/
/*													*/
/* Entry: None										*/
/* Exit : None										*/
/*													*/
/* Note : Call with impunity, it checks for NULLs	*/
/*													*/
/****************************************************/

void FreeMusic(void)
{
	WORD	i;

	if (doneport) FreePlayXSong();

	if (tickserverflag)
	{
		RemIntServer(INTB_VERTB, &musictickint);
		tickserverflag	= FALSE;
	}

	if (audiodev)
	{
		CloseDevice(&audioctrliob->ioa_Request);
		audiodev = NULL;
	}

	if (doneport)
	{
		DeletePrivatePort(doneport);
		doneport = NULL;
	}

	if (poolport)
	{
		DeletePrivatePort(poolport);
		poolport = NULL;
	}

	if (playport)
	{
		DeletePrivatePort(playport);
		playport = NULL;
	}

	if (ctrlport)
	{
		DeletePrivatePort(ctrlport);
		ctrlport = NULL;
	}

	DeleteStruct(stopaudioiob);	/* macro sets ptr to NULL		*/

	DeleteStruct(audioctrliob);

	DeleteStructArray(playmsgs, NUMPLAYMSG);

	DeleteStructArray(audioiob, NUMAUDIOIOB);

#if SEPERATE
	CloseLibrary(DOSBase);
#endif
}

/****************************************************/
/*													*/
/* HandleNote() - C interrupt code, called from		*/
/*				  assembly interrupt handler.		*/
/*													*/
/* Exit : None										*/
/*													*/
/****************************************************/

void HandleNote(void)
{
	register struct IOAudio *attackiob;		/* AudioIOB for attack			*/
	register struct IOAudio *sustainiob;	/* AudioIOB for sustain			*/
	register struct channel *channelinfo;	/* ptr to channel data			*/
	register struct playmsg *pm;			/* play message ptr				*/
	struct sample		*sampptr;			/* ptr to a sample				*/
	UBYTE				*waveform;			/* ptr to waveform data			*/
	LONG				attack_length;		/* size of attack portion		*/
	LONG				sustain_length; 	/* size of the sustain portion	*/
	WORD				channelnum;			/* audio channel to use			*/
	WORD				period_index;		/* index into period table		*/
	WORD				octave_shift;		/* number of octaves to shift	*/
	WORD				period;				/* period for correct pitch		*/
	UBYTE				midipitch;			/* midi pitch number			*/
	UBYTE				midichannel;		/* midi channel number			*/
	UBYTE				pri;				/* note priority				*/
	BOOL				sfx;				/* flag if special effect		*/

/* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */

/* if there are any messages then process them, else exit	*/

	while (pm = (struct playmsg *) GetMsg(playport))
	{
		sampptr		= pm->sample;			/* copy sample struct ptr	*/
		channelnum	= pm->audio_channel;	/* copy audio channel number*/
		midipitch	= pm->midi_note;		/* copy midi pitch			*/
		midichannel = pm->midi_channel;		/* copy midi channel		*/
		pri			= pm->priority;

		ReplyMsg(&pm->message);					/* reply to message		*/

		channelinfo = &channeldata[channelnum]; /* get ptr to channel data	*/

	/*		silence any note that may be on this channel	*/

		channelinfo->envelope.status = ENV_STOPPED;	/* stop envelope */
		channelinfo->priority = 0;

		stopaudioiob->ioa_Request.io_Unit	= (void *) (1L << channelnum);
		stopaudioiob->ioa_Request.io_Flags = IOF_QUICK;

		BeginIO(&stopaudioiob->ioa_Request);	/* send CMD_FLUSH command	*/
		COLORFLASH(0x0f00);

	/* if no sample then just continue, else start a new note		*/

		if (!sampptr) continue;

	/* calculate the audio period based on the midi pitch & the sample		*/

		period_index = (midipitch - 45L) * 24L;

		octave_shift = 0;

		period_index += sampptr->tune;		/* tweak (tune) period	*/

		while (period_index < PERIOD_THRESH && sampptr->next_sample)	/* if below threshold && another sample */
		{
			sampptr = sampptr->next_sample;	/* use next sample	*/
			period_index += (12*24);		/* adjust period		*/
		}

/**/		while (period_index < 0)			/* if below zero		*/
		{
			octave_shift++;					/* double the period	*/
			period_index += (12*24);		/* use next octave		*/
		}

		if (period_index >= PERIOD_ENTRIES)	/* if note is unplayable*/
			continue;

	/* setup parameters for the sample		*/

		attack_length	= sampptr->attack_size;
		sustain_length	= sampptr->sustain_size;
		waveform		= sampptr->waveform_data;
		period			= period_table[period_index] << octave_shift;

	/* update info in channeldata			*/

		channelinfo->current_sample = sampptr;
		channelinfo->midi_note		= midipitch;
		channelinfo->midi_channel	= midichannel;
		channelinfo->octave_shift	= octave_shift;
		channelinfo->period_entry	= period_index;
		channelinfo->priority		= pri & 0x7f;
		if (pri & 0x80) sfxchannel = channelinfo;

	/* send commands to audio device			*/

		if (attack_length)	/* if there is an attack segment	*/
		{
			if ((attackiob = (struct IOAudio *)GetMsg(poolport)))	/* if IOB available	 */
			{
				attackiob->ioa_Request.io_Flags = ADIOF_PERVOL | IOF_QUICK;
				attackiob->ioa_Request.io_Error = 0;
				attackiob->ioa_Cycles			= 1;
				attackiob->ioa_Volume			=
					(EnvelopeStart(channelinfo, sampptr->envelope_data) * volume_divide) / FULL_VOLUME;
				attackiob->ioa_Request.io_Unit	= (void *) (1L << channelnum);
				attackiob->ioa_Period			= period;
				attackiob->ioa_Data				= waveform;
				attackiob->ioa_Length			= attack_length;

				BeginIO(&attackiob->ioa_Request);
				COLORFLASH(0x00f0);

				if (attackiob->ioa_Request.io_Error)
				{
#ifdef DEBUG
					kprintf("Music: error from audio.device!\n");
#endif
					continue;
				}
			}
#ifdef DEBUG
			else
			{
				kprintf("Music: couldn't get attackiob!\n");
			}
#endif
		}

	/* if no sustain then we are done		*/

		if (!sustain_length) continue;

		if ((sustainiob = (struct IOAudio *)GetMsg(poolport)))
		{
			sustainiob->ioa_Request.io_Flags = IOF_QUICK;
			sustainiob->ioa_Request.io_Error = 0;
			sustainiob->ioa_Cycles			 = 0;	/* forever		*/
			sustainiob->ioa_Request.io_Unit	 = (void *) (1L << channelnum);
			sustainiob->ioa_Data		 	 = waveform + attack_length;
			sustainiob->ioa_Length		 	 = sustain_length;

			if (!attack_length)	/* if no attack, also PERVOL */
			{
				sustainiob->ioa_Request.io_Flags = ADIOF_PERVOL | IOF_QUICK;
				sustainiob->ioa_Volume		 	 =
					(EnvelopeStart(channelinfo, sampptr->envelope_data) * volume_divide) / FULL_VOLUME;
				sustainiob->ioa_Period		 	 = period;
				/* if (pri & 0x80) channelinfo->envelope.status = ENV_RELEASE; */
			}

			BeginIO(&sustainiob->ioa_Request);
			if (!attack_length) COLORFLASH(0x00f0);

			if (sustainiob->ioa_Request.io_Error)
			{
#ifdef DEBUG
				kprintf("Music: error from audio.device!!\n");
#endif
				continue;
			}
		}
#ifdef DEBUG
		else
		{
			kprintf("Music: couldn't get sustainiob!\n");
		}
#endif
	}
}

#if PLAYSAMPLE
/****************************************************/
/*													*/
/* PlaySample() - Play a sample (sound effect)		*/
/*													*/
/*													*/
/* Entry: sample number, channel number				*/
/* Exit : success code (TRUE = played OK)			*/
/*													*/
/****************************************************/

static WORD ps_open = 0;

WORD PlaySample(int samplenum,int channel)
{
	struct playmsg *pm;

	if (audiodev == NULL)
	{	OpenMusic();
		if (audiodev == NULL) return FALSE;
		ps_open = 1;
	}

	if (StopSample(channel) == FALSE) return FALSE;

	if (pm = (struct playmsg *) GetMsg(doneport))
	{
		pm->sample = amiga_samples[samplenum];	/* copy sample struct ptr	*/
		pm->audio_channel = channel;			/* copy audio channel number*/
		pm->midi_note = 60;						/* play "normally" (Mid-C)	*/
		pm->midi_channel = samplenum;			/* copy midi channel		*/
		pm->priority = 0;

		PutMsg(playport, &pm->message);
		return TRUE;
	}
	return FALSE;
}

/* if audiodev != NULL, the sample will end on its own */

void EndSample(channel)
{
	if (audiodev != NULL) StopSample(channel);

	if (ps_open == 1)
	{
		CloseDevice(&audioctrliob->ioa_Request);
		audiodev = NULL;
		ps_open = 0;
	}
}
#endif

#if SOUNDFX

/****************************************************/
/*													*/
/* SetChannels() - reserve channels					*/
/*													*/
/*													*/
/* Entry: mask of channels to enable				*/
/* Exit : None										*/
/*													*/
/****************************************************/

void SetChannels(WORD mask)
{
	WORD	i;
	BYTE	old = avail_channels;

	avail_channels = mask;

	for (i=0;i<4;i++)
	{
		if (!(mask & 1 << i) && (old & 1 << i)) StopSample(i);
	}
}

/****************************************************/
/*													*/
/* PlaySample() - Play a sample (sound effect)		*/
/*													*/
/*													*/
/* Entry: sample number, channel number				*/
/* Exit : success code (TRUE = played OK)			*/
/*													*/
/****************************************************/

WORD PlaySample(int samplenum,int channel)
{
	struct playmsg *pm;

	if (audiodev == NULL)
	{	OpenMusic();
		if (audiodev == NULL) return FALSE;
	}

	if (StopSample(channel) == FALSE) return FALSE;

			if ((attackiob = (struct IOAudio *)GetMsg(poolport)))	/* if IOB available	 */
			{
				attackiob->ioa_Request.io_Flags = ADIOF_PERVOL | IOF_QUICK;
				attackiob->ioa_Request.io_Error = 0;
				attackiob->ioa_Cycles			= 1;
				attackiob->ioa_Volume			=
					(EnvelopeStart(channelinfo, sampptr->envelope_data) * volume_divide) / FULL_VOLUME;
				attackiob->ioa_Request.io_Unit	= (void *) (1L << channelnum);
				attackiob->ioa_Period			= period;
				attackiob->ioa_Data				= waveform;
				attackiob->ioa_Length			= attack_length;

				BeginIO(&attackiob->ioa_Request);

	if (pm = (struct playmsg *) GetMsg(doneport))
	{
		pm->sample = amiga_samples[samplenum];	/* copy sample struct ptr	*/
		pm->audio_channel = channel;			/* copy audio channel number*/
		pm->midi_note = 60;						/* play "normally" (Mid-C)	*/
		pm->midi_channel = samplenum;			/* copy midi channel		*/
		pm->priority = 0;

		PutMsg(playport, &pm->message);
		return TRUE;
	}
	return FALSE;
}

/* if audiodev != NULL, the sample will end on its own */

void EndSample(channel)
{
	if (audiodev != NULL) StopSample(channel);
}
#endif

/****************************************************/
/*													*/
/* PlayNote() - Play a sample as a note				*/
/*				Music system must be on				*/
/*													*/
/* Entry: channel #, note #							*/
/* Exit : success code (TRUE = played OK)			*/
/*													*/
/****************************************************/

WORD PlayNote(UWORD channel,UWORD note,UWORD dur)
{
	static struct event sfx;

	if (clocksfx) return FALSE;

	if (OpenMusic() == FALSE) return FALSE;

	sfx.starttime = 0;
	sfx.stoptime = dur;
	sfx.command = note;
	sfx.data = ~channel;

	sfxeventptr = &sfx;

	return TRUE;
}

/****************************************************/
/*													*/
/* StopSample() - Stop playing a note				*/
/*													*/
/*													*/
/* Entry: channel number							*/
/* Exit : success code (TRUE = stopped OK)			*/
/*													*/
/****************************************************/

WORD StopSample(int channel)
{
	struct playmsg *pm;

	if (pm = (struct playmsg *) GetMsg(doneport))
	{
		pm->sample = NULL;
		pm->audio_channel = channel;
		pm->priority = 0;

		PutMsg(playport, &pm->message);
		return TRUE;
	}
	else return FALSE;
}

/****************************************************/
/*													*/
/* ResetSamples() - Reset sample banks				*/
/*													*/
/*													*/
/* Entry: None										*/
/* Exit : None										*/
/*													*/
/****************************************************/

void ResetSamples(void)
{	WORD i;

	for (i=0;i<16;i++) sample_bank[i] = amiga_samples[i];
}

/****************************************************/
/*													*/
/* PlaySong() - Play a loaded song (in interrupts)	*/
/*													*/
/* Entry: Song Number (END/LABEL delimited)			*/
/* Exit : None										*/
/* Note : Song will be playing in interrupts after	*/
/*	      this function.  Use StopSong() to stop.	*/
/*													*/
/****************************************************/

struct event *PlaySong(WORD which)
{	struct event *begin;

	OpenMusic();

	StopMusic();

	clockval	= 0;					/* reset clockval */

	SetTempo((ULONG) sequence.tempo);
	volume_divide = FULL_VOLUME;

	stopeventptr = &stopevents[0];		/* init pointer to bogus event	*/

	stopevents[0].command  = 0;
	stopevents[0].data	   = 0;
	stopevents[0].stoptime = 0xFFFF;	/* bogus event always last		*/

	loopeventptr = NULL;
	begin = playeventptr = sequence.events;		/* start at first event */

		/* skip past <which> LABEL/END's */
	while (which--)
	{	while (playeventptr->command != END)
		{	if (playeventptr->command == SYNC &&
				playeventptr->data == 0x03) break;	/* song label */
			playeventptr++;
		}
		if (playeventptr->command == END) begin = playeventptr + 1;
		playeventptr++;
		clockval = playeventptr->starttime << 16;	/* set clockval */
	}

	ResetSamples();
	playflag = TRUE;

	return begin;
}

/****************************************************/
/*													*/
/* LoopSong() - Play a loaded song (in interrupts)	*/
/*				with looping						*/
/* Entry: Song Number (LABEL/END delimited)			*/
/* Exit : None										*/
/* Note : Song will be playing in interrupts after	*/
/*	      this function.  Use StopSong() to stop.	*/
/*													*/
/****************************************************/

void LoopSong(WORD which)
{
	loopeventptr = PlaySong(which);
}

void ContinueSong(void)
{
	OpenMusic();

	StopMusic();

	/* DON't reset clockval */

	stopeventptr = &stopevents[0];		/* init pointer to bogus event	*/
	stopevents[0].command  = 0;
	stopevents[0].data	   = 0;
	stopevents[0].stoptime = 0xFFFF;	/* bogus event always last		*/

	playflag	= TRUE;
}

/****************************************************/
/*													*/
/* AdvanceSong() - Advance by n labels				*/
/*													*/
/* Entry: Number of songs (LABEL delimited)			*/
/* Exit : None										*/
/* Note : Only works for first END delimited		*/
/*        sequence									*/
/****************************************************/

void AdvanceSong(WORD which)
{
	if (playflag == FALSE) return;

	StopMusic();

	SetTempo((ULONG) sequence.tempo);

	stopeventptr = &stopevents[0];		/* init pointer to bogus event	*/
	stopevents[0].command  = 0;
	stopevents[0].data	   = 0;
	stopevents[0].stoptime = 0xFFFF;	/* bogus event always last		*/

	while (which--)
	{	while (playeventptr->command != END)
		{	if (playeventptr->command == SYNC &&
				playeventptr->data == 0x03) break;	/* song label */
			playeventptr++;
		}
		if (playeventptr->command == END)
		{	clockval = 0;
			playeventptr = sequence.events;
		}
		else
		{	playeventptr++;
			clockval = playeventptr->starttime << 16;	/* set clockval */
		}
	}

	ResetSamples();
	playflag = TRUE;
}

/****************************************************/
/*													*/
/* SyncMusic() - start or stop music sync			*/
/*													*/
/* Entry: task,sigbit								*/
/* Exit : None										*/
/*													*/
/* Note : Call with task = NULL to stop	sync'ing	*/
/*													*/
/****************************************************/

void SyncMusic(struct Task *task,UBYTE sigbit)
{	if (sync_task = task)
	{	sync_bit = 1 << sigbit;
		sync_val = 0;
	}
}

/****************************************************/
/*                                                  */
/* StopMusic() - Stop the music						*/
/*                                                  */
/* Entry: None										*/
/* Exit : None										*/
/*                                                  */
/****************************************************/

void StopMusic(void)
{
	UWORD i;

	playflag = FALSE;

	for (i = 0; i < 4; i++)
	{
		StopSample(i);
	}
}

/****************************************************/
/*                                                  */
/* SuspendMusic() - Stop the music/Release audio	*/
/*                                                  */
/* Entry: None										*/
/* Exit : None										*/
/*                                                  */
/****************************************************/

void SuspendMusic(void)
{
	if (audiodev)
	{
		StopMusic();

		CloseDevice(&audioctrliob->ioa_Request);
		audiodev = NULL;
	}
}

#ifdef SETVOLUME
/****************************************************/
/*                                                  */
/* SetVolume() - Set global volume					*/
/*                                                  */
/* Entry: new volume (0-16)							*/
/* Exit : None										*/
/*                                                  */
/****************************************************/

void SetVolume(WORD vol)
{
	if (vol < 0) vol = 0;
	if (vol > 16) vol = 16;
	volume_divide = vol;
}
#endif

FILE			infh;

/****************************************************/
/*													*/
/* LoadPlayXSong() - Load what player needs to		*/
/*		 			 play a song, in cryptic form	*/
/*													*/
/* Entry: file name,                                */
/*	      option (0 = all, 1 = seq, 2 = samp)       */
/* Exit : success code (TRUE = saved OK)			*/
/*													*/
/****************************************************/

BOOL LoadPlayXSong(char	*name,LONG options)
{
	WORD			i;
	LONG			longid;
	struct sample	*sp, *np;
	struct sequence	tempseq;

/* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */

	StopMusic();

	switch (options)
	{
		case 0:
			FreePlayXSong();
			break;
		case 1:
			FreePlayXSeq();
			break;
		case 2:
			FreePlayXSamp();
			break;
		default:
			BREAK;
	}

	if ((infh = Open(name, MODE_OLDFILE)) == NULL)
		BREAK;

	read(&longid, 4);

	if (longid != 'MSXS')			/* music player song id */
		BREAK;

	read(&tempseq, sizeof(struct sequence));

	if (options < 2)
	{
		sequence = tempseq;			/* struct copy	*/

		if ((sequence.events = AllocMem((long)(sequence.numevents * sizeof(struct event)), 0L)) == NULL)
			BREAK;

		read(sequence.events, sequence.numevents * sizeof(struct event));
	}
	else
	{
		skip(tempseq.numevents * sizeof(struct event));
	}

	if (options != 1)
	{
		for (i = 0; i < MAXAMIGASAMPLES; i++)
		{
			read(&longid, 4);

			if (longid == 'STOP') break;

			if (longid)
			{
				if ((sp = AllocMem(longid, MEMF_CHIP)) == NULL)
					BREAK;

				amiga_samples[i] = sp;

				read(sp, longid);

				read(&longid, 4);

				while (longid)
				{
					if ((np = AllocMem(longid, MEMF_CHIP)) == NULL)
						BREAK;

					sp->next_sample = np;

					read(np, longid);

					read(&longid, 4);

					sp = np;
				}
			}
			else
			{
				amiga_samples[i] = NULL;
			}
		}
	}

	Close(infh);

	return TRUE;

leave:
	if (infh)
		Close(infh);

	return FALSE;
}

/****************************************************/
/*													*/
/* FreePlayXSong() - Free any samples and events	*/
/*					 allocated by LoadPlayXSong()	*/
/*													*/
/* Entry: None										*/
/* Exit : None										*/
/*													*/
/****************************************************/

void FreePlayXSong(void)
{
	/* StopMusic(); */
	
	FreePlayXSamp();
	FreePlayXSeq();
}

/****************************************************/
/*													*/
/* FreePlayXSamp() - Free any samples				*/
/*		 allocated by LoadPlayXSong()				*/
/*													*/
/* Entry: None										*/
/* Exit : None										*/
/*													*/
/****************************************************/

void FreePlayXSamp(void)
{
	long	 i;

	StopMusic();

	for (i = 0; i < MAXAMIGASAMPLES; i++)
	{
		FreeSample(i);
	}
}

/****************************************************/
/*													*/
/* FreePlayXSeq() - Free any events					*/
/*		 			allocated by LoadPlayXSong()	*/
/*													*/
/* Entry: None										*/
/* Exit : None										*/
/*													*/
/****************************************************/

void FreePlayXSeq(void)
{
	if (sequence.numevents)
	{
		FreeMem(sequence.events, (long)(sequence.numevents * sizeof(struct event)));
		sequence.numevents = 0;
	}
}

/****************************************************/
/*													*/
/* AllocSample() - Allocate memory for a sample but */
/*				   don't starve the system			*/
/*													*/
/* Entry: previous sample, attack & sustain length	*/
/* Exit : ptr to new sample (or NULL)				*/
/*													*/
/****************************************************/

struct sample *AllocSample(
	struct sample	*prev_sample,
	LONG			attacksize,
	LONG			sustainsize)
{
	register struct sample	*newsample;

	if (newsample = AllocMem((long)(attacksize + sustainsize + sizeof(struct sample) - 2), MEMF_CHIP|MEMF_CLEAR))
	{
		if (AvailMem(MEMF_CHIP) < CHIPRAM_THRESH)		/* don't starve system	*/
		{
			FreeMem(newsample, (long)(attacksize + sustainsize + sizeof(struct sample) - 2));
			newsample = NULL;
		}
		else
		{
			newsample->attack_size	= attacksize;
			newsample->sustain_size = sustainsize;

			if (prev_sample)		/* link to previous if any		*/
				prev_sample->next_sample = newsample;
		}
	}
	return newsample;
}

/****************************************************/
/*													*/
/* FreeSample() - Free previously allocated sample	*/
/*													*/
/* Entry: sample number								*/
/* Exit : None										*/
/*													*/
/****************************************************/

void FreeSample(LONG number)
{
	struct sample	*sample;
	struct sample	*next_sample;

	sample = amiga_samples[number];

	while (sample)
	{
		next_sample = sample->next_sample;
		FreeMem(sample, (long)(sample->attack_size + sample->sustain_size + sizeof(struct sample)-2));
		sample = next_sample;
	}

	amiga_samples[number] = NULL;
}

#ifdef FREESTANDING

#ifdef HI_RES
#define RESOLUTION_SHIFT		1
#define TEMPO_MULTIPLIER		4
#else
#define RESOLUTION_SHIFT		3
#define TEMPO_MULTIPLIER		1
#endif

ULONG   	formlength;
ULONG   	numoldevents;
ULONG   	bogus;

UWORD cook_time(ULONG time)
{	UWORD ctime;

	ctime = ((time & 0xfff000) >> 12) * sequence.barsize;
	ctime += (time & 0x0fff) + 4 >> RESOLUTION_SHIFT;
	return ctime;
}

int LoadPerf(char *name)
{
	UWORD  		i;
	ULONG  		j;
	ULONG   	chunk_id;
	ULONG   	length;
	ULONG   	samplenum;
	ULONG   	seqnum;
	LONG    	templong;
	char    	tempbyte;
	struct event *ep;

	ULONG   	starttime;      	/* 24 bits really, ignore first byte    */
	UBYTE   	midicommand;
	UBYTE   	data1;
	UBYTE   	data2;
	ULONG   	stoptime;       	/* 24 bits really, ignore first byte    */
	UBYTE   	data3;

	StopMusic();

	FreePlayXSong();

	bogus = 0;
	formlength = 0;

	if ((infh = Open(name, MODE_OLDFILE)) == NULL) BREAK;

	read(&chunk_id, 4);
	read(&formlength, 4);

	if (chunk_id != 'FORM') BREAK;

	read(&chunk_id, 4);

	if (chunk_id != 'MSCX') BREAK;

	formlength -= 4;

	length = 0;

	while (formlength > 0)
	{
		if (length & 1)
		{
			skip(1);
			formlength -= 1;
		}

		read(&chunk_id, 4);

		read(&length, 4);

		formlength -= 8;

		if (chunk_id == 'FORM')
		{
			read(&chunk_id, 4);
			length -= 4;
			formlength -= 4;
		}

		if (length < 0 || length > formlength) BREAK;

		switch (chunk_id)
		{
			case 'SLOT':
			{
				read(&samplenum, 4);
				if (samplenum > 15) BREAK;
				break;
			}

			case '8SVX':
			{
				if (!ReadSample(samplenum, length)) BREAK;
				length -= bogus;
				break;
			}

			case 'TMPO':
			{
				read(&templong, 4);
				sequence.tempo = templong * TEMPO_MULTIPLIER >> 16;
				break;
			}

			case 'TSIG':
			{
				read(&sequence.beatsperbar, 2);
				read(&sequence.beatsize, 2);
				sequence.barsize = (sequence.beatsperbar * (768 >> sequence.beatsize)) >> RESOLUTION_SHIFT;
				break;
			}

			case 'SEQU':
			{
				read(&seqnum, 4);

				if (sequence.numevents) { skip(length-4); }
				else
				{
					numoldevents = (length - 40) / 10;

					skip(30);       	/* skip name    */
					read(&sequence.startmode, 1);
					read(&sequence.clockmode, 1);
					skip(2);		/* skip output bank + pad */
					read(&sequence.startmeasure, 2);

					if ((sequence.events = AllocMem(numoldevents * sizeof(struct event), MEMF_CLEAR)) == NULL)
						BREAK;

					sequence.numevents = numoldevents; 	/* number of events     after filter    */
					ep = sequence.events;
					j = 0;  	/* numnewevents */

					for (i = 0; i < numoldevents; i++)
					{
						read((((char *)&starttime)+1), 3);
						read(&midicommand, 1);
						read(&data1, 1);
						read(&data2, 1);
						read((((char *)&stoptime)+1), 3);
						read(&data3, 1);

						ep->starttime = cook_time(starttime);
						ep->stoptime = cook_time(stoptime);

						switch (midicommand)
						{
							case 0x00:
								ep->command = END;
								ep++;
								j++;
								break;

							case 0x72:
								ep->command = TEMPO;
								ep->data = (((data2 << 7) | data1) * TEMPO_MULTIPLIER)/2;
								ep++;
								j++;
								break;

							default:
								if ((midicommand & 0xF0) == 0x90)
								{
									ep->command = data1 & 0x7F;
									ep->data = midicommand & 0xF;
									ep++;
									j++;
								}
								break;
						}
					}
				}
			}
			break;

			default:
				skip(length);   	/* skip chunk */
				break;
		}
		formlength -= length;
	}

	if (sequence.tempo == 0) BREAK;

	Close(infh);

	return TRUE;

leave:
	if (infh)
		Close(infh);
	return FALSE;
}

/****************************************************/
/*      										    */
/* ReadSample() - Read an 8SVX sample from disk and */
/*      		  converts to music player sample   */
/*      										    */
/* Entry: sample number, size   					*/
/* Exit : success code (TRUE = loaded OK)           */
/*      										    */
/****************************************************/

int ReadSample(long number, long size)
{
	struct sample  			*new = NULL;
	WORD    				seg_count = 0;
	LONG    				length = 0;
	LONG    				chunk_id;
	WORD    				i;
	WORD    				lastvolume;
	WORD    				vdiff;
	LONG    				attack_length;
	LONG    				sustain_length;
	Voice8Header    		vhdr;
	struct envseg   		env[16];


/* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */

	if (amiga_samples[number])
		FreeSample(number);

	for (i = 0; i < DEFAULT_ENV_SIZE; i++)
		env[i] = std_envelope[i];       			/* init default envelope	*/

	length = 0;

	while (size > 0)						/* while more data      		*/
	{
		if (length & 1) 					/* skip byte if odd length      */
		{
			size -= 1;
			skip(1);
		}

		read(&chunk_id, sizeof(chunk_id));

		read(&length, sizeof(length));

		size -= 8;      						/* update size  			*/

		if ((length > size) || (length < 0))
			BREAK;

		switch (chunk_id)
		{
			case 'VHDR':
			{
				read(&vhdr, sizeof(vhdr));
				break;
			}
			case 'ATAK':
			case 'RLSE':
			{
				if (chunk_id == 'ATAK')
					 seg_count = 0;

				for (i = 0; i < length / sizeof (EGPoint); i++)
				{
					EGPoint 	seg;

					read(&seg, sizeof(seg));

					if (seg_count < 16)
					{
						env[seg_count].duration = (UWORD) (((ULONG) seg.duration << 16) / MSEC_PER_VBLANK);
						env[seg_count].volume   = (seg.dest >> 12);
						env[seg_count].flags    = 0;
						seg_count++;

					}
				}

				if (seg_count)
					env[seg_count-1].flags = ENV_SUST;

				break;
			}
			case 'BODY':
			{
				attack_length   = vhdr.oneShotHiSamples;
				sustain_length  = vhdr.repeatHiSamples;

				while (vhdr.ctOctave--)
				{
					new = AllocSample(new, attack_length, sustain_length);
					if (!new) BREAK;

					if (!amiga_samples[number]) amiga_samples[number] = new;

					read(new->waveform_data, attack_length + sustain_length);

					attack_length  += attack_length;
					sustain_length += sustain_length;

					if (vhdr.samplesPerSec)
					{
						LONG samples_sec = vhdr.samplesPerSec;
						LONG ticks_second = ATICKS_PER_SEC;
						LONG ideal_period;
						WORD octave_shift = -363;       	/* fudge factor??? */

						while (TRUE)
						{
							ideal_period = ticks_second / samples_sec;

							if (ideal_period > 1017)
							{
								samples_sec  *= 2;
								octave_shift -= 288;
							}
							else
							{
								if (ideal_period <= 508)
								{
									ticks_second *= 2;
									octave_shift += 288;
								}
								else break;
							}
						}

						for (i=0; i <= 288; i++)
						{
							if (ideal_period > period_table[i])
								break;
						}

						new->tune = i + octave_shift;
					}
					else
						new->tune = 72; 	/* default (???)	*/
											/* copy envelope	*/
					if (seg_count)
						env[seg_count-1].flags = ENV_HALT;

					if (!seg_count)
						seg_count = DEFAULT_ENV_SIZE;

					lastvolume = 0;

					for (i=0; i<16; i++)
					{
						if (i < seg_count)
						{
						/* pre-calculate increment for envelope interrupt */

							vdiff = env[i].volume - lastvolume;

							if (env[i].duration)
							{	LONG tt;

								tt = (vdiff >= 0 ? vdiff : -vdiff) << 16 | 0x8000L;
								env[i].delta = tt / env[i].duration;

								if (vdiff < 0)
									env[i].delta = -env[i].delta;
							}
							else
							{
								env[i].delta = (LONG)vdiff << 16;
							}

							lastvolume = env[i].volume;
						}
						else
						{
						/* clear out unused envelope segments   */

							env[i].delta = 0;
							env[i].volume = 0;
							env[i].duration = 0;
							env[i].flags = 0;
						}

						new->envelope_data[i] = env[i]; 	/* struct copy  */
					}
				}

			/* read no more hunks */

				bogus = size-length;
				length += size-length;

				break;
			}
			default:
			{
				skip(length);
				break;
			}
		}
		size -= length;
	}
	return TRUE;

leave:
	return FALSE;
}

#endif

/* EOF */
