/* MHI DCR MAS-Player library */

#include <clib/exec_protos.h>
#include <clib/alib_protos.h>
#include <clib/cia_protos.h>
#include <clib/misc_protos.h>
#include <clib/dos_protos.h>

#include <exec/libraries.h>
#include <exec/types.h>
#include <exec/memory.h>
#include <exec/lists.h>
#include <exec/interrupts.h>
#include <hardware/intbits.h>
#include <hardware/cia.h>
#include <resources/cia.h>
#include <resources/misc.h>

#include "mhi.h"

#pragma libbase MHIBase

extern void dcr_pro_int();
extern void SetupParPort();
extern void SetVolume(register __d0 ULONG l, register __d1 ULONG r);
extern void SetPrefactor(register __d0 UWORD prefac);
extern void SetBass(register __d0 UWORD bass);
extern void SetTreble(register __d0 UWORD treb);

/* volume
 * first is loudest
 * 21 total
 */

ULONG voltab[21] = 
	{0x80000,0x8DEB8,0x9A537,0xA5621,0xAF3CD,0xB8053,0xBFD92,0xC6D31,
	 0xCD0AD,0xD2958,0xD785E,0xDBECC,0xDFD91,0xE3583,0xE675F,0xE93CF,
	 0xEBB6A,0xEDEB6,0xEFE2C,0xF1A36,0xFFFFA};

/* prefactor
 * first is quietest
 * 16 total
 */

UWORD preftab[16] = 
	{0x8000,0x8E00,0x9A40,0xA580,0xAF40,0xB800,0xBFC0,0xC6C0,
	 0xCD00,0xD25C,0xD780,0xDC00,0xDFC0,0xE340,0xE680,0xE940};

/* bass
 * 0x0000 (15) is normal
 * 31 total
 */
 
UWORD basstab[31] = 
	{0x9E40,0xA280,0xA740,0xAC40,0xB180,0xB740,0xBD40,0xC3C0,
	 0xCA40,0xD180,0xD8C0,0xE040,0xE800,0xEFC0,0xF7C0,0x0000,
	 0x0800,0x1000,0x17C0,0x1F80,0x2700,0x2E40,0x3580,0x3C00,
	 0x4280,0x4880,0x4E40,0x5380,0x5880,0x5D40,0x6180};

/* treble
 * 0x0000 (15) is normal
 * 31 total
 */

UWORD trebtab[31] = 
	{0xB2C0,0xBB40,0xC180,0xC6C0,0xCBC0,0xD040,0xD500,0xD980,
	 0xDE00,0xE280,0xE7E0,0xEC00,0xF0C0,0xF5C0,0xFAC0,0x0000,
	 0x0540,0x0AC0,0x1040,0x1600,0x1C00,0x2200,0x2840,0x2EC0,
	 0x3540,0x3C00,0x42C0,0x49C0,0x5180,0x5840,0x5F80};

STRPTR DriverName		= "MAS Player Pro";
STRPTR DriverVersion = "$VER: 1.4 (03/07/01)";
STRPTR DriverAuthor	= "Paul Qureshi";

struct MPHandle
{
	APTR data;				/* current buffer node 				*/
	APTR endnode;			/* node at the end of the list 	*/
	struct Task *task;	/* task that wants signals 		*/
	ULONG mhisignal;		/* signal mask to use 				*/
	UBYTE status;			/* player status flags				*/
	UBYTE	oldstatus;		/* last status							*/

	UBYTE volume;
	UBYTE panning;
	UBYTE mixing;
	UBYTE prefactor;
	UBYTE bass;
	UBYTE treble;
	UBYTE mid;
};

struct MPBufferNode
{
	struct MinNode	my_MinNode;
	APTR	buffer;
	APTR	pos;
	ULONG	bytesleft;
};

/* Prototypes */

APTR 	MHIAllocDecoder	(register __a0 struct Task *task, register __d0 ULONG mhisignal);
VOID 	StartTimer			(struct freetimer *ft);
BOOL 	FindFreeTimer		(struct freetimer *ft, int preferA);
BOOL 	TryTimer				(struct freetimer *ft);
VOID 	MHIFreeDecoder		(register __a3 APTR handle);
BOOL 	MHIQueueBuffer		(register __a3 MPHandle *handle, register __a0 APTR buffer, register __d0 ULONG size);
APTR	MHIGetEmpty			(register __a3 MPHandle *handle);
UBYTE	MHIGetStatus		(register __a3 MPHandle *handle);
VOID	MHIPlay				(register __a3 MPHandle *handle);
VOID	MHIStop				(register __a3 MPHandle *handle);
VOID	MHIPause				(register __a3 MPHandle *handle);
ULONG	MHIQuery				(register __d1 ULONG query);
VOID	MHISetParam			(register __a3 MPHandle *handle, register __d0 UWORD param, register __d1 ULONG value);

struct MinList BufList;

BOOL mhiallocated;

UBYTE *owner = NULL;
struct Library *MiscBase = NULL;

/*
 * CIA stuff
 *
 */

#define HICOUNT 0x0c
#define LOCOUNT 0x00

#define STOPA_AND  CIACRAF_TODIN | CIACRAF_PBON | CIACRAF_OUTMODE | CIACRAF_SPMODE
#define STOPB_AND  CIACRBF_ALARM | CIACRBF_PBON | CIACRBF_OUTMODE

#define STARTA_OR  CIACRAF_START
#define STARTB_OR  CIACRBF_START

/*
 * Structure which will be used to hold all relevant information about
 * the cia timer we manage to allocate.
 *
 */

struct freetimer
{
    struct Library *ciabase;        /* CIA Library Base             */
    ULONG  timerbit;                /* timer bit allocated          */
    struct CIA *cia;                /* ptr to hardware              */
    UBYTE *ciacr;                   /* ptr to control register      */
    UBYTE *cialo;                   /* ptr to low byte of timer     */
    UBYTE *ciahi;                   /* ptr to high byte of timer    */
    struct Interrupt timerint;      /* Interrupt structure          */
    UBYTE  stopmask;                /* Stop/set-up timer            */
    UBYTE  startmask;               /* Start timer                  */
};

struct CIA *ciaa = (struct CIA *)0xbfe001;
struct CIA *ciab = (struct CIA *)0xbfd000;

struct freetimer ft;

/* for mas3507 timing */

struct LibBase *GFXBase;

/* --------------------------------------------------------------------- */
/*                             Start of code                             */
/* --------------------------------------------------------------------- */

APTR MHIAllocDecoder(register __a0 struct Task *task, register __d0 ULONG mhisignal)
/* Allocate MHI. Open all needed resources etc, and be prepared
	to output.

	*task is the task that wants signals
	mhisignal is the signal mask to use
*/
{
	struct MPHandle *handle;

	if ( !( GFXBase = (struct LibBase *) OpenLibrary("graphics.library",0)) )
		return 0L;		/* ech! */
	
	if (!mhiallocated)
		if ( handle = AllocMem(sizeof(MPHandle), MEMF_CLEAR) )
		{
			handle->task = task;
			handle->mhisignal = mhisignal;
			handle->status = MHIF_STOPPED;

			handle->volume		= 100;
			handle->panning	= 50;
			handle->mixing		= 50;
			handle->prefactor	= 50;
			handle->bass		= 50;
			handle->treble		= 50;
			handle->mid			= 50;

			SetVolume(voltab[0], voltab[0]);
			SetPrefactor(preftab[0]);
			SetBass(basstab[15]);
			SetTreble(trebtab[15]);

			if ( MiscBase = (struct Library *)OpenResource(MISCNAME) )
			{
				if ( ((owner = AllocMiscResource(MR_PARALLELPORT, "MHI")) == NULL)
				&&	  ((owner = AllocMiscResource(MR_PARALLELBITS, "MHI")) == NULL) )
				{

					SetupParPort();	// set par port bits

					ft.timerint.is_Node.ln_Type 	= NT_INTERRUPT;
					ft.timerint.is_Node.ln_Pri 	= 0;
					ft.timerint.is_Node.ln_Name 	= "MHI_MAS_Player_Pro";
					ft.timerint.is_Data 				= handle;
					ft.timerint.is_Code 				= dcr_pro_int;

					if (FindFreeTimer(&ft,TRUE))
					{
						StartTimer(&ft);

						/* setup main list */
						BufList.mlh_Head		= (struct MinNode *) &BufList.mlh_Tail;
						BufList.mlh_Tail		= 0;
						BufList.mlh_TailPred	= (struct MinNode *) &BufList.mlh_Head;
						mhiallocated = TRUE;
						return handle;
					}
					else
						return 0L;
				}
				else
					return 0L;
			}
			else
				return 0L;
		}
		else
			return 0L;
	else
		return 0L;
}

VOID MHIFreeDecoder(register __a3 APTR handle)
/* Free MHI and all resources
*/
{
	APTR killednode;

	if (handle)
	{
		RemICRVector(ft.ciabase,ft.timerbit,&ft.timerint);
		
		FreeMem(handle, sizeof(MPHandle));
		
		FreeMiscResource(MR_PARALLELPORT);
		FreeMiscResource(MR_PARALLELBITS);
		
		/* free buffer list */
		while ( killednode = RemHead((struct List *)&BufList) )
			FreeMem(killednode, sizeof(struct MPBufferNode));
			
		mhiallocated = FALSE;

		CloseLibrary((struct Library *) GFXBase);
	}
}

BOOL MHIQueueBuffer(register __a3 MPHandle *handle, register __a0 APTR buffer, register __d0 ULONG size)
/* Add this buffer to the queue
*/
{
	struct MPBufferNode *newnode;
	
	if (!( newnode = AllocMem(sizeof(MPBufferNode), MEMF_CLEAR) ))
		return FALSE;
	else
	{
		newnode->buffer 		= buffer;
		newnode->pos			= buffer;
		newnode->bytesleft 	= size;
		AddTail((struct List *)&BufList,(struct Node *)newnode);
		handle->endnode = newnode;
		
		if (handle->data == 0)
			handle->data = newnode;
			
		return TRUE;
	}
}

APTR MHIGetEmpty(register __a3 MPHandle *handle)
/* Find the next empty buffer node and return the buffers address
*/
{
	struct MPBufferNode *mynode;
	APTR temp = 0;
	
	mynode = (MPBufferNode *)BufList.mlh_Head;
	
	for (mynode = (MPBufferNode *)BufList.mlh_Head ; mynode->my_MinNode.mln_Succ ; mynode = (MPBufferNode *)mynode->my_MinNode.mln_Succ)
		if (mynode->bytesleft == 0)
			temp = mynode;
			
	mynode = temp;
	
	if ((APTR)mynode != &BufList && mynode != 0)
	{
		Remove((struct Node *)mynode);
		temp = mynode->buffer;
		FreeMem(mynode, sizeof(struct MPBufferNode));
		return temp;
	}
	else
		return 0L;
}

UBYTE MHIGetStatus(register __a3 MPHandle *handle)
/* Get the MHI players status flags
*/
{
	return handle->status;
}

VOID MHIPlay(register __a3 MPHandle *handle)
/* Set the player to play mode
*/
{
	handle->status = MHIF_PLAYING;
}

VOID MHIStop(register __a3 MPHandle *handle)
/* Stop the player and free all buffers
*/
{
	APTR killednode;

	handle->status = MHIF_STOPPED;
	handle->data = 0;
	/* free buffer list */
	while ( killednode = RemHead((struct List *)&BufList) )
		FreeMem(killednode, sizeof(struct MPBufferNode));	
}

VOID MHIPause(register __a3 MPHandle *handle)
/* Pause the player
*/
{
	if (handle->status == MHIF_PAUSED)			/* Unpause */
		handle->status = MHIF_PLAYING;
	else
		if (handle->status == MHIF_PLAYING)		/* Pause */
			handle->status = MHIF_PAUSED;
}

ULONG MHIQuery(register __d1 ULONG query)
/* Respond to a feature query from an application
*/
{
	switch (query)
	{
		case MHIQ_MPEG1: case MHIQ_MPEG2: case MHIQ_MPEG25:
			return MHIF_SUPPORTED;
			break;
		
		case MHIQ_MPEG4:
			return MHIF_UNSUPPORTED;
			break;
			
		case MHIQ_LAYER1:
			return MHIF_UNSUPPORTED;
			break;
			
		case MHIQ_LAYER2: case MHIQ_LAYER3:
			return MHIF_SUPPORTED;
			break;
			
		case MHIQ_VARIABLE_BITRATE:
			return MHIF_SUPPORTED;
			break;
		
		case MHIQ_JOINT_STERIO:
			return MHIF_SUPPORTED;
			break;
			
		case MHIQ_BASS_CONTROL: case MHIQ_TREBLE_CONTROL: case MHIQ_PREFACTOR_CONTROL:
			return MHIF_SUPPORTED;
			break;
			
		case MHIQ_MID_CONTROL:
			return MHIF_UNSUPPORTED;
			
		case MHIQ_VOLUME_CONTROL: case MHIQ_PANNING_CONTROL:
			return MHIF_SUPPORTED;
			break;

		case MHIQ_CROSSMIXING:
			return MHIF_UNSUPPORTED;
			
		case MHIQ_IS_HARDWARE:
			return MHIF_TRUE;
			break;

		case MHIQ_IS_68K: case MHIQ_IS_PPC:
			return MHIF_FALSE;
			break;
			
		case MHIQ_DECODER_NAME:
			return (ULONG) DriverName;
			break;

		case MHIQ_DECODER_VERSION:
			return (ULONG) DriverVersion;
			break;
			
		case MHIQ_AUTHOR:
			return (ULONG) DriverAuthor;
			break;
			
		default:
			return MHIF_UNSUPPORTED;
			break;			
	}
}

VOID MHISetParam(register __a3 MPHandle *handle, register __d0 UWORD param, register __d1 ULONG value)
/* Set decoder paramiter.
*/
{
	UBYTE calc;
	UBYTE r;
	UBYTE l;

	switch (param)
	{
		case MHIP_VOLUME:

			if (value != handle->volume)
			{
				handle->volume = value;
				value = handle->panning;

				if (value > 50)		// pan to right
				{
					r = (float)(100 - ( handle->volume * ((100-value)*0.02) ) * 0.2);
					l = (float)(100 - handle->volume) * 0.2;
				}
				else
				{
					if (value < 50)
					{
						r = (float)(100 - handle->volume) * 0.2;
						l = (float)(100 - (handle->volume * (value*0.02)) * 0.2);
					}
					else
						l = r = (float)(100 - handle->volume) * 0.2;
				}

				if (r > 20)
					r = 20;
				if (l > 20)
					l = 20;
				if (r < 0)
					r = 0;
				if (l < 0)
					l = 0;

				handle->oldstatus = handle->status;
				handle->status = MHIF_PAUSED;
				SetVolume(voltab[l], voltab[r]);
				handle->status = handle->oldstatus;
			}
		
			break;
		
		case MHIP_PANNING:

			if (value != handle->panning)
			{
				handle->panning = value;

				if (value > 50)		// pan to right
				{
					r = (float)(100 - ( handle->volume * ((100-value)*0.02) )) * 0.2;
					l = (float)(100 - handle->volume) * 0.2;
				}
				else
				{
					if (value < 50)		// pan to right
					{
						r = (float)(100 - handle->volume) * 0.2;
						l = (float)(100 - (handle->volume * (value*0.02))) * 0.2;
					}
					else
						l = r = (float)(100 - handle->volume) * 0.2;
				}

				if (r > 20)
					r = 20;
				if (l > 20)
					l = 20;
				if (r < 0)
					r = 0;
				if (l < 0)
					l = 0;

				handle->oldstatus = handle->status;
				handle->status = MHIF_PAUSED;
				SetVolume(voltab[l], voltab[r]);
				handle->status = handle->oldstatus;
			}
			
			break;

		case MHIP_CROSSMIXING:

			if (value != handle->mixing)
			{
				handle->mixing = value;
			}
			
			break;
			
		case MHIP_PREFACTOR:

			if (value != handle->prefactor)
			{
				handle->prefactor = value;
				if (value > 50)
				{
					value -= 50;							// only prefactors above 50
					if (value < 0)						// are used by the MAS3507
						value = 0;
					calc = (float)value * .3;
					if (calc > 15)
						calc = 15;
					if (calc < 0)
						calc = 0;
				}
				else
				{
					calc=0;
				}
				SetPrefactor(preftab[calc]);
			}
			
			break;

		case MHIP_BASS:

			if (value != handle->bass)
			{
				handle->bass = value;
				calc = (float)value * 0.3;
				if (calc > 30)
					calc = 30;
				if (calc < 0)
					calc = 0;
				SetBass(basstab[calc]);
			}

			break;

		case MHIP_TREBLE:

			if (value != handle->treble)
			{
				handle->treble = value;
				calc = (float)value * 0.3;
				if (calc > 30)
					calc = 30;
				if (calc < 0)
					calc = 0;
				SetTreble(trebtab[calc]);
			}

			break;

		case MHIP_MID:

			if (value != handle->mid)
			{
				handle->mid = value;
				// SetMid(mid);
			}

			break;

	}
	
}

void StartTimer(struct freetimer *ft)
{
register struct CIA *cia;

cia = ft->cia;

/* Note that there are differences between control register A,
 * and B on each CIA (e.g., the TOD alarm bit, and INMODE bits.
 */

if (ft->timerbit == CIAICRB_TA)
    {
    ft->ciacr = &cia->ciacra;       /* control register A   */
    ft->cialo = &cia->ciatalo;      /* low byte counter     */
    ft->ciahi = &cia->ciatahi;      /* high byte counter    */

    ft->stopmask = STOPA_AND;       /* set-up mask values   */
    ft->startmask = STARTA_OR;
    }
else
    {
    ft->ciacr = &cia->ciacrb;       /* control register B   */
    ft->cialo = &cia->ciatblo;      /* low byte counter     */
    ft->ciahi = &cia->ciatbhi;      /* high byte counter    */

    ft->stopmask = STOPB_AND;       /* set-up mask values   */
    ft->startmask = STARTB_OR;
    }

Disable();
*ft->ciacr &= ft->stopmask;
Enable();

/* Clear signal bit - interrupt will signal us later */
/* SetSignal(0L,1L<<ed->signal);
*/

*ft->cialo = LOCOUNT;
*ft->ciahi = HICOUNT;

/* Turn on start bit - same bit for both A, and B control regs  */

Disable();
*ft->ciacr |= ft->startmask;
Enable();
}



BOOL FindFreeTimer(struct freetimer *ft, int preferA)
{
//struct CIABase *ciaabase, *ciabbase;
struct Library *ciaabase, *ciabbase;

ciaabase = OpenResource(CIAANAME);
ciabbase = OpenResource(CIABNAME);

if (preferA)
    {
    ft->ciabase = ciaabase; /* library address  */
    ft->cia     = ciaa;     /* hardware address */
    }
else
    {
    ft->ciabase = ciabbase; /* library address  */
    ft->cia     = ciab;     /* hardware address */
    }

if (TryTimer(ft))
    return(TRUE);

if (!(preferA))
    {
    ft->ciabase = ciaabase; /* library address  */
    ft->cia     = ciaa;     /* hardware address */
    }
else
    {
    ft->ciabase = ciabbase; /* library address  */
    ft->cia     = ciab;     /* hardware address */
    }

if (TryTimer(ft))
    return(TRUE);

return(FALSE);

}



BOOL TryTimer(struct freetimer *ft)
{

if (!(AddICRVector(ft->ciabase,CIAICRB_TA,&ft->timerint)))
    {
    ft->timerbit = CIAICRB_TA;
    return(TRUE);
    }

if (!(AddICRVector(ft->ciabase,CIAICRB_TB,&ft->timerint)))
    {
    ft->timerbit = CIAICRB_TB;
    return(TRUE);
    }

return(FALSE);
}