/****************************   Sound.c   *********************************

    Sound is copyright (c) 1988 by Richard Lee Stockton, 21305 60th Ave W.,
Mountlake Terrace, Washington 98043, 206/776-1253(voice), but may be freely
distributed as long as no profit is made from its distribution or sale
without my written permission. I call this concept 'FreeWare', you can
call it whatcha want.

    I also include the source code, (Manx 3.4/3.6), in the hope that it
will be of benefit to someone in the Amiga programming community. Feel free
to alter it at will, (but at your own risk!). I _would_ appreciate
receiving a copy of whatever it may become and it is always nice to receive
credit, (perhaps just a few lines of glowing tribute.^)

               Long Live Leo and All the Little Schwabies!

                    To Manufacture with Manx 3.4/3.6:

                            cc Sound.c
                            ln +cd Sound.o -lc

     You'll get a warning that you've re-defined IconBase. Ignore it.

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

#include <exec/memory.h>
#include <workbench/startup.h>
#include <workbench/workbench.h>
#include <workbench/icon.h>
#include <libraries/dosextens.h>
#include <devices/audio.h>
#include <functions.h>

/* Less than WorkBench 1.2 need not apply. That's nobody, right? ;-> */

#define  REVISION   33L

/* We'll need 4 buffers in CHIP memory, all else in FAST, if ya got it */

#define  BUFSIZE  1024L

/* Some MANX specific, byte saving, (usually), string routines */

#define  strcpy     _BUILTIN_strcpy
#define  strcmp     _BUILTIN_strcmp
#define  strlen     _BUILTIN_strlen

/* A pretty little HELP message showing valid variable ranges */

#define  USAGE      "\
\033[42m\033[31m\
 USAGE: Sound <file> [0-55]     [56-65535]    [on/off] \
\033[m\n\033[42m\
 ABORT: <CTRL> 'c'\033[33m\
   CYCLES SAMPLES_PER_SECOND STEREO  \
\033[m\n\033[42m\033[31m\
 05/18/88 04:28  copyright \xa9 1988 Richard Lee Stockton \
\033[m\n"

/* Probably more GLOBALS than are required. 'C' is funny stuff. */

extern struct   IconBase      *IconBase=NULL;
extern struct   IntuitionBase *IntuitionBase=NULL;
extern struct   WBStartup     *WBenchMsg=NULL;
extern struct   WBArg         *argp=NULL;
       struct   IntuiMessage  *message=NULL;
       struct   Window        *StatusWindo=NULL;
       struct   DiskObject    *infofile=NULL;
       struct   IOAudio       *sound[4]={NULL,NULL,NULL,NULL};
                long          sactual=0L, sstart=0L, savelock=0L,
                               atol(), sps=0L, cycles=1L, lock=0L;
                short         k=0;
                UBYTE         sunit[4]={12,10,5,3};
                BOOL          stereo=FALSE, help=FALSE;
                char          *sbuffer=NULL, *ltoa(),
                               title[50]="Sound: ",
                               *cbuf[4]={NULL,NULL,NULL,NULL},
                               *sname[108]=NULL, *SafeAllocMem();
                void          loadSound(), setStatusWindo(),
                               soundSound(), quit();

/*********** quit, give-up, go home, finish... Neatness counts! ******/

void quit(string)
char    *string;
{
   if(sound[0])      /* This cleans up the audio device stuff */
   {
      for(k=3;k>(-1);k--)    if(sound[k]) AbortIO(sound[k]);
      if(sound[0]->ioa_Request.io_Device) CloseDevice(sound[0]);
      for(k=3;k>(-1);k--)
      {
         if(sound[k]->ioa_Request.io_Message.mn_ReplyPort)
            DeletePort(sound[k]->ioa_Request.io_Message.mn_ReplyPort);
      }
      for(k=3;k>(-1);k--)
      {
         if(sound[k]) FreeMem(sound[k],(long)sizeof(struct IOAudio));
         if(cbuf[k])  FreeMem(cbuf[k],BUFSIZE);
      }
      sound[0]=NULL;
   }

/* Write any message to out. May be error or could be samples/second */
/* You'll be sorry if you try to Write(Output()) to WorkBench!  8-)  */

   if(help) Write(Output(),string,(long)strlen(string));
   else if(*string)
   {
      strcpy(title,"Sound Error: ");   strcat(title,string);
      setStatusWindo();
   }
   if(!help&&(cycles>-1L)&&(sactual<(16000L*(stereo+1L)))) Delay(77L);

/* Clean up everything else */

   if(StatusWindo)      CloseWindow(StatusWindo);
   if(IntuitionBase)    CloseLibrary(IntuitionBase);
   if(sbuffer)          FreeMem(sbuffer,sactual);
   if(infofile)         FreeDiskObject(infofile);
   if(IconBase)         CloseLibrary(IconBase);
   if(sound[3]) exit(0); else exit(10);
}


/*  Don't Allocate if Low Mem - by Bryce Nesbitt  */
/* Aberations by RLS. 4096 should be fudge enough */

char *SafeAllocMem(size,flags)
long size, flags;
{
   register char *p;
   
   if(p=(char *)AllocMem(size,flags))
      if(AvailMem(MEMF_CHIP)<4096L)
         { FreeMem(p,size);  return(NULL); }
   return(p);
}

/**** Status Window. Uses <CTRL> 'c' to abort.  ****/

void setStatusWindo()
{
    struct NewWindow w;
    
/* Try to Open Intuition and if successful, make a 'status' window */

    if(StatusWindo) CloseWindow(StatusWindo); StatusWindo=NULL;
    if(!IntuitionBase)
    {
       IntuitionBase = (struct IntuitionBase *)
                       OpenLibrary("intuition.library",REVISION);
    }
    if(!IntuitionBase) quit(NULL);

    w.LeftEdge    =   82L;
    w.TopEdge     =    0L;

/* if title==USAGE, the large Width value will abort the window open */
/* This is just fine, because we don't WANT it to open in this case. */

    w.Width       = (long)(strlen(title)*8+8);
    w.Height      =   10L;
    w.DetailPen   =  0x01;
    w.BlockPen    =  0x03;
    w.Title       = (UBYTE *)title;
    w.Flags       = SMART_REFRESH|NOCAREREFRESH|ACTIVATE;
    w.IDCMPFlags  = VANILLAKEY;   /* So we can get <CTRL> 'c' message */
    w.Type        = WBENCHSCREEN;
    w.FirstGadget = NULL;
    w.CheckMark   = NULL;
    w.Screen      = NULL;
    w.BitMap      = NULL;
    w.MinWidth    = 0;
    w.MinHeight   = 0;
    w.MaxWidth    = 0;
    w.MaxHeight   = 0;
    StatusWindo = OpenWindow(&w);

/* Abort ONLY if cycles = 0 (loop) AND the window failed to open. */
/* This set of conditions would otherwise result in an Endless and */
/* Escape-proof loop. If cycles != 0, sound will end, sometime... */

    if(!StatusWindo&&(cycles==0L)) quit(NULL);
}


/******** Load SoundFile 'sPath' & set cycles-sps-stereo *******/

void loadSound(sPath)
char    *sPath[80];
{
   struct FileHandle    *sFile=NULL;
   struct FileInfoBlock *finfo=NULL;
   struct FileLock      *lock=NULL;
          long          i, j;
          char          string[5];

/* Allocate 256 bytes as work memory */

   if(!(sbuffer=SafeAllocMem(256L,MEMF_CLEAR|MEMF_PUBLIC)))
        quit("No Work Memory!");

/* Check for and parse IFF data in first 256 bytes of file */

   if(!(sFile=Open(sPath,MODE_OLDFILE)))
   {
      sactual=256L;      quit("Can't Find SoundFile!");
   }
   Read(sFile,sbuffer,256L);              /* load the 1st 256 bytes */
   for(sstart=0L, sps=0L, i=0L; i<252L; i+=4L)
   {
      strncpy(string,sbuffer+i,4);   string[4]=NULL;
      if(!(strcmp(string,"VHDR")))        /* get samples per second */
      {
         for(j=0;j<(long)((UBYTE)sbuffer[i+20]);j++) sps+=256L;
         sps += ((UBYTE)sbuffer[i+21L]);
      }
      if(!(strcmp(string,"CHAN")))            /* Channel Assignment */
      {
         if((sbuffer[i+7]==6)||(sbuffer[i+11]==6)) stereo=TRUE;
      }
      if(!(strcmp(string,"BODY")))        /* get size of sound data */
      {
         for(j=0;j<4;j++) sactual+=(((UBYTE)sbuffer[i+7L-j])<<(8*j));
         sstart = i+8L; i=252L;
      }
   }

/* if not in IFF format, get filesize from FileInfoBlock */

   if(!sactual)
   {

/* Allocate a file info block, get size from it, and de-allocate */

      if((!(finfo=(struct FileInfoBlock *)
         SafeAllocMem((long)sizeof(struct FileInfoBlock),MEMF_CLEAR)))
       ||(!(lock=Lock(sname,ACCESS_READ)))||(!(Examine(lock,finfo))) )
                    quit("FileInfoBlock Problem!");
      sactual = finfo->fib_Size;      if(lock) UnLock(lock);
      if(finfo) FreeMem(finfo,(long)sizeof(struct FileInfoBlock));
   }

/* clean up work area */

   FreeMem(sbuffer,256L); sbuffer=NULL;

/* Allocate _contiguous_ memory for SOUND data. */
/* We'll transfer in BUFSIZE chunks to CHIP memory a little later. */
/* We have to do the contiguity(?) check since AllocMem() does not. */

   if((AvailMem(MEMF_LARGEST)<sactual) ||
     (!(sbuffer=SafeAllocMem(sactual,MEMF_CLEAR|MEMF_PUBLIC))))
        { Close(sFile); quit("Need Contiguous Memory!"); }

/* Load the data into sbuffer */

   Seek(sFile,sstart,OFFSET_BEGINNING);
   if((Read(sFile,sbuffer,sactual)) == -1L)
      { Close(sFile); quit("Read Error!");}
   Close(sFile);
}

/*****************  make a noise ******************/

void soundSound()
{
    ULONG   class;
    LONG    i, dactual, dlength, remaining;
    USHORT  code, count;

/* Put up a 'status' window on the top line. */

    strcat(title,ltoa(sps)); strcat(title," SAMPLES_PER_SECOND");
    if(stereo) strcat(title," in STEREO");     setStatusWindo();

/* Allocate sound data buffers from CHIP memory. Ports and */
/* Audio Request Structures do NOT require CHIP memory */

   for(k=0;k<4;k++)
   {
     if(!(cbuf[k]=SafeAllocMem(BUFSIZE,
           MEMF_CHIP|MEMF_CLEAR|MEMF_PUBLIC))) quit("No CHIP Memory!");
     if(!(sound[k]=(struct IOAudio *)SafeAllocMem((long)sizeof(struct IOAudio),
                     MEMF_CLEAR|MEMF_PUBLIC))) quit("No IOA Memory!");
   }
   if( (!(sound[0]->ioa_Request.io_Message.mn_ReplyPort =
           CreatePort("Sound0",0L))) ||
       (!(sound[1]->ioa_Request.io_Message.mn_ReplyPort =
           CreatePort("Sound1",0L))) ||
       (!(sound[2]->ioa_Request.io_Message.mn_ReplyPort =
           CreatePort("Sound2",0L))) ||
       (!(sound[3]->ioa_Request.io_Message.mn_ReplyPort =
           CreatePort("Sound3",0L))) )         quit("No Port Memory!");
   

/* Open Audio using the first IOAudio as the 'initializer' request */

   sound[0]->ioa_Request.io_Message.mn_Node.ln_Pri = 20;
   sound[0]->ioa_Data   = &sunit[0];
   sound[0]->ioa_Length = 4L;
   if((OpenDevice(AUDIONAME,0L,sound[0],0L))!=NULL)
      quit("No Audio Device!");

/* Set all IOAudios. */

   for(k=0;k<4;k++)
   {
      sound[k]->ioa_Request.io_Message.mn_Node.ln_Pri = 20;
      sound[k]->ioa_Request.io_Command = CMD_WRITE;
      sound[k]->ioa_Request.io_Flags   = ADIOF_PERVOL;

/* Note copies of Device & AllocKey from initializer. */

      sound[k]->ioa_Request.io_Device  = sound[0]->ioa_Request.io_Device;
      sound[k]->ioa_AllocKey  = sound[0]->ioa_AllocKey;

/* Each IOAudio has its own CHIP buffer, Port, and Unit (left/right) */

      sound[k]->ioa_Data   = (UBYTE *)cbuf[k];

/* 3579547 divided by 55 = 65083, nearly the maximum Period (65535) */

      sound[k]->ioa_Period = 3579547L/sps;

/* As LOUD as possible. Use your monitor/stereo volume. Rock 'n Roll! */

      sound[k]->ioa_Volume = 64L;

/* One time through this BUFSIZE (or smaller) part of the whole */

      sound[k]->ioa_Cycles = 1L;
   }

/* The compiler wants 'Unit' to be a structure, we just want to mask */
/* into the allocated left/right channels. left=1 or 8, right=2 or 4 */
/*        ...zap! You're a Unit structure! Feel any different?       */

   for(k=2;k>(-1);k-=2)
   {
      sound[k+1]->ioa_Request.io_Unit = (struct Unit *)
                      ((ULONG)(sound[0]->ioa_Request.io_Unit)&6L);
      sound[k]->ioa_Request.io_Unit  = (struct Unit *)
                      ((ULONG)(sound[0]->ioa_Request.io_Unit)&9L);
   }

/* If in STEREO, split file. If in MONO, 'b' buffers use 'a' data */

   if(stereo) remaining=(sactual/2L)-(sactual&1L);
   else
   {
      remaining=sactual;
      sound[1]->ioa_Data   = (UBYTE *)cbuf[0];
      sound[3]->ioa_Data   = (UBYTE *)cbuf[2];
   }

/* dactual is the length of one channel's complete data */

   dactual=remaining;     k=count=0;

/* we be doing loops here */

   do
   {

/* be CERTAIN ioa_Length is an even number & set datalength */

      if(remaining>BUFSIZE) dlength=BUFSIZE;
       else  { dlength=remaining;  dlength-=(dlength&1L); }

/* Move the data into the proper CHIP buffer of BUFSIZE */

      movmem(sbuffer+(dactual-remaining),cbuf[k],(int)dlength);

/* Don't load or use the right CHIP buffers if MONO. Saves time. */

      if(stereo) movmem(sbuffer+(sactual-remaining),
                        cbuf[k+1],(int)dlength);

/* Data has been moved, so adjust 'remaining' */

      remaining-=dlength;

/* Left and Right Lengths are the same, no matter what! */

      sound[k]->ioa_Length = sound[k+1]->ioa_Length = dlength;

/* Start one set of Left/Right Channels. */

      BeginIO(sound[k]);      BeginIO(sound[k+1]);

/* Check Intuition for the ABORT message */

      while(message=(struct IntuiMessage *)GetMsg(StatusWindo->UserPort))
      {
         class = message->Class;  code = message->Code;

/* Hi Intuition! Thanks for the message, Have a nice day! */

         ReplyMsg(message);

/* <CTRL> 'c' abort.  1,2,3, easy as a,b,c, baby you and me! */

         if((class==VANILLAKEY)&&(code==3)) quit(NULL);
      }

/* Is this the last time AND the last cycle? If yes & no, reset. */

      if(remaining<2L) if(--cycles!=0L)
         { remaining=dactual;   dlength=BUFSIZE; }

/* Is this the last time, or what? */

      if(remaining<2L)  WaitIO(sound[k+1]);   /* wait for LAST request */
      else
      {
         if(k) k=0; else k=2;    /* switch buffers & wait for PREVIOUS */
         if(count++)  WaitIO(sound[k+1]);
      }

/* Keep going until we run out of data */

   } while(remaining>1L);                   /* End of Loop */
}


/********** long int to char string (up to 7 digits) ***********/

char *ltoa(num)
          long  num;
{
   static char ostring[8]="";
          short  next = 7,  shift = 0;
   
   if (!num) ostring[next--] = '0';
   while ((num+9L)/10)
      { ostring[next--] = num % 10L + '0';   num /= 10L; }
   next+=1;
   while(next<8)       ostring[shift++] = ostring[next++];
   while(shift<8)      ostring[shift++] = '\0';
   return(ostring);
}


/************************  MAIN  ****************************/

main(argc,argv)
 int           argc;
 char          *argv[];
{
    long      temp=0L;
    short     i=0;
    char      string[10];
    
    if((argc==0)&&(WBenchMsg->sm_NumArgs>1))     /*  from WorkBench  */
    {
       argp=(WBenchMsg->sm_ArgList)+1;           /*  CD to lock and  */
       if(lock=argp->wa_Lock)  savelock=(long)CurrentDir(lock);
       strcpy(sname,argp->wa_Name);              /* get the filename */
    }

/* If Called From CLI */

    if(argc==1) {help=TRUE; quit(USAGE);}    /* No SOUNDfile so quit */
    if(argc>1)  strcpy(sname,argv[1]);   /* 2nd arg MUST be filename */

/* Load 'em up! */

    loadSound(sname);

/* If WorkBench, try to get info from the .info file */

    if((argc==0)&&(WBenchMsg->sm_NumArgs>1))
    {
       IconBase=(struct IconBase *)OpenLibrary("icon.library",REVISION);
       if((!IconBase) ||   
         ((infofile=(struct DiskObject *)GetDiskObject(sname))==NULL))
                quit(NULL);

/* then check 'ToolTypes' to set cycles, stereo, & samples per second. */
/* Note use of strncpy to copy only what we need. This is an attempt to */
/* take care of a .info editing bug which may cause extra long strings. */

       strncpy(string,(char *)FindToolType(infofile->do_ToolTypes,
                       "CYCLES"),3);

/* We do this check for '0' since atol() thinks ' ', or NULL = '0'  */
/* But if data is blank, or NULL, we want cycles set to 1 (default) */

       if(string[0]=='0') cycles = 0L;
       temp=atol(string);  if(temp) cycles = temp;

/* Valid STEREO strings will have o or O as the first letter. */

       strncpy(string,(char *)FindToolType(infofile->do_ToolTypes,
                       "STEREO"),4);
       if(string[0]=='o'||string[0]=='O')
       {
          if(string[1]=='n'||string[1]=='N')   stereo = TRUE;
           else  stereo = FALSE;
       }

/* We'll take any old sps because we check for validity before playing */

       strncpy(string,(char *)FindToolType(infofile->do_ToolTypes,
                       "SAMPLES_PER_SECOND"),6);
       temp = atol(string);    if(temp) sps = temp;
    } 

/* Parse arguments from CLI. Note that from WorkBench, argc will be 0, */
/* so the following for() loop will only execute if called from CLI. */

    for(i=2;i<argc;i++)
    {
       if(argv[i][0]=='o'||argv[i][0]=='O')
       {
          if(argv[i][1]=='n'||argv[i][1]=='N')   stereo = TRUE;
          else   stereo = FALSE;
       }
       else  /* must be sps or cycles, size of arg determines which one */
       {
          temp = atol(argv[i]);
          if(temp<56L)  cycles = temp;    else  sps = temp;
       }
    }

/* Make sure we have valid values before 'sounding' */

    if((sps<56L)||(sps>65534L)) sps = 10000L;
    if(cycles>65535L) cycles = 1L;       /* 55 limit only applies to CLI */

    soundSound();                                    /* MAKE THAT NOISE! */

    if(lock) CurrentDir(savelock);                /* restore current DIR */

/* Then release everything you've used here. (Be a tidy camper.) */

    quit(NULL);
}

/************************** end of Sound.c ******************************/
