/***************************************************/
/*                                                 */
/*                   xPlay V1.0                    */
/*           Play XPK-Compressed samples!          */
/*      Written in 1993 by Christian Buchner       */
/*                                                 */
/*     This is beer-ware, send me some bottles     */
/*      of your favourite beer! Thanks, mate!      */
/*                                                 */
/* To Do:                                          */
/* IFFONLY/S Toggle (defaults to OFF)              */
/* Stereo replay (open same file twice)            */
/*                                                 */
/*         Future ideas and enhancements:          */
/*                                                 */
/*   Ability to play Fibonacci-Coded IFF-Samples   */
/* Usability from WorkBench (by Icon Default Tool) */
/*       Workbench AppIcon for Playing Samples     */
/*      Commodity with embedded UPD-Emulation      */
/*       STOP DREAMING! - START PROGRAMMING!       */
/*                                                 */
/***************************************************/

#include <exec/exec.h>
#include <dos/dos.h>
#include <libraries/dos.h>
#include <libraries/xpk.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <clib/dos_protos.h>
#include <clib/exec_protos.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <devices/audio.h>
#include <hardware/cia.h>


extern struct DosLibrary *DOSBase;


/* Function Prototypes */

LONG PlaySample(struct DosLibrary *DOSBase,struct Library *XpkBase,UBYTE *Sample,ULONG Period,ULONG Volume,ULONG Priority,ULONG Double,ULONG Mono,ULONG Quiet,ULONG Filter,ULONG NoFilter,BOOL PC);


/* Argument Array */

struct	ArgArray
{
	char **aa_Samples;
	ULONG aa_All;
	ULONG *aa_Volume;
	ULONG *aa_Frequency;
	ULONG *aa_Period;
	ULONG *aa_Priority;
	ULONG aa_Double;
	ULONG aa_Mono;
	ULONG aa_Quiet;
	ULONG aa_Filter;
	ULONG aa_NoFilter;
	ULONG aa_PC;
};

char *VersionString = "$VER: xPlay 1.0 ("__DATE__")";
UBYTE	*Template = "SAMPLES/M/A,ALL/S,V=VOLUME/N,F=FREQUENCY/N,P=PERIOD/N,PRI=PRIORITY/N,D=DOUBLE/S,M=MONO/S,Q=QUIET/S,FL=FILTER/S,NFL=NOFILTER/S,PC/S";


/* Pattern Matching */

LONG __saveds main(void)
{
	struct Process *MyProc;
	char ProgName[32];
	struct Library *XpkBase;
	struct RDArgs *RDArgs;
	struct ArgArray	AA	= { NULL,FALSE,NULL,NULL,NULL,NULL,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE };
	char **Files;
	struct AnchorPath *AnchorPath;
	ULONG ReturnCode=RETURN_FAIL;
	UWORD Period=0;
	
	if ((MyProc=(struct Process *)FindTask(NULL))->pr_CLI==NULL)
	{
		(void)WaitPort(&MyProc->pr_MsgPort);
		Forbid();
		ReplyMsg (GetMsg(&MyProc->pr_MsgPort));
		return(RETURN_OK);
	}
	
	if (!(DOSBase=(struct DosLibrary *)OpenLibrary("dos.library",33L)))
	{
		Alert (AT_Recovery|AG_OpenLib|AO_DOSLib);
		return(RETURN_FAIL);
	}
	
	if (DOSBase->dl_lib.lib_Version<37L)
	{
		(void)Write(Output(),"This program requires OS 2.04 or newer.\n",40L);
	}
	else
	{
		if (!GetProgramName(ProgName,32L)) strcpy(ProgName,"xPack");
		
		if ((XpkBase=OpenLibrary(XPKNAME,2L))==NULL)
		{
			Printf("%s: %s V2 or newer required !\n",ProgName,XPKNAME);
		}
		else
		{
			if (!(RDArgs=ReadArgs(Template,(LONG *)&AA,0)))
			{
				PrintFault(IoErr(),ProgName);
			}
			else
			{
				if (AA.aa_Period) Period = *AA.aa_Period;
				if (AA.aa_Frequency) Period = (*(struct ExecBase**)(4))->ex_EClockFrequency*5/(*AA.aa_Frequency);
				
				if (!(Files=AA.aa_Samples))
				{
					ReturnCode=RETURN_OK;
				}
				else
				{
					if (!(AnchorPath=(struct AnchorPath *)AllocVec(sizeof(struct AnchorPath)+256L,MEMF_PUBLIC|MEMF_CLEAR)))
					{
						PrintFault(ERROR_NO_FREE_STORE,ProgName);
					}
					else
					{
						AnchorPath->ap_BreakBits=SIGBREAKF_CTRL_C;
						AnchorPath->ap_Strlen=256;
						
						while (*Files)
						{
							LONG RetVal;
							
							RetVal=MatchFirst(*Files,AnchorPath);
							while(!RetVal)
							{
								if (AnchorPath->ap_Info.fib_DirEntryType>0L)
								{
									if (((AnchorPath->ap_Flags&APF_DIDDIR)==NULL)&&AA.aa_All)
									{
										AnchorPath->ap_Flags|=APF_DODIR;
									}
									AnchorPath->ap_Flags&=~APF_DIDDIR;
								}
								else
								{
									ReturnCode=PlaySample(
										DOSBase,
										XpkBase,
										AnchorPath->ap_Buf,
										Period ? Period : 0,
										AA.aa_Volume   ? *AA.aa_Volume   :0,
										AA.aa_Priority ? *AA.aa_Priority :0,
										AA.aa_Double,
										AA.aa_Mono,
										AA.aa_Quiet,
										AA.aa_Filter,
										AA.aa_NoFilter,
										AA.aa_PC);
									
									if (ReturnCode)
									{
										MatchEnd(AnchorPath);
										goto FreeAnchor;
									}
								}
								RetVal=MatchNext(AnchorPath);
							}
							MatchEnd(AnchorPath);
							
							if (RetVal!=ERROR_NO_MORE_ENTRIES)
							{
								Printf("%s: %s - ",ProgName,*Files);
								PrintFault(RetVal,NULL);
								goto FreeAnchor;
							}
							else Files++;
						}
						ReturnCode=RETURN_OK;
						
FreeAnchor:				FreeVec((APTR)AnchorPath);
					}
				}
				FreeArgs(RDArgs);
			}
			CloseLibrary(XpkBase);
		}
	}
	CloseLibrary(&DOSBase->dl_lib);
	return(ReturnCode);
}


/* ---------- Voice8Header ---------------------------------------------*/

struct Voice8Header
{
	ULONG oneShotHiSamples;		/* # samples in the high octave 1-shot part */
	ULONG repeatHiSamples;		/* # samples in the high octave repeat part */
	ULONG samplesPerHiCycle;	/* # samples/cycle in high octave, else 0 */
	UWORD samplesPerSec;		/* data sampling rate */
	UBYTE ctOctave;				/* # of octaves of waveforms */
	UBYTE sCompression;			/* data compression technique used */
	ULONG volume;				/* playback nominal volume from 0 to 0x10000
								 * (full volume). Map this value into
								 * the output hardware's dynamic range.
								 */
};

/* sCompression: Choice of compression algorithm applied to the samples. */
#define sCmpNone		0		/* not compressed */
#define sCmpFibDelta	1		/* Fibonacci-delta encoding (Appendix C) */
								/* Could be more kinds in the future. */


/* Static Data */

UBYTE Channels[2][2] = { 1,8, 2,4 };


/* Access to CIA registers */

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


/* No. of Sample Buffers */

#define NumBuffers 2


/* Play a Sample */

LONG PlaySample(struct DosLibrary *DOSBase,struct Library *XpkBase,UBYTE *Sample,ULONG Period,ULONG Volume,ULONG Priority,ULONG Double,ULONG Mono,ULONG Quiet,ULONG Filter,ULONG NoFilter,BOOL PC)
{
	LONG ReturnCode=RETURN_ERROR;
	struct XpkFH *xfh=NULL;
	struct TagItem Tag[10];
	UBYTE ErrorMsg[XPKERRMSGSIZE];
	ULONG BufSize;
	UBYTE *Buffer[NumBuffers]={0,0};
	LONG Got[NumBuffers];
	struct MsgPort *ReplyPort[NumBuffers][2][2]={0,0,0,0,0,0,0,0};
	struct IOAudio *AudIO[NumBuffers][2][2]={0,0,0,0,0,0,0,0};
	LONG b,s,c;
	LONG NumSides = (Mono ? 1 : 2);
	LONG NumChannels = (Double ? 2 : 1);
	BOOL Flag[NumBuffers]={FALSE,FALSE};
	BOOL Ende=FALSE;
	ULONG Offset=0;
	ULONG Length;
	ULONG ToPlay;
	ULONG Position=0;
	ULONG Pos[NumBuffers];
	LONG i;
	UBYTE *p;
	
	Tag[0].ti_Tag=XPK_InName;    Tag[0].ti_Data=(ULONG)Sample;
	Tag[1].ti_Tag=XPK_PassThru;  Tag[1].ti_Data=(ULONG)TRUE;
	Tag[2].ti_Tag=XPK_GetError;  Tag[2].ti_Data=(ULONG)ErrorMsg;
	Tag[3].ti_Tag=TAG_DONE;
	
	if (XpkOpen(&xfh,Tag))
	{
		Printf("Error opening %s: %s\n",Sample,ErrorMsg);
		goto CleanUp;
	}
	BufSize=xfh->NLen;
	for (b=0;b<NumBuffers;b++)
	{
		if (!(Buffer[b]=AllocVec(BufSize+XPK_MARGIN,MEMF_CHIP)))
		{
			Printf("Not enough chip memory for sample buffer %ld.\n",b+1);
			goto CleanUp;
		}
	}
	for (b=0;b<NumBuffers;b++)
	{
		for (s=0;s<NumSides;s++)
		{
			for (c=0;c<NumChannels;c++)
			{
				if(!(ReplyPort[b][s][c] = CreateMsgPort())) goto CleanUp;
				if(!(AudIO[b][s][c] = (struct IOAudio *)CreateIORequest(ReplyPort[b][s][c],sizeof(struct IOAudio)))) goto CleanUp;
				AudIO[b][s][c]->ioa_Request.io_Message.mn_Node.ln_Pri=Priority ? Priority : 0;
				if (!b && !s && !c)
				{
					if (OpenDevice("audio.device",0L,(struct IORequest *)AudIO[b][s][c],0L))
					{
						Printf("Error opening audio.device!\n");
						goto CleanUp;
					}
				}
				else
				{
					AudIO[b][s][c]->ioa_Request.io_Device = AudIO[0][0][0]->ioa_Request.io_Device;
				}
				if (b==0)
				{
					AudIO[b][s][c]->ioa_Data = Channels[s];
					AudIO[b][s][c]->ioa_Length = 4/NumSides;
					AudIO[b][s][c]->ioa_Request.io_Flags = ADIOF_NOWAIT;
					AudIO[b][s][c]->ioa_Request.io_Command = ADCMD_ALLOCATE;
					BeginIO((struct IORequest *)AudIO[b][s][c]);
					if (WaitIO((struct IORequest *)AudIO[b][s][c]))
					{
						Printf("Can't get audio channels.\n");
						goto CleanUp;
					}
				}
				else
				{
					AudIO[b][s][c]->ioa_AllocKey = AudIO[0][s][c]->ioa_AllocKey;
					AudIO[b][s][c]->ioa_Request.io_Unit = AudIO[0][s][c]->ioa_Request.io_Unit;
				}
			}
		}
	}
	
	Length=ToPlay=xfh->ULen;
	
	if (!Quiet) Printf("\r\33[KPlaying %s\r",Sample);
	
	if (Filter)
	{
		ciaa->ciapra &= ~(CIAF_LED);
	}
	if (NoFilter)
	{
		ciaa->ciapra |= CIAF_LED;
	}
	
	for (b=0 ; Flag[b] || (!Ende) ; b=(b+1)%NumBuffers)
	{
		if (Flag[b])
		{
			ULONG Percentage;
			for (s=0;s<NumSides;s++)
			{
				for (c=0;c<NumChannels;c++)
				{
					if (Wait((1L<<ReplyPort[b][s][c]->mp_SigBit)|SIGBREAKF_CTRL_C)&SIGBREAKF_CTRL_C)
					{
						if (!Quiet) Printf("\n");
						PrintFault(ERROR_BREAK,NULL);
						goto CleanUp;
					}
					if (WaitIO((struct IORequest*)AudIO[b][s][c]))
					{
						Printf("\nChannels have been stolen.\n");
						goto CleanUp;
					}
				}
			}
			if ((Percentage=100*Pos[b]/Length)<100)
			{
				if (!Quiet) Printf("\rPlaying %s (%2ld%%)\r",Sample,Percentage);
			}
		}
		
		if ((Got[b]=XpkRead(xfh, Buffer[b], BufSize))<0)
		{
			Printf("\nError reading from %s: %s\n",Sample,ErrorMsg);
			goto CleanUp;
		}
		
		if (!Position)
		{
			if (*(ULONG*)(Buffer[b]) == 'FORM')
			{
				if (*(ULONG*)(Buffer[b]+8) == '8SVX')
				{
					UBYTE *Chunk=Buffer[b]+12;
					
					for (;;)
					{
						ULONG ChunkLen=((*(ULONG*)(Chunk+4))+1) & 0xFFFFFFFE;
						
						if (*(ULONG*)(Chunk) == 'VHDR')
						{
							struct Voice8Header *VHDR=(struct Voice8Header*)(Chunk+8);
							
							if (!Volume)
							{
								Volume=VHDR->volume*64/0x10000;
							}
							if (!Period)
							{
								Period=(*(struct ExecBase**)(4))->ex_EClockFrequency*5/VHDR->samplesPerSec;
							}
							if (VHDR->sCompression != sCmpNone)
							{
								Printf("\nError! Can't play a compressed sample.\n");
								goto CleanUp;
							}
						}
						
						if (*(ULONG*)(Chunk) == 'BODY')
						{
							Length=ToPlay=ChunkLen;
							Offset=(ULONG)Chunk+8-(ULONG)Buffer[b];
							break;
						}
						
						Chunk+=ChunkLen+8;
					}
				}
			}
		}
		
		if (PC) for (i=0,p=Buffer[b];i<Got[b];i++) (*p++) += 0x80;
		
		Pos[b]=(Position+=(Got[b]-=Offset));
		
		if (Got[b] && ToPlay)
		{
			ULONG PlayLength=(ToPlay >= Got[b] ? Got[b] : ToPlay);
			
			for (s=0;s<NumSides;s++)
			{
				for (c=0;c<NumChannels;c++)
				{
					AudIO[b][s][c]->ioa_Data = Buffer[b]+Offset;
					AudIO[b][s][c]->ioa_Length = PlayLength;
					AudIO[b][s][c]->ioa_Period = Period ? Period : 200;
					AudIO[b][s][c]->ioa_Volume = Volume ? Volume : 64;
					AudIO[b][s][c]->ioa_Cycles = 1;
					AudIO[b][s][c]->ioa_Request.io_Flags = ADIOF_PERVOL|ADIOF_NOWAIT;
					AudIO[b][s][c]->ioa_Request.io_Command = CMD_WRITE;
					SetSignal(0L,1L<<ReplyPort[b][s][c]->mp_SigBit);
				}
			}
			Forbid();
			for (s=0;s<NumSides;s++)
			{
				for (c=0;c<NumChannels;c++)
				{
					BeginIO((struct IORequest*)AudIO[b][s][c]);
				}
			}
			Permit();
			
			Offset=0;
			ToPlay-=PlayLength;
			Flag[b]=TRUE;
		}
		else
		{
			Flag[b]=FALSE;
			Ende=TRUE;
		}
	}
	if (!Quiet) Printf("\rPlayed  %s - OK \n",Sample);
	ReturnCode=RETURN_OK;
	
CleanUp:
	for (b=0;b<NumBuffers;b++)
	{
		for (s=0;s<NumSides;s++)
		{
			for (c=0;c<NumChannels;c++)
			{
				if (AudIO[b][s][c])
				{
					if (AudIO[b][s][c]->ioa_Request.io_Device)
					{
						if (b==0)
						{
							AbortIO((struct IORequest*)AudIO[b][s][c]);
							WaitIO((struct IORequest*)AudIO[b][s][c]);
							AudIO[b][s][c]->ioa_Request.io_Command=ADCMD_FREE;
							DoIO((struct IORequest*)AudIO[b][s][c]);
						}
						if (b==NumBuffers-1 && s==NumSides-1 && c==NumChannels-1)
						{
							CloseDevice((struct IORequest*)AudIO[b][s][c]);
						}
					}
					DeleteIORequest(AudIO[b][s][c]);
				}
				if (ReplyPort[b][s][c]) DeleteMsgPort(ReplyPort[b][s][c]);
			}
		}
	}
	
	for (b=0;b<NumBuffers;b++)
	{
		if (Buffer[b]) FreeVec(Buffer[b]);
	}
	
	if (xfh) XpkClose(xfh);
	
	return(ReturnCode);
}
