 
/*        Stand-Alone Player for ADPCM audio samples            */
/* Written in 1995 by Christian Buchner. This is Public Domain. */
/* Also look out for xPlay, also available on AmiNet */

/* Note: TAB SIZE = 4 */

/* History:

   V1.1: Now recognizes when channels have been stolen
         Uses WaitIO() instead of WaitPort()/GetMsg() pair.
*/

/* Includes */

#include <clib/dos_protos.h>
#include <clib/exec_protos.h>
#include <clib/alib_protos.h>
#include <pragmas/dos_pragmas.h>
#include <pragmas/exec_pragmas.h>

#include <libraries/dos.h>
#include <dos/rdargs.h>
#include <utility/tagitem.h>
#include <devices/audio.h>
#include <exec/execbase.h>
#include <exec/memory.h>
#include <string.h>
#include <hardware/cia.h>


/* Version string */

UBYTE Version[]="$VER: PlayADPCM 1.1 "__AMIGADATE__" by Christian Buchner";


/* Prototypes */

BOOL PlayADPCM(struct MyVars *mv, UBYTE *Filename, ULONG Size, ULONG Filter, ULONG NoFilter, ULONG Quiet);
BOOL OpenAudio(struct MyVars *mv, ULONG Filter, ULONG NoFilter);
void CloseAudio(struct MyVars *mv);
void AbortPlay(struct MyVars *mv);
void WaitPlay(struct MyVars *mv);


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	);


/* Replay Buffer size */

#define CHIP_SIZE 16384


/* CLI Arguments */

UBYTE Template[]="FILES/M/A,ALL/S,FL=FILTER/S,NFL=NOFILTER/S,QUIET/S";

struct ArgArray
{
	UBYTE **aa_Files;
	ULONG aa_All;
	ULONG aa_Filter;
	ULONG aa_NoFilter;
	ULONG aa_Quiet;
};


/* Library Bases */

struct DosLibrary *DOSBase;


/* Local Variables */

struct MyVars
{
	struct Process *MyProc;
	
	struct MsgPort *LeftReply[2];
	struct MsgPort *RightReply[2];
	struct IOAudio *LeftAudio[2];
	struct IOAudio *RightAudio[2];
	
	BOOL Playing;
	
	UBYTE *ChipBuffer[2];
	
	BOOL BufPlaying[2];
};


/* Access to CIA registers */

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


/* CLI interface and Pattern Matching */

LONG __saveds main(void)
{
	struct MyVars *mv;
	UBYTE ProgName[60];
	struct RDArgs *RDArgs;
	struct ArgArray AA={NULL,FALSE,FALSE,FALSE,FALSE};
	char **Files;
	struct AnchorPath *AnchorPath;
	ULONG ReturnCode=RETURN_FAIL;
	
	if (mv=AllocVec(sizeof(struct MyVars),MEMF_ANY|MEMF_CLEAR))
	{
		if (DOSBase=(struct DosLibrary*)OpenLibrary("dos.library",37))
		{
			if (!GetProgramName(ProgName,sizeof(ProgName))) strcpy(ProgName,"PlayADPCM");
			
			if (!(RDArgs=ReadArgs(Template,(LONG *)&AA,0)))
			{
				PrintFault(IoErr(),ProgName);
			}
			else
			{
				if (Files=AA.aa_Files)
				{
					if (!(AnchorPath=(struct AnchorPath *)AllocVec(sizeof(struct AnchorPath)+256,MEMF_PUBLIC|MEMF_CLEAR)))
					{
						PrintFault(ERROR_NO_FREE_STORE,ProgName);
					}
					else
					{
						ReturnCode=RETURN_OK;
						
						AnchorPath->ap_BreakBits=SIGBREAKF_CTRL_C;
						AnchorPath->ap_Strlen=256;
						
						while ((!ReturnCode) && *Files)
						{
							LONG RetVal;
							
							RetVal=MatchFirst(*Files,AnchorPath);
							while(!RetVal)
							{
								if (AnchorPath->ap_Info.fib_DirEntryType>0L)
								{
									if ((!(AnchorPath->ap_Flags&APF_DIDDIR))&&AA.aa_All)
									{
										AnchorPath->ap_Flags|=APF_DODIR;
									}
									AnchorPath->ap_Flags&=~APF_DIDDIR;
								}
								else
								{
									if (PlayADPCM(mv, AnchorPath->ap_Buf, AnchorPath->ap_Info.fib_Size, AA.aa_Filter, AA.aa_NoFilter, AA.aa_Quiet))
									{
										RetVal=ERROR_BREAK;
										break;
									}
								}
								RetVal=MatchNext(AnchorPath);
							}
							MatchEnd(AnchorPath);
							
							if (RetVal==ERROR_BREAK)
							{
								PrintFault(RetVal,NULL);
								ReturnCode=RETURN_WARN;
							}
							else
							{
								if (RetVal!=ERROR_NO_MORE_ENTRIES)
								{
									PrintFault(RetVal,*Files);
									ReturnCode=RETURN_ERROR;
								}
							}
							Files++;
						}
						FreeVec((APTR)AnchorPath);
					}
				}
				FreeArgs(RDArgs);
			}
			CloseLibrary((struct Library*)DOSBase);
		}
		FreeVec(mv);
	}
}


/* The primitive sample header */

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


/* The playback routine */

BOOL PlayADPCM(struct MyVars *mv, UBYTE *Filename, ULONG Size, ULONG Filter, ULONG NoFilter, ULONG Quiet)
{
	BOOL Break=FALSE;
	
	BPTR File;
	struct ADPCMHeader ADPCMHeader;
	
	if (!(File=Open(Filename,MODE_OLDFILE)))
	{
		PrintFault(IoErr(),Filename);
	}
	else
	{
		/* Read in sample header */
		if (Read(File,&ADPCMHeader,sizeof(struct ADPCMHeader))==sizeof(struct ADPCMHeader))
		{
			if (strncmp("ADPCM",ADPCMHeader.Identifier,5))
			{
				Printf("%s: Not ADPCM format!\n",Filename);
			}
			else
			{
				Size-=sizeof(ADPCMHeader);
				
				/* Check if it is a supported ADPCM format */
				if (ADPCMHeader.Identifier[5] != '2' && ADPCMHeader.Identifier[5] != '3')
				{
					Printf("%s: Unsupported ADPCM format!\n",Filename);
				}
				else
				{
					ULONG Bits;
					
					/* Retrieve the number of compression bits */
					if (ADPCMHeader.Identifier[5] == '2') Bits=2;
					if (ADPCMHeader.Identifier[5] == '3') Bits=3;
					
					if (!OpenAudio(mv,Filter,NoFilter))
					{
						Printf("%s: Can't get audio channels!\n",Filename);
					}
					else
					{
						UBYTE *ADPCMBuffer;
						ULONG ADPCMSize;
						
						if (Bits==2) ADPCMSize=(CHIP_SIZE+3)/4;
						if (Bits==3) ADPCMSize=(CHIP_SIZE+7)/8*3;
						
						if (!(ADPCMBuffer=AllocVec(ADPCMSize, MEMF_ANY|MEMF_CLEAR)))
						{
							Printf("%s: Out of memory!\n",Filename);
						}
						else
						{
							struct Process *MyProc;
							ULONG Position=0;
							ULONG JoinCode=0;
							BOOL ProcActive=TRUE;
							ULONG Signals;
							UWORD i;
							LONG ChipMax, Left, Do, DMALen;
							BOOL Aborted=FALSE;
							
							if (!Quiet) Printf("\rPlaying %s",Filename);
							
							MyProc=(struct Process*)FindTask(NULL);
							
							Signal(MyProc,(1L<<mv->LeftReply[0]->mp_SigBit));
							Signal(MyProc,(1L<<mv->LeftReply[1]->mp_SigBit));
							
							while(ProcActive)
							{
								ULONG SigMask = SIGBREAKF_CTRL_C |
												(1L<<mv->LeftReply[0]->mp_SigBit) | 
												(1L<<mv->LeftReply[1]->mp_SigBit) ;
								
								Signals=Wait(SigMask);
								
								if (Signals & SIGBREAKF_CTRL_C)
								{
									Printf("\n");
									
									AbortPlay(mv);
									
									Break=TRUE;
									Aborted=TRUE;
									ProcActive=FALSE;
								}
								
								for (i=0;ProcActive && i<2;i++)
								{
									if (Signals & (1L<<mv->LeftReply[i]->mp_SigBit))
									{
										if (mv->BufPlaying[i])
										{
											BYTE LError, RError;
											
											LError=WaitIO(mv->LeftAudio[i]);
											RError=WaitIO(mv->RightAudio[i]);
											mv->BufPlaying[i]=FALSE;
											
											if (LError || RError)
											{
												Printf("\r%s: Channels have been stolen!\n",Filename);
												
												AbortPlay(mv);
												
												Aborted=TRUE;
												ProcActive=FALSE;
												break;
											}
										}
										
										if (Bits==2) ChipMax = (CHIP_SIZE+3)/4;
										if (Bits==3) ChipMax = (CHIP_SIZE+7)/8*3;
										
										if (Position>=Size)
										{
											Position=0;
											
											ProcActive=FALSE;
											break;
										}
										
										Left = Size-Position;
										Do = Left < ChipMax ? Left : ChipMax;
										
										if (Do>0)
										{
											LONG Got;
											
											if (!Quiet) Printf("\rPlaying %s (%02ld%%)",Filename,100*Position/Size);
											
											if (Got=Read(File, ADPCMBuffer, Do) != Do)
											{
												if (Got<0) PrintFault(IoErr(),Filename);
												else Printf("\r%s: Error, short read!\n",Filename);
												Aborted=TRUE;
												ProcActive=FALSE;
												break;
											}
											
											if (Bits==2) JoinCode=DecompressADPCM2(ADPCMBuffer, Do, mv->ChipBuffer[i], JoinCode);
											if (Bits==3) JoinCode=DecompressADPCM3(ADPCMBuffer, Do, mv->ChipBuffer[i], JoinCode);
											
											Position+=Do;
											
											if (Bits==2) DMALen = Do*4;
											if (Bits==3) DMALen = Do*8/3;
											
											mv->LeftAudio[i]->ioa_Data =
											mv->RightAudio[i]->ioa_Data = mv->ChipBuffer[i];
											
											mv->LeftAudio[i]->ioa_Length =
											mv->RightAudio[i]->ioa_Length = DMALen;
											
											mv->LeftAudio[i]->ioa_Period = 
											mv->RightAudio[i]->ioa_Period=(*(struct ExecBase**)(4))->ex_EClockFrequency*5/ADPCMHeader.Frequency;
											
											mv->LeftAudio[i]->ioa_Volume=64;
											mv->RightAudio[i]->ioa_Volume=64;
											
											mv->LeftAudio[i]->ioa_Cycles=
											mv->RightAudio[i]->ioa_Cycles=1;
											
											mv->LeftAudio[i]->ioa_Request.io_Flags|=ADIOF_PERVOL;
											mv->RightAudio[i]->ioa_Request.io_Flags|=ADIOF_PERVOL;
											
											mv->LeftAudio[i]->ioa_Request.io_Command=
											mv->RightAudio[i]->ioa_Request.io_Command=CMD_WRITE;
											
											Forbid();
											BeginIO(mv->LeftAudio[i]);
											BeginIO(mv->RightAudio[i]);
											mv->BufPlaying[i]=TRUE;
											Permit();
										}
									}
								}
							}
							
							WaitPlay(mv);
							
							if ((!Quiet) && (!Aborted)) Printf("\rPlayed  %s - OK \n",Filename);
							
							FreeVec(ADPCMBuffer);
						}
						CloseAudio(mv);
					}
				}
			}
		}
		Close(File);
	}
	return(Break);
}


/* Allocate audio channels */

BOOL OpenAudio(struct MyVars *mv, ULONG Filter, ULONG NoFilter)
{
	BOOL Success=FALSE;
	UWORD i;
	
	UBYTE LeftArray[2]={1,8};
	UBYTE RightArray[2]={2,4};
	
	for (i=0;i<2;i++)
	{
		if (!(mv->LeftReply[i]=CreateMsgPort())) break;
		if (!(mv->RightReply[i]=CreateMsgPort())) break;
		
		if (!(mv->LeftAudio[i]=CreateIORequest(mv->LeftReply[i],sizeof(struct IOAudio)))) break;
		if (!(mv->RightAudio[i]=CreateIORequest(mv->RightReply[i],sizeof(struct IOAudio)))) break;
		
		if (!(mv->ChipBuffer[i]=AllocVec(CHIP_SIZE,MEMF_CHIP))) break;
	}
	if (i==2)
	{
		mv->LeftAudio[0]->ioa_Request.io_Message.mn_Node.ln_Pri=0;
		mv->LeftAudio[0]->ioa_Length=sizeof(LeftArray);
		mv->LeftAudio[0]->ioa_Data=LeftArray;
		mv->LeftAudio[0]->ioa_Request.io_Flags|=ADIOF_NOWAIT;
		if (!OpenDevice("audio.device",0L,(struct IORequest *)mv->LeftAudio[0],0))
		{
			mv->LeftAudio[1]->ioa_Request.io_Device=mv->LeftAudio[0]->ioa_Request.io_Device;
			mv->LeftAudio[1]->ioa_Request.io_Unit=mv->LeftAudio[0]->ioa_Request.io_Unit;
			mv->LeftAudio[1]->ioa_AllocKey=mv->LeftAudio[0]->ioa_AllocKey;
			
			mv->RightAudio[0]->ioa_Length=sizeof(RightArray);
			mv->RightAudio[0]->ioa_Request.io_Message.mn_Node.ln_Pri=0;
			mv->RightAudio[0]->ioa_Data=RightArray;
			mv->RightAudio[0]->ioa_Request.io_Flags|=ADIOF_NOWAIT;
			if (!OpenDevice("audio.device",0L,(struct IORequest *)mv->RightAudio[0],0))
			{
				mv->RightAudio[1]->ioa_Request.io_Device=mv->RightAudio[0]->ioa_Request.io_Device;
				mv->RightAudio[1]->ioa_Request.io_Unit=mv->RightAudio[0]->ioa_Request.io_Unit;
				mv->RightAudio[1]->ioa_AllocKey=mv->RightAudio[0]->ioa_AllocKey;
				
				mv->BufPlaying[0]=mv->BufPlaying[1]=FALSE;
				
				if (Filter)
				{
					ciaa->ciapra &= ~(CIAF_LED);
				}
				if (NoFilter)
				{
					ciaa->ciapra |= CIAF_LED;
				}
				
				Success=TRUE;
			}
		}
	}
	
	if (!Success)
	{
		CloseAudio(mv);
	}
	
	return(Success);
}


/* Abort playing */

void AbortPlay(struct MyVars *mv)
{
	WORD i;
	
	for (i=0;i<2;i++)
	{
		if (mv->BufPlaying[i])
		{
			AbortIO(mv->LeftAudio[i]);
			AbortIO(mv->RightAudio[i]);
		}
	}
	
	WaitPlay(mv);
}


/* Wait for audio requests */

void WaitPlay(struct MyVars *mv)
{
	WORD i;
	
	for (i=0;i<2;i++)
	{
		if (mv->BufPlaying[i])
		{
			WaitIO(mv->LeftAudio[i]);
			WaitIO(mv->RightAudio[i]);
			mv->BufPlaying[i]=FALSE;
		}
	}
}


/* Close the audio channels */

void CloseAudio(struct MyVars *mv)
{
	WORD i;
	
	for (i=0;i<2;i++)
	{
		if (mv->BufPlaying[i])
		{
			AbortIO(mv->LeftAudio[i]);
			AbortIO(mv->RightAudio[i]);
			WaitIO(mv->LeftAudio[i]);
			WaitIO(mv->RightAudio[i]);
			mv->BufPlaying[i]=FALSE;
		}
	}
	
	for (i=1;i>=0;i--)
	{
		if (mv->ChipBuffer[i])
		{
			FreeVec(mv->ChipBuffer[i]);
			mv->ChipBuffer[i]=NULL;
		}
		
		if (mv->RightAudio[i])
		{
			if (i==0 && mv->RightAudio[i]->ioa_Request.io_Device)
			{
				CloseDevice(mv->RightAudio[i]);
				mv->RightAudio[i]->ioa_Request.io_Device=NULL;
			}
			DeleteIORequest(mv->RightAudio[i]);
			mv->RightAudio[i]=NULL;
		}
		
		if (mv->RightReply[i])
		{
			DeleteMsgPort(mv->RightReply[i]);
			mv->RightReply[i]=NULL;
		}
		
		if (mv->LeftAudio[i])
		{
			if (i==0 && mv->LeftAudio[i]->ioa_Request.io_Device)
			{
				CloseDevice(mv->LeftAudio[i]);
				mv->LeftAudio[i]->ioa_Request.io_Device=NULL;
			}
			DeleteIORequest(mv->LeftAudio[i]);
			mv->LeftAudio[i]=NULL;
		}
		
		if (mv->LeftReply[i])
		{
			DeleteMsgPort(mv->LeftReply[i]);
			mv->LeftReply[i]=NULL;
		}
	}
}
