 
/*         DeliTracker-Player for ADPCM audio samples           */
/* Written in 1995 by Christian Buchner. This is Public Domain. */

/* Note: TAB SIZE = 4 */

/* History:

   added version string (still V1.0)

   V1.1: reduced chip buffer size to 1K

   V1.2: rewritten for loading constantly while playing
         added dummy NotePlayer interface, makes Stereoscope etc. work

*/

/* Includes */

#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/intuition.h>
#include <libraries/dos.h>
#include <dos/dostags.h>
#include <utility/tagitem.h>
#include <devices/audio.h>
#include <exec/execbase.h>
#include <exec/memory.h>
#include <string.h>
#include <stdarg.h>
#include "DeliPlayer.h"


/* Version String */

#define PLAYER_VERSION 1
#define PLAYER_REVISION 2

UBYTE Version[]="$VER: ADPCM-Player 1.2 "__AMIGADATE__" by Christian Buchner";


/* Externals */

extern struct DosLibrary *DOSBase;
extern struct IntuitionBase *IntuitionBase;

/* DeliTracker's stuff */

struct DeliTrackerGlobals *DeliBase;
struct MsgPort *DeliPort;

/* Copyright and info */

UBYTE AboutString[]="a player for ADPCM audio samples\n"
					"in MONO ADPCM2 or ADPCM3 format.\n"
					"(c) 1995 by Christian Buchner";


void __asm __saveds DeliProcess(void);
ULONG __asm __saveds Check(void);
ULONG __asm __saveds InitPlayer(void);
ULONG __asm __saveds EndPlayer(void);
ULONG __asm __saveds InitSound(void);
ULONG __asm __saveds EndSound(void);
ULONG __asm __saveds StartInt(void);
ULONG __asm __saveds StopInt(void);
ULONG __asm __saveds Faster(void);
ULONG __asm __saveds Slower(void);
ULONG __asm __saveds VolBalance(void);
void SetPerVol(void);
void __stdargs Message(UBYTE *Msg,...);

struct NoteStruct *NotePlay;

extern __asm ULONG DecompressADPCM2(	register __a0 UBYTE *Source,
										register __d0 ULONG Length,
										register __a1 UBYTE *Destination,
										register __d1 ULONG JoinCode	);

extern __asm ULONG DecompressADPCM3(	register __a0 UBYTE *Source,
										register __d0 ULONG Length,
										register __a1 UBYTE *Destination,
										register __d1 ULONG JoinCode	);


/* Tag list for DeliTracker */

struct TagItem PlayerTagArray[]=
{
	DTP_RequestDTVersion,	17,
	DTP_PlayerVersion,		(PLAYER_VERSION<<16)+10*PLAYER_REVISION,
	DTP_PlayerName,			(ULONG)"ADPCM-Player",
	DTP_Creator,			(ULONG)AboutString,
	DTP_Description,		(ULONG)"a player for ADPCM samples",
	DTP_Flags,				PLYF_SONGEND,
	DTP_DeliBase,			(ULONG)&DeliBase,
	DTP_Check1,				(ULONG)&Check,
	DTP_Process,			(ULONG)&DeliProcess,
	DTP_Priority,			22,
	DTP_StackSize,			4096,
	DTP_MsgPort,			(ULONG)&DeliPort,
	DTP_NoteStruct,			(ULONG)&NotePlay,
	DTP_InitPlayer,			(ULONG)&InitPlayer,
	DTP_EndPlayer,			(ULONG)&EndPlayer,
	DTP_InitSound,			(ULONG)&InitSound,
	DTP_EndSound,			(ULONG)&EndSound,
	DTP_StartInt,			(ULONG)&StartInt,
	DTP_StopInt,			(ULONG)&StopInt,
	DTP_Volume,				(ULONG)&VolBalance,
	DTP_Balance,			(ULONG)&VolBalance,
	DTP_Faster,				(ULONG)&Faster,
	DTP_Slower,				(ULONG)&Slower,
	TAG_DONE
};


/* The primitive sample header */

struct ADPCMHeader
{
	UBYTE Identifier[6];
	ULONG Frequency;
};

UBYTE IDString[]="ADPCM";

struct Process *PlayerTask;

BOOL ChanInit;
struct MsgPort *LeftReply[3];		/* 2 for buffers, 1 for period/volume */
struct MsgPort *RightReply[3];
struct IOAudio *LeftAudio[3];
struct IOAudio *RightAudio[3];

#define CHIP_SIZE 1024
UBYTE *ChipBuffer[2];

/* specifies the buffer size in _decrunched_ bytes */
#define LOAD_SIZE (128*CHIP_SIZE)

/* specifies the length of the load queue */
#define QUEUE_LEN 2

BPTR LoadHandle;
ULONG FileSize;

struct LoadBuffer
{
	struct MinNode lb_Node;
	ULONG lb_ADPCMPosition;
	UBYTE *lb_ADPCMData;
	ULONG lb_ADPCMLen;
	ULONG lb_PlayOffset;
};

struct List BufferList;
UWORD NumBuffers;
struct SignalSemaphore BufferSemaphore;
struct SignalSemaphore LoadQuitSemaphore;

struct Process *LoadProc;
void __saveds LoadBuffers(void);

ULONG Bits;
ULONG Frequency;
ULONG LeftVolume=64;
ULONG RightVolume=64;

ULONG ADPCMLen;
ULONG ADPCMPosition;
BOOL Loading=FALSE;
BOOL LoadingStopped=TRUE;

BOOL Playing;
BOOL BufPlaying[2];
ULONG PlayPosition;
ULONG JoinCode;


/*** NotePlayer interface ***/

BOOL NoteInit=FALSE;
UBYTE *NotePosition=NULL;
ULONG NoteLength=0;
UWORD NotePeriod=0;

struct NoteChannel NoteChannels[2]=
{
	&NoteChannels[1],		/* NextChannel */
	0,						/* for use by NotePlayer */
	0,						/* Reserved0 */
	0,						/* Private */
	0,						/* Changed */
	NCHD_FarLeft,0,			/* StereoPos, Stereo */
	0,0,					/* SampleStart, SampleLength */
	0,0,					/* RepeatStart, RepeatLength */
	0,						/* Frequency */
	0,						/* Volume */
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,		/* Reserved1 */
	
	NULL,					/* NextChannel */
	0,						/* for use by NotePlayer */
	0,						/* Reserved0 */
	0,						/* Private */
	0,						/* Changed */
	NCHD_FarRight,0,		/* StereoPos, Stereo */
	0,0,					/* SampleStart, SampleLength */
	0,0,					/* RepeatStart, RepeatLength */
	0,						/* Frequency */
	0,						/* Volume */                      
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0			/* Reserved1 */
};

struct NoteStruct NoteStruct=
{
	&NoteChannels[0],								/* Channels */
	NSTF_Dummy|NSTF_Period|NSTF_Signed|NSTF_8Bit,	/* Flags */
	28867,											/* Max Frequency */
	64,												/* Max Volume */
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0				/* reserved */
};

struct NoteStruct *NotePlay = &NoteStruct;


/****************************************************************************/

/* Our player's Process */

void __asm __saveds DeliProcess(void)
{
	BOOL ProcActive=TRUE;
	ULONG Signals;
	struct DeliMessage *DeliMessage;
	
	DOSBase=(struct DosLibrary*)DeliBase->DOSBase;
	IntuitionBase=(struct IntuitionBase*)DeliBase->IntuitionBase;
	
	PlayerTask=(struct Process*)FindTask(NULL);
	PlayerTask->pr_Task.tc_Node.ln_Pri=22;
	
	NewList(&BufferList);
	InitSemaphore(&BufferSemaphore);
	InitSemaphore(&LoadQuitSemaphore);
	
	if (LoadProc=CreateNewProcTags(	NP_Entry, &LoadBuffers,
									NP_Name, "ADPCM Loader",
									NP_Priority, 21,
									TAG_DONE ))
	{
		while(ProcActive)
		{
			ULONG SigMask = SIGBREAKF_CTRL_C | (1L<<DeliPort->mp_SigBit);
			
			if (ChanInit) SigMask |=	(1L<<LeftReply[0]->mp_SigBit) | 
										(1L<<LeftReply[1]->mp_SigBit) ;
			
			Signals=Wait(SigMask);
			
			if (Signals & SIGBREAKF_CTRL_C)
			{
				ProcActive=FALSE;
			}
			
			if (Signals & (1L<<DeliPort->mp_SigBit))
			{
				if (DeliMessage=(struct DeliMessage*)GetMsg(DeliPort))
				{
					DeliMessage->Result=(*DeliMessage->Function)();
					ReplyMsg((struct Message*)DeliMessage);
				}
			}
			
			if (ChanInit)
			{
				UWORD i;
				
				for (i=0;i<2;i++)
				{
					if (Signals & (1L<<LeftReply[i]->mp_SigBit))
					{
						if (BufPlaying[i])
						{
							WaitPort(LeftReply[i]);		GetMsg(LeftReply[i]);
							WaitPort(RightReply[i]);	GetMsg(RightReply[i]);
							BufPlaying[i]=FALSE;
						}
						
						if (Playing)
						{
							if (NoteInit)
							{
								NoteChannels[0].nch_SampleStart=
								NoteChannels[0].nch_RepeatStart=
								NoteChannels[1].nch_SampleStart=
								NoteChannels[1].nch_RepeatStart=NotePosition;
								
								NoteChannels[0].nch_SampleLength=
								NoteChannels[0].nch_RepeatLength=
								NoteChannels[1].nch_SampleLength=
								NoteChannels[1].nch_RepeatLength=NoteLength;
								
								NoteChannels[0].nch_Frequency=
								NoteChannels[1].nch_Frequency=NotePeriod;
								
								NoteChannels[0].nch_Volume=64;
								NoteChannels[1].nch_Volume=64;
								
								NoteChannels[0].nch_Changed=
								NoteChannels[1].nch_Changed=NCHF_Sample|NCHF_Repeat|NCHF_Frequency|NCHF_Volume;
								
								dt_NotePlayer();
								
								NoteInit=FALSE;
							}
							
							if (PlayPosition > ADPCMLen)
							{
								Message("WARNING! PlayPosition > ADPCMLen!?!");
							}
							
							if (PlayPosition>=ADPCMLen)
							{
								/* Signal Songend */
								dt_SongEnd();
							}
							
							ObtainSemaphore(&BufferSemaphore);
							
							if (NumBuffers > 0)
							{
								struct LoadBuffer *lb;
								
								ULONG Do;
								ULONG ChipMax;
								ULONG DMALen;
								
								lb=(struct LoadBuffer*)BufferList.lh_Head;
								
								if (	lb->lb_ADPCMPosition==0 &&
										lb->lb_PlayOffset==0		)
								{
									JoinCode=0;
								}
								
								if (Bits==2) ChipMax = (CHIP_SIZE+3)/4;
								if (Bits==3) ChipMax = (CHIP_SIZE+7)/8*3;
								
								Do=lb->lb_ADPCMLen-lb->lb_PlayOffset;
								if (Do>ChipMax) Do=ChipMax;
								
								if (Do>0)
								{
									UWORD Period=(*(struct ExecBase**)(4))->ex_EClockFrequency*5/Frequency;
									
									if (Bits==2) JoinCode=DecompressADPCM2(lb->lb_ADPCMData+lb->lb_PlayOffset, Do, ChipBuffer[i], JoinCode);
									if (Bits==3) JoinCode=DecompressADPCM3(lb->lb_ADPCMData+lb->lb_PlayOffset, Do, ChipBuffer[i], JoinCode);
									
									lb->lb_PlayOffset += Do;
									PlayPosition=lb->lb_ADPCMPosition+lb->lb_PlayOffset;
									
									if (Bits==2) DMALen = Do*4;
									if (Bits==3) DMALen = Do*8/3;
									
									LeftAudio[i]->ioa_Data =
									RightAudio[i]->ioa_Data = ChipBuffer[i];
									
									LeftAudio[i]->ioa_Length =
									RightAudio[i]->ioa_Length = DMALen;
									
									LeftAudio[i]->ioa_Period = 
									RightAudio[i]->ioa_Period= Period;
									
									LeftAudio[i]->ioa_Volume=LeftVolume;
									RightAudio[i]->ioa_Volume=RightVolume;
									
									LeftAudio[i]->ioa_Cycles=
									RightAudio[i]->ioa_Cycles=1;
									
									LeftAudio[i]->ioa_Request.io_Flags|=ADIOF_PERVOL;
									RightAudio[i]->ioa_Request.io_Flags|=ADIOF_PERVOL;
									
									LeftAudio[i]->ioa_Request.io_Command=
									RightAudio[i]->ioa_Request.io_Command=CMD_WRITE;
									
									Forbid();
									BeginIO(LeftAudio[i]);
									BeginIO(RightAudio[i]);
									BufPlaying[i]=TRUE;
									Permit();
									
									NotePosition=ChipBuffer[i];
									NoteLength=DMALen;
									NotePeriod=Period;
									NoteInit=TRUE;
								}
								
								if (ADPCMPosition > ADPCMLen)
								{
									Message("WARNING! lb_PlayOffset > lb_ADPCMLen!?!");
								}
								
								if (lb->lb_PlayOffset >= lb->lb_ADPCMLen)
								{
									Remove((struct Node*)lb);
									NumBuffers--;
									
									Signal((struct Task*)LoadProc, SIGBREAKF_CTRL_D);
									
									if (lb->lb_ADPCMData)
									{
										FreeVec(lb->lb_ADPCMData);
										lb->lb_ADPCMData=NULL;
									}
									
									FreeVec(lb);
									lb=NULL;
								}
							}
							
							ReleaseSemaphore(&BufferSemaphore);
						}
					}
				}
			}
		}
		Signal((struct Task*)LoadProc, SIGBREAKF_CTRL_C);
		ObtainSemaphore(&LoadQuitSemaphore);
	}
}


/****************************************************************************/

void __saveds LoadBuffers(void)
{
	ULONG Signals;
	BOOL LoadActive=TRUE;
	
	ObtainSemaphore(&LoadQuitSemaphore);
	
	while (LoadActive)
	{
		Signals=Wait(SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D);
		
		if (Signals & SIGBREAKF_CTRL_C)
		{
			LoadActive=FALSE;
		}
		
		if (Signals & SIGBREAKF_CTRL_D)
		{
			if (Loading==FALSE)
			{
				LoadingStopped=TRUE;
				
				Signal(PlayerTask, SIGBREAKF_CTRL_D);
			}
			else
			{
				LoadingStopped=FALSE;
				
				if (LoadHandle && ADPCMLen>sizeof(struct ADPCMHeader))
				{
					while (NumBuffers < QUEUE_LEN)
					{
						struct LoadBuffer *lb;
						if (lb=AllocVec(sizeof(struct LoadBuffer),MEMF_ANY|MEMF_CLEAR))
						{
							BOOL Success=FALSE;
							
							ULONG Do = ADPCMLen - ADPCMPosition;
							ULONG Max;
							if (Bits==2) Max=(LOAD_SIZE+3)/4;
							if (Bits==3) Max=(LOAD_SIZE+7)/8*3;
							if (Do > Max) Do=Max;
							
							if (Do)
							{
								if (lb->lb_ADPCMData=AllocVec(Do, MEMF_ANY))
								{
									LONG Got;
									Seek(	LoadHandle,
											ADPCMPosition+sizeof(struct ADPCMHeader),
											OFFSET_BEGINNING );
									
									Got=Read(LoadHandle, lb->lb_ADPCMData, Do);
									
									if (Got < 0)
									{
										Message("Read error!");
									}
									else
									{
										if (Got != Do)
										{
											Message("Warning, short read! Wanted %ld, got %ld",Do,Got);
										}
										
										if (Got != 0) Success=TRUE;
										
										lb->lb_ADPCMPosition=ADPCMPosition;
										lb->lb_ADPCMLen=Got;
										ADPCMPosition+=Got;
										
										if (ADPCMPosition > ADPCMLen)
										{
											Message("WARNING! Position > Filesize!?!");
										}
										
										/* Restart loading */
										if (ADPCMPosition >= ADPCMLen)
										{
											ADPCMPosition = 0;
										}
									}
								}
							}
							
							if (Success)
							{
								ObtainSemaphore(&BufferSemaphore);
								AddTail(&BufferList,(struct Node*)lb);
								NumBuffers++;
								
								if (	NumBuffers    == QUEUE_LEN &&
										BufPlaying[0] == FALSE     &&
										BufPlaying[1] == FALSE		  )
								{
									Signal(PlayerTask,(1L<<LeftReply[0]->mp_SigBit));
									Signal(PlayerTask,(1L<<LeftReply[1]->mp_SigBit));
								}
								
								ReleaseSemaphore(&BufferSemaphore);
							}
							else
							{
								if (lb->lb_ADPCMData)
								{
									FreeVec(lb->lb_ADPCMData);
									lb->lb_ADPCMData=NULL;
								}
								FreeVec(lb);
								lb=NULL;
							}
						}
					}
				}
			}
		}
	}
	
	Forbid();
	ReleaseSemaphore(&LoadQuitSemaphore);
}


/****************************************************************************/

/* Module check routine */

ULONG __asm __saveds Check(void)
{
	BOOL Result=TRUE;
	
	struct ADPCMHeader *Header=(struct ADPCMHeader*)(DeliBase->ChkData);
	
	if (!strncmp(IDString,Header->Identifier,strlen(IDString)))
	{
		if ( Header->Identifier[5]=='2' ||
			 Header->Identifier[5]=='3' )
		{
			if (Header->Identifier[5]=='2') Bits=2;
			if (Header->Identifier[5]=='3') Bits=3;
			Frequency=Header->Frequency;
			
			Result=FALSE;
		}
	}
	return(Result);
}


/****************************************************************************/

/* Init player (alloc channels, etc...) */

ULONG __asm __saveds InitPlayer(void)
{
	BOOL Error=TRUE;
	
	if (LoadHandle=Open(DeliBase->PathArrayPtr,MODE_OLDFILE))
	{
		struct FileInfoBlock *fib;
		if (fib=(struct FileInfoBlock *)AllocMem(sizeof(struct FileInfoBlock),MEMF_ANY))
		{
			UBYTE LeftArray[2]={1,8};
			UBYTE RightArray[2]={2,4};
			UWORD i;
			
			if (ExamineFH(LoadHandle, fib))
			{
				FileSize = fib->fib_Size;
			}
			else
			{
				Seek(LoadHandle, 0, OFFSET_END);
				FileSize = Seek(LoadHandle, 0, OFFSET_BEGINNING);
			}
			
			FreeMem(fib, sizeof(struct FileInfoBlock));
			
			ADPCMLen = FileSize-sizeof(struct ADPCMHeader);
			
			for (i=0;i<3;i++)
			{
				if (!(LeftReply[i]=CreateMsgPort())) break;
				if (!(RightReply[i]=CreateMsgPort())) break;
				
				if (!(LeftAudio[i]=CreateIORequest(LeftReply[i],sizeof(struct IOAudio)))) break;
				if (!(RightAudio[i]=CreateIORequest(RightReply[i],sizeof(struct IOAudio)))) break;
				
				if (i<2) if (!(ChipBuffer[i]=AllocVec(CHIP_SIZE,MEMF_CHIP))) break;
			}
			if (i==3)
			{
				LeftAudio[0]->ioa_Request.io_Message.mn_Node.ln_Pri=127;
				LeftAudio[0]->ioa_Length=sizeof(LeftArray);
				LeftAudio[0]->ioa_Data=LeftArray;
				LeftAudio[0]->ioa_Request.io_Flags|=ADIOF_NOWAIT;
				if (!OpenDevice("audio.device",0L,(struct IORequest *)LeftAudio[0],0))
				{
					for (i=1;i<3;i++)
					{
						LeftAudio[i]->ioa_Request.io_Device=LeftAudio[0]->ioa_Request.io_Device;
						LeftAudio[i]->ioa_Request.io_Unit=LeftAudio[0]->ioa_Request.io_Unit;
						LeftAudio[i]->ioa_AllocKey=LeftAudio[0]->ioa_AllocKey;
					}
					RightAudio[0]->ioa_Length=sizeof(RightArray);
					RightAudio[0]->ioa_Request.io_Message.mn_Node.ln_Pri=127;
					RightAudio[0]->ioa_Data=RightArray;
					RightAudio[0]->ioa_Request.io_Flags|=ADIOF_NOWAIT;
					if (!OpenDevice("audio.device",0L,(struct IORequest *)RightAudio[0],0))
					{
						for (i=1;i<3;i++)
						{
							RightAudio[i]->ioa_Request.io_Device=RightAudio[0]->ioa_Request.io_Device;
							RightAudio[i]->ioa_Request.io_Unit=RightAudio[0]->ioa_Request.io_Unit;
							RightAudio[i]->ioa_AllocKey=RightAudio[0]->ioa_AllocKey;
						}
						
						ChanInit=TRUE;
						
						Error=FALSE;
					}
				}
			}
		}
	}
	
	if (Error)
	{
		EndPlayer();
	}
	
	DeliBase->SndNum=1;
	
	return(Error);
}


/****************************************************************************/

/* Clean up the Player (deallocate, etc..) */

ULONG __asm __saveds EndPlayer(void)
{
	WORD i;
	
	for (i=2;i>=0;i--)
	{
		if (i<2)
		{
			if (ChipBuffer[i])
			{
				FreeVec(ChipBuffer[i]);
				ChipBuffer[i]=NULL;
			}
		}
		
		if (RightAudio[i])
		{
			if (i==0 && RightAudio[i]->ioa_Request.io_Device)
			{
				CloseDevice(RightAudio[i]);
				RightAudio[i]->ioa_Request.io_Device=NULL;
			}
			DeleteIORequest(RightAudio[i]);
			RightAudio[i]=NULL;
		}
		
		if (RightReply[i])
		{
			DeleteMsgPort(RightReply[i]);
			RightReply[i]=NULL;
		}
		
		if (LeftAudio[i])
		{
			if (i==0 && LeftAudio[i]->ioa_Request.io_Device)
			{
				CloseDevice(LeftAudio[i]);
				LeftAudio[i]->ioa_Request.io_Device=NULL;
			}
			DeleteIORequest(LeftAudio[i]);
			LeftAudio[i]=NULL;
		}
		
		if (LeftReply[i])
		{
			DeleteMsgPort(LeftReply[i]);
			LeftReply[i]=NULL;
		}
	}
	
	if (LoadHandle)
	{
		Close(LoadHandle);
		LoadHandle=NULL;
	}
	
	ChanInit=FALSE;
	
	return(0);
}


/****************************************************************************/

/* Initialize the "Module" */

ULONG __asm __saveds InitSound(void)
{
	ADPCMPosition = 0;
	Loading = TRUE;
	Signal((struct Task*)LoadProc, SIGBREAKF_CTRL_D);
	
	return(0);
}


/****************************************************************************/

/* End sound */

ULONG __asm __saveds EndSound(void)
{
	struct LoadBuffer *lb,*nextlb;
	
	LoadingStopped=FALSE;
	
	Loading=FALSE;
	
	Forbid();
	
	while (!LoadingStopped)
	{
		Signal((struct Task*)LoadProc, SIGBREAKF_CTRL_D);
		Wait(SIGBREAKF_CTRL_D);
	}
	
	Permit();
	
	ObtainSemaphore(&BufferSemaphore);
	
	for(	lb=(struct LoadBuffer*)BufferList.lh_Head ;
			nextlb = (struct LoadBuffer*)lb->lb_Node.mln_Succ ;
			lb = nextlb	)
	{
		Remove((struct Node*)lb);
		NumBuffers--;
		
		if (lb->lb_ADPCMData)
		{
			FreeVec(lb->lb_ADPCMData);
			lb->lb_ADPCMData=NULL;
		}
		
		FreeVec(lb);
		lb=NULL;
	}
	
	ReleaseSemaphore(&BufferSemaphore);
	
	return(0);
}


/****************************************************************************/

/* Start sound */

ULONG __asm __saveds StartInt(void)
{
	PlayPosition=0;
	Playing=TRUE;
	
	if (NumBuffers == QUEUE_LEN)
	{
		Signal(PlayerTask,(1L<<LeftReply[0]->mp_SigBit));
		Signal(PlayerTask,(1L<<LeftReply[1]->mp_SigBit));
	}
	
	return(0);
}


/****************************************************************************/

/* Stop sound */

ULONG __asm __saveds StopInt(void)
{
	UWORD i;
	
	Playing=FALSE;
	
	for (i=0;i<2;i++)
	{
		if (BufPlaying[i])
		{
			AbortIO(LeftAudio[i]);
			AbortIO(RightAudio[i]);
			WaitPort(LeftReply[i]);		GetMsg(LeftReply[i]);
			WaitPort(RightReply[i]);	GetMsg(RightReply[i]);
			BufPlaying[i]=FALSE;
		}
	}
	
	NoteInit=FALSE;
	
	return(0);
}


/****************************************************************************/

/* Play Faster */

ULONG __asm __saveds Faster(void)
{
	Frequency+=100;
	
	SetPerVol();
	return(0);
}


/****************************************************************************/

/* Slower */

ULONG __asm __saveds Slower(void)
{
	Frequency-=100;
	
	SetPerVol();
	return(0);
}


/****************************************************************************/

/* Volume and Balance */

ULONG __asm __saveds VolBalance(void)
{
	LeftVolume= DeliBase->SndVol*DeliBase->SndLBal/64;
	RightVolume=DeliBase->SndVol*DeliBase->SndRBal/64;
	
	SetPerVol();
	return(0);
}


/****************************************************************************/

/* Set Period and Volume */

void SetPerVol(void)
{
	UWORD i;
	
	UWORD Period= (*(struct ExecBase**)(4))->ex_EClockFrequency*5/Frequency;
	
	for (i=0;i<3;i++)
	{
		LeftAudio[i]->ioa_Period = 
		RightAudio[i]->ioa_Period=Period;
		
		LeftAudio[i]->ioa_Volume=LeftVolume;
		RightAudio[i]->ioa_Volume=RightVolume;
	}
	
	LeftAudio[2]->ioa_Request.io_Command=
	RightAudio[2]->ioa_Request.io_Command=ADCMD_PERVOL;
	
	DoIO((struct IORequest*)LeftAudio[2]);
	DoIO((struct IORequest*)RightAudio[2]);
}


/*******************************************************************************/

/* Show a message to the user */

void __stdargs Message(UBYTE *Msg,...)
{
	va_list Arg;
	struct EasyStruct Req={sizeof(struct EasyStruct),0,"ADPCM-Player message",0,"Okay"};
	Req.es_TextFormat=Msg;
	va_start(Arg,Msg);
	
	if (IntuitionBase)
	{
		EasyRequestArgs(NULL,&Req,0,Arg);
	}
	else
	{
		VPrintf(Msg,Arg);
		Printf("\n");
	}
	
	va_end(Arg);
}
