
/**************************************************************************/
/*				DSound V1.00				  */
/*	 Copyright 1991-1992 by Dave Schreiber, All Rights Reserved	  */
/*									  */
/* To compile:								  */
/*    lmk								  */
/*									  */
/* Revision history:							  */
/*    V1.00  - Added a new module (Mem.c) which allows a sample to be     */
/*	       loaded entirely into memory, so samples can be played from */
/*	       floppy disk without first copying to a hard or RAM drive.  */
/*	       DSound also can now play a single channel of a stereo	  */
/*	       out of two speakers.  The small window, used to let the	  */
/*	       user abort a playing sample, as been redone (DSound also   */
/*	       now responds instantly when the user clicks on the Close   */
/*	       gadget).  DSound now checks a given 8SVX sample to make	  */
/*	       sure that it is actually a valid sample.  Finally, DSound  */
/*	       has been made pure (residentiable).                        */
/*	       Second release (April 26, 1992)                            */
/*    V0.94a - Can now play a mono sample out of both speakers at the	  */
/*	       same time (using the -2 switch).                           */
/*	       March 27, 1992 (a little later)                            */
/*    V0.93a - Now handles stereo sound samples.  Either the right or	  */
/*	       left, or both, stereo channels can be played.  Also split  */
/*	       off the code that actually plays the sound sample into a   */
/*	       separate source file (Play.c).                             */
/*	       March 27, 1992						  */
/*    V0.92a - Now gets the length of the sound sample from the head of   */
/*	       the BODY chunk, instead of the VHDR (a workaround to a bug */
/*	       in the Perfect Sound software that would sometimes store   */
/*	       an incorrect length in the VHDR chunk of a sound sample).  */
/*	       November 4, 1991 					  */
/*    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 <stdlib.h>
#include <stdio.h>

#include "dsound.h"

#include <proto/intuition.h>
#include <proto/exec.h>
#include <proto/dos.h>

char filename[140];

#define DEF_BUF_SIZE 30000

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

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

char *version="$VER: DSound V1.00 (26.4.91)";
char *copyright="Copyright 1991-1992 by Dave Schreiber, All Rights Reserved";

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

struct Window *window=NULL;

BPTR file=NULL;

channel audioChannel=UNSPECIFIED;
BOOL bothChan=FALSE;
BOOL readAll=FALSE;

/*The window definition*/
struct NewWindow newWindow = {
	124,31,
	250,56,
	0,1,
	CLOSEWINDOW,
	SMART_REFRESH|WINDOWDRAG|WINDOWDEPTH|WINDOWCLOSE,
	NULL,
	NULL,
	"DSound V1.00",
	NULL,
	NULL,
	5,5,
	640,200,
	WBENCHSCREEN
};

main(int argc,char *argv[])
{
   struct Voice8Header vhdr;
   UBYTE foo2[5];
   UBYTE foo[5];
   ULONG chan;
   ULONG sampleLength;
   ULONG lock;
   char *chanStr;

   filename[0]=NULL;

   /*Open libraries*/
   GfxBase=(struct GfxBase *)OpenLibrary("graphics.library",0L);
   IntuitionBase=(struct IntuitionBase *)OpenLibrary("intuition.library",0L);

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

   /*Get and interpret the command-line arguments*/
   InterpretArgs(argc,argv);

   /*Exit if there was no sound sample specified*/
   if(filename[0]==NULL)
   {
      WriteMsg("Please specify the name of a sound sample\n");
      cleanup(75);
   }

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

   /*Get the size of the title bar font in a rather illegal way 	 */
   /*Note:  programmers at C= should put a GetDefTitleBarFontHeight()    */
   /*function into Intuition before complaining to me about the following*/
   /*code.								 */

   lock=LockIBase(0L);
   newWindow.Height=IntuitionBase->ActiveScreen->Font->ta_YSize+3;
   UnlockIBase(lock);

   window=OpenWindow(&newWindow);

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

   /*Read the header*/
   Read(file,foo,4);
   Seek(file,4,OFFSET_CURRENT);
   Read(file,foo2,4);

   foo[4]=foo2[4]=NULL;

   /*Check the header's validity, more or less*/
   if((strcmp(foo,"FORM")!=0) || (strcmp(foo2,"8SVX")!=0))
   {
      WriteMsg("Not a valid IFF 8SVX sound sample file.\n");
      cleanup(120);
   }

   if(strcmp(FindChunk(file,"VHDR"),"VHDR")!=0)
   {
      WriteMsg("Couldn't find the 8SVX header (VHDR).\n");
      cleanup(130);
   }

   /*Skip past the chunk size*/
   Seek(file,4,OFFSET_CURRENT);

   /*Get the VHDR*/
   Read(file,&vhdr,sizeof(struct Voice8Header));

   /*Check for compression*/
   if(vhdr.sCompression!=0)
   {
      WriteMsg("Can't play a compressed sample!\n");
      cleanup(400);
   }

   /*Get the CHAN chunk (which will tell us if the sample is stereo, or,*/
   /*if it is mono, which speaker it should be played out of*/
   chanStr=FindChunk(file,"CHAN");
   if(strcmp(chanStr,"CHAN")==0)
   {
      /*Skip past chunk size*/
      Seek(file,4,OFFSET_CURRENT);
      Read(file,&chan,sizeof(long));

      /*The information we're looking for consists of one longword*/
      switch(chan)
      {
	 case 2:  /*Mono sample, left speaker*/
	    if(bothChan)
	       /*Play out of both channels if -2 used*/
	       audioChannel=MONO_BOTH;
	    else
	       if(audioChannel==UNSPECIFIED)
		  audioChannel=MONO_LEFT;
	    break;
	 case 4:  /*Mono sample, right speaker*/
	    if(bothChan)
	       /*Play out of both channels if -2 used*/
	       audioChannel=MONO_BOTH;
	    else
	       if(audioChannel==UNSPECIFIED)
		  audioChannel=MONO_RIGHT;
	    break;
	 case 6:     /*Stereo*/
	    switch(audioChannel)
	    {
	       /*This reconciles a user's choice with the fact that the*/
	       /*sample is in stereo*/

	       /*Play left stereo channel*/
	       case MONO_LEFT:
		  if(bothChan)
		     audioChannel=STEREO_LEFT_BOTH;
		  else
		     audioChannel=STEREO_LEFT;
		  break;

	       /*Play right stereo channel*/
	       case MONO_RIGHT:
		  if(bothChan)
		     audioChannel=STEREO_RIGHT_BOTH;
		  else
		     audioChannel=STEREO_RIGHT;
		  break;

	       /*Play both channels*/
	       case UNSPECIFIED:
		  audioChannel=STEREO;
		  break;
	    }
	    break;
      }

      /*Find the start of the BODY chunk*/
      chanStr=FindChunk(file,"BODY");
   }
   else
   {
      chan=0;
      if(bothChan)
	 audioChannel=MONO_BOTH;
   }

   if(strcmp(chanStr,"BODY")!=0)
   {
      WriteMsg("Couldn't find body of sample.\n");
      cleanup(140);
   }

   if(noFilter)
      filter_off();

   /*Get the length of the sample*/
   Read(file,(char *)&sampleLength,4);

   /*Play the sample by choosing the appropriate player function*/
   switch(audioChannel)
   {
      case MONO_LEFT:
      case MONO_RIGHT:
      case UNSPECIFIED:
	 /*Simple mono playback*/
	 playMonoSample(file,audioChannel,&vhdr,sampleLength);
	 break;
      case MONO_BOTH:
	 /*Mono playback using both speakers*/
	 playMonoTwice(file,audioChannel,&vhdr,sampleLength);
	 break;
      case STEREO_RIGHT:
	 /*Right stereo channel*/
	 audioChannel=MONO_RIGHT;
	 Seek(file,sampleLength/2,OFFSET_CURRENT);
	 playMonoSample(file,audioChannel,&vhdr,sampleLength/2);
	 break;
      case STEREO_RIGHT_BOTH:
	 /*Right stereo channel out of both speakers*/
	 audioChannel=MONO_RIGHT;
	 Seek(file,sampleLength/2,OFFSET_CURRENT);
	 playMonoTwice(file,audioChannel,&vhdr,sampleLength/2);
	 break;
      case STEREO_LEFT:
	 /*Left stereo channel*/
	 audioChannel=MONO_LEFT;
	 playMonoSample(file,audioChannel,&vhdr,sampleLength/2);
	 break;
      case STEREO_LEFT_BOTH:
	 /*Left stereo channel out of both speakers*/
	 audioChannel=MONO_LEFT;
	 playMonoTwice(file,audioChannel,&vhdr,sampleLength/2);
	 break;
      case STEREO:
	 /*Stereo sample (both channels)*/
	 playStereoSample(file,audioChannel,&vhdr,sampleLength/2,filename);
	 break;
   }

   if(noFilter)
      filter_on();

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



/* Get an audio channel */
struct IOAudio *GetAudioChannel(ULONG bufferSize,UBYTE *allocationMap)
{
   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=4;
   aIOB->ioa_Request.io_Command=ADCMD_ALLOCATE;

   /*Open the audio device*/
   OpenDevice("audio.device",0,(struct IORequest *)aIOB,0);


   if(aIOB->ioa_AllocKey==0)
   {  /*There was an error*/
      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);

   if(file==0L)
      getLeft(aIOB->ioa_Data);
   else if(file==4L)
      getRight(aIOB->ioa_Data);
   else
      aIOB->ioa_Length=Read(file,aIOB->ioa_Data,toRead);
   return(aIOB->ioa_Length);
}

/*Find the beginning of an IFF chunk.  This routine will search for that*/
/*chunk's name, and if found, will leave the file cursor at the chunk size*/
/*field.  If the chunk isn't found, the file cursor will be left at the*/
/*size field of the BODY chunk, if there was one*/
char *FindChunk(BPTR file,char *string)
{
   static char buf[5];
   long len,actLen;
   buf[4]=NULL;

   actLen=Read(file,buf,4);
   while(strcmp(string,buf)!=0 && strcmp(buf,"BODY")!=0 && actLen > 0)
   {
      Read(file,(char *)&len,4);
      Seek(file,len,OFFSET_CURRENT);
      actLen=Read(file,buf,4);
   }
   return(buf);
}

/* 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])
	 {
	    /* Read the entire sample into memory before playing */
	    case 'm':
	    case 'M':
	       readAll=TRUE;
	       break;

	    /* Use the left channel */
	    case 'l':
	    case 'L':
	       audioChannel=MONO_LEFT;
	       break;

	    /* Use the right channel */
	    case 'r':
	    case 'R':
	       audioChannel=MONO_RIGHT;
	       break;

	    /*Play a mono sample out of both speakers*/
	    case '2':
	       bothChan=TRUE;
	       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=DEF_BUF_SIZE;
	       break;
	 }
      else if(argv[i][0]=='?')
      {
	 /*On-line help*/
	 WriteMsg("DSound V1.00 ©1991-1992 by Dave Schreiber\n");
	 WriteMsg("Usage:\n");
	 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("  -2 -- Play the sample using both speakers\n");
	 WriteMsg("  -f -- Shut off the low-pass filter\n");
	 WriteMsg("  -m -- Load the entire sample into memory\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));
}

/*Take a file handle and that handle's filename, and open that file again*/
/*The position in the second file in set to the position in the first */
/*file (so that the two file handles are essentially identical)*/
/*This requires that the first file was opened in a shared mode, like */
/*MODE_OLDFILE*/
BPTR dupFileHandle(BPTR origFile,char *filename)
{
   BPTR dupFile;

   dupFile=Open(filename,MODE_OLDFILE);

   if(dupFile==NULL)
      return(NULL);

   Seek(dupFile,getPosInFile(origFile),OFFSET_BEGINNING);
   return(dupFile);
}

/*Get the current position in a file*/
ULONG getPosInFile(BPTR file)
{
   LONG position;

   position=Seek(file,0,OFFSET_CURRENT);
   return((ULONG)position);
}

/* Free allocated resources */
void cleanup(int err)
{
   /*If the entire sample was read into memory, this will delete whatever*/
   /*part of the sample still remains in memory*/
   deleteLeft();
   deleteRight();

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

/*End of DSound.c*/

