
/**************************************************************************/
/*			     DSound V0.91a				  */
/*	 Copyright 1991 by Dave Schreiber, All Rights Reserved		  */
/*									  */
/* To compile:								  */
/*    lc -Lcd -v DSound 						  */
/*									  */
/* Revision history:							  */
/*    V0.91a - First release (September 11, 1991)                         */
/**************************************************************************/

#include <exec/types.h>
#include <exec/exec.h>
#include <devices/audio.h>
#include <dos/dos.h>
#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>
#include <graphics/gfxbase.h>
#include "dsound.h"

#include <clib/intuition_protos.h>
#include <clib/exec_protos.h>

UBYTE rightAMap[]={2,4};
UBYTE leftAMap[]={1,8};
UBYTE eitherAMap[]={1,2,4,8};
UBYTE *allocationMap=eitherAMap;
char filename[140];

#define BUF_SIZE 30000

void InterpretArgs(int argc,char *argv[]);
BOOL noFilter=FALSE;
UBYTE volume=0;
UWORD speed=0;
ULONG bufSize=BUF_SIZE;

void filter_on(void);
void filter_off(void);

char *version="$VER: DSound V0.91a (11.9.91)";
char *copyright="Copyright 1991 by Dave Schreiber, All Rights Reserved";

struct IntuitionBase *IntuitionBase=NULL;
struct GfxBase *GfxBase=NULL;

struct Window *window=NULL;

struct NewWindow newWindow=
{
   100,50,300,10,-1,-1,CLOSEWINDOW,WINDOWCLOSE|WINDOWDRAG|WINDOWDEPTH,
   NULL,NULL,"<-- Click to stop (DSound V0.91)",NULL,NULL,1,1,1000,1000,
   WBENCHSCREEN
};

BPTR file=NULL;

main(int argc,char *argv[])
{
   struct IOAudio *iob1,*iob2;
   struct Voice8Header vhdr;
   UBYTE foo2[5];
   ULONG len;
   ULONG toRead;
   BOOL done=FALSE;

   filename[0]=NULL;

   GfxBase=(struct GfxBase *)OpenLibrary("graphics.library",0L);

   IntuitionBase=(struct IntuitionBase *)OpenLibrary("intuition.library",0L);

   if(GfxBase==NULL || IntuitionBase==NULL)
      cleanup(50);

   InterpretArgs(argc,argv);

   if(filename[0]==NULL)
   {
      WriteMsg("Please specify a filename\n");
      cleanup(75);
   }

   /*Open the file*/
   file=Open(filename,MODE_OLDFILE);
   if(file==NULL)
   {
      WriteMsg("Couldn't open the file\n");
      cleanup(100);
   }

   newWindow.Height=IntuitionBase->ActiveScreen->Font->ta_YSize+3;

   window=OpenWindow(&newWindow);

   if(window==NULL)
      cleanup(110);

   /*Read the header*/
   Position(file,"VHDR");
   Read(file,foo2,4);
   Read(file,&vhdr,sizeof(struct Voice8Header));

   /*Check for compression*/
   if(vhdr.sCompression!=0)
   {
      WriteMsg("The sound sample is compressed\n");
      cleanup(400);
   }

   /*Find the BODY*/
   Position(file,"BODY");
   Read(file,foo2,4);

   /*Get the length of the sample*/
   len=vhdr.oneShotHiSamples;

   /*Get the first audio channel*/
   iob1=GetAudioChannel(bufSize);
   if(iob1==NULL)
   {
      WriteMsg("Couldn't create the first buffer\n");
      cleanup(150);
   }

   /* If the user didn't specify a volume, get it from the VHDR */
   if(volume==0)
      volume=(vhdr.volume>>10);

   /* If the VHDR gave a volume of zero, use maximum volume*/
   if(volume==0)
      volume=64;

   /* Get the samples/sec rate (either the rate given by the user, or the*/
   /* rate found in the VHDR) */
   if(speed==0)
      speed=1000000000/(vhdr.samplesPerSec*279);
   else
      speed=1000000000/(speed*279);

   InitAudioChannel(iob1,volume,speed);

   /*Get the 2nd audio channel*/
   iob2=DuplicateAudioChannel(iob1);

   if(iob2==NULL)
   {
      FreeAudioChannel(iob1);
      WriteMsg("Couldn't create the second buffer");
      cleanup(175);
   }

   /* Load the first buffer*/
   toRead=MIN(len,bufSize);
   LoadAudioBuffer(file,iob1,toRead);
   len-=toRead;
   iob1->ioa_Length=toRead;

   /* Make sure there's enough data so that we have something to put in */
   /* the second buffer */
   if(len!=0)
   {
      toRead=MIN(len,bufSize);
      LoadAudioBuffer(file,iob2,toRead);
      len-=toRead;
      iob2->ioa_Length=toRead;
   }
   else
      /* It appears that the entire sound sample is small enough to */
      /* fit into the first buffer, so don't play the second */
      iob2->ioa_Length=0;

   /*Switch off the filter, if necessary */
   if(noFilter)
      filter_off();

   /*And queue up the play requests*/
   BeginIO((struct ioRequest *)iob1);
   if(iob2->ioa_Length!=0)
      BeginIO((struct ioRequest *)iob2);

   /* If the sound sample was small enough to fit into the two buffers, */
   /* play them then finish up */
   if(len==0 || GetMsg(window->UserPort)!=NULL)
   {
      WaitIO((struct ioRequest *)iob1);
      if(iob2->ioa_Length!=0)
	 WaitIO((struct ioRequest *)iob2);
      done=TRUE;
   }
   /*Otherwise, play those samples then read more from disk*/

   /*Loop while there's stuff to read*/
   while(!done)
   {
      /*Fill the first buffer*/
      WaitIO((struct ioRequest *)iob1);

      toRead=MIN(len,bufSize);

      if(toRead==0 || GetMsg(window->UserPort)!=NULL)
      {
	 /*If there's no stuff left to read, wait 'till the second buffer*/
	 /*finishes, then quit*/
	 WaitIO((struct ioRequest *)iob2);
	 done=TRUE;
	 break;
      }

      LoadAudioBuffer(file,iob1,toRead);
      len-=toRead;

      /*Play the first buffer*/
      BeginIO((struct ioRequest *)iob1);

      /*Wait for the second buffer to finish*/
      WaitIO((struct ioRequest *)iob2);

      toRead=MIN(len,bufSize);

      if(toRead==0 || GetMsg(window->UserPort)!=NULL)
      {
	 /*If there's no stuff left to read, wait 'till the first buffer*/
	 /*finishes, then quit*/
	 WaitIO((struct ioRequest *)iob1);
	 done=TRUE;
	 break;
      }

      /*Reload it*/
      LoadAudioBuffer(file,iob2,toRead);
      len-=toRead;

      /*Play it*/
      BeginIO((struct ioRequest *)iob2);
   }

   /*Switch the filter off if it was switched off*/
   if(noFilter)
      filter_on();

   /*Restore the buffer lengths, so that FreeAudio() channel, etc., knows*/
   /*how much memory to free*/
   iob1->ioa_Length=iob2->ioa_Length=bufSize;
   FreeAudioChannel(iob1);
   DeleteDuplication(iob2);

   /*Free allocated resources and exit*/
   cleanup(0);
}


// Get an audio channel
struct IOAudio *GetAudioChannel(ULONG bufferSize)
{
   struct IOAudio *aIOB;
   void *audioBuf;
   struct Port *aPort;

   aPort=(struct Port *)CreatePort("dsound",0);
   if(aPort==NULL)
      return(NULL);

   /* Allocate the chip memory buffer for the channel */
   audioBuf=(void *)AllocMem(bufferSize,MEMF_CHIP);
   if(audioBuf==NULL)
   {
      DeletePort(aPort);
      return(NULL);
   }

   /* Allocate an IOAudio structure*/
   aIOB=(struct IOAudio *)AllocMem(sizeof(struct IOAudio),MEMF_PUBLIC|MEMF_CLEAR);
   if(aIOB==NULL)
   {
      DeletePort(aPort);
      FreeMem(audioBuf,bufferSize);
      return(NULL);
   }

   /* Set up the IOAudio to allocate the command channel */
   aIOB->ioa_Request.io_Message.mn_Node.ln_Pri=0;
   aIOB->ioa_Request.io_Message.mn_ReplyPort=aPort;

   aIOB->ioa_Data=allocationMap;
   aIOB->ioa_Length=2;
   aIOB->ioa_Request.io_Command=ADCMD_ALLOCATE;

   OpenDevice("audio.device",0,(struct ioRequest *)aIOB,0);
   if(aIOB->ioa_AllocKey==0)
   {
      DeletePort(aPort);
      FreeMem(audioBuf,bufferSize);
      FreeMem(aIOB,sizeof(struct IOAudio));
      return(NULL);
   }
   else
   {
      /* Set up the IOAudio for writes */
      aIOB->ioa_Request.io_Command=CMD_WRITE;
      aIOB->ioa_Request.io_Flags=ADIOF_PERVOL;
      aIOB->ioa_Data=audioBuf;
      aIOB->ioa_Length=bufferSize;
      return(aIOB);
   }
}

/* Free an allocated audio channel */
void FreeAudioChannel(struct IOAudio *aIOB)
{
   if(aIOB==NULL)
      return;

   /* Free the audi obuffer */
   if(aIOB->ioa_Data!=NULL)
      FreeMem(aIOB->ioa_Data,aIOB->ioa_Length);

   /* Free the audio channel */
   aIOB->ioa_Request.io_Command=ADCMD_FREE;
   BeginIO((struct ioRequest *)aIOB);
   WaitIO((struct ioRequest *)aIOB);
   DeletePort(aIOB->ioa_Request.io_Message.mn_ReplyPort);

   /* Close the audio channel */
   CloseDevice((struct ioRequest *)aIOB);

   /* Free the IOAudio structure */
   FreeMem(aIOB,sizeof(struct IOAudio));
   return;
}

/* Initialize an IOAudio's volume, period, and set the number of cycles */
/* to one */
void InitAudioChannel(struct IOAudio *aIOB,UWORD volume,UWORD period)
{
   aIOB->ioa_Period=period;
   aIOB->ioa_Volume=volume;
   aIOB->ioa_Cycles=1;
   return;
}

/* Duplicate an IOAudio structure */
struct IOAudio *DuplicateAudioChannel(struct IOAudio *OrigIOB)
{
   struct IOAudio *aIOB;
   void *audioBuf;

   if(OrigIOB==NULL)
      return(NULL);

   /* Allocate the alternate buffer */
   audioBuf=(void *)AllocMem(OrigIOB->ioa_Length,MEMF_CHIP);
   if(audioBuf==NULL)
      return(NULL);

   /*Allocate the IOAudio structure*/
   aIOB=(struct IOAudio *)AllocMem(sizeof(struct IOAudio),MEMF_PUBLIC|MEMF_CLEAR);
   if(aIOB==NULL)
   {
      FreeMem(audioBuf,OrigIOB->ioa_Length);
      return(NULL);
   }

   /*Copy the original IOAudio's contents to the new one*/
   CopyMem(OrigIOB,aIOB,sizeof(struct IOAudio));

   /*Except for the buffer pointer, of course*/
   aIOB->ioa_Data=audioBuf;

   return(aIOB);
}

/*Delete a duplicated IOAudio*/
void DeleteDuplication(struct IOAudio *aIOB)
{
   if(aIOB != NULL)
   {
      /* Free the memory for the buffer and IOAudio */
      if(aIOB->ioa_Data != NULL)
	 FreeMem(aIOB->ioa_Data,aIOB->ioa_Length);
      FreeMem(aIOB,sizeof(struct IOAudio));
   }
   return;
}

/* Load an IOAudio's buffer from an open file */
ULONG LoadAudioBuffer(BPTR file,struct IOAudio *aIOB,ULONG toRead)
{
   if(toRead==0)
      return(0);
   aIOB->ioa_Length=Read(file,aIOB->ioa_Data,toRead);
   return(aIOB->ioa_Length);
}

/* Seek forward in a file until the given string is found (useful for */
/* finding chunk in IFF files */
void Position(BPTR file,char *string)
{
   UBYTE len;
   char chr;
   UBYTE i;

   len=strlen(string);

   for(;;)
   {
      Read(file,&chr,1);
      if(chr==string[0])
      {
	 Read(file,&chr,1);
	 for(i=1;i<4 && string[i]==chr;i++)
	 {
	    if(i!=3)
	       Read(file,&chr,1);
	 }
	 if(i==4)
	    return;
      }
   }
}


/* Interpret the command line arguments */
void InterpretArgs(int argc,char *argv[])
{
   int i;

   for(i=1;i<argc;i++)
   {
      if(argv[i][0]=='-')
	 switch(argv[i][1])
	 {
	    /* Use the left channel */
	    case 'l':
	    case 'L':
	       allocationMap=leftAMap;
	       break;

	    /* Use the right channel */
	    case 'r':
	    case 'R':
	       allocationMap=rightAMap;
	       break;

	    /* Switch off the low-pass filter while the sample is playing */
	    case 'f':
	    case 'F':
	       noFilter=TRUE;
	       break;

	    /* Play a sample at a given speed */
	    case 's':
	    case 'S':
	       speed=atol(&argv[i][2]);
	       if(speed > 28000)
		  speed=0;
	       break;

	    /* The volume at which the sample should be played */
	    case 'v':
	    case 'V':
	       volume=atol(&argv[i][2]);
	       if(volume > 64)
		  volume=0;
	       break;

	    /* The size of the chip RAM buffers */
	    case 'b':
	    case 'B':
	       bufSize=(atol(&argv[i][2])+1)&(~1);
	       if(bufSize==0)
		  bufSize=BUF_SIZE;
	       break;
	 }
      else if(argv[i][0]=='?')
      {
	 /*On-line help*/
	 WriteMsg("DSound V0.91a ©1991 by Dave Schreiber\n");
	 WriteMsg("Usage:\m");
	 WriteMsg("  DSound <options> <filename>\n");
	 WriteMsg("Where the options are:\n");
	 WriteMsg("  -l -- Play the sample using the left speaker\n");
	 WriteMsg("  -r -- Play the sample using the right speaker\n");
	 WriteMsg("  -f -- Shut off the low-pass filter\n");
	 WriteMsg("  -s<speed> -- Play the sample at the given speed (samples/sec)\n");
	 WriteMsg("  -v<volume> -- Play the sample at the given volume (0-64)\n");
	 WriteMsg("  -b<bufsize> -- Use a buffer of size <bufsize> (default is 30K)\n");
	 exit(0);
      }
      else     /*Otherwise, the argument is a filename */
	 strcpy(filename,argv[i]);
   }
}

/*Switch on the low-pass filter */
void filter_on()
{
   *((char *)0x0bfe001)&=0xFD;
}

/*Switch off the low-pass filter*/
void filter_off()
{
   *((char *)0x0bfe001)|=0x02;
}

/*Write a message to the CLI*/
void WriteMsg(char *errMsg)
{
   Write(Output(),errMsg,strlen(errMsg));
}

/* Free allocated resources */
void cleanup(int err)
{
   if(file!=NULL)
      Close(file);

   if(window!=NULL)
      CloseWindow(window);

   if(GfxBase!=NULL)
      CloseLibrary((struct Library *)GfxBase);

   if(IntuitionBase!=NULL)
      CloseLibrary((struct Library *)IntuitionBase);

   exit(err);
}


