/* Audiotools.c release 3.1 -- by Rob Peck */

/* This is a modified version of AudioTools -- changes by Dick Taylor */
/* to use with the SCALE program.    November 1992 */

/* Please refer to file scale.doc for AudioTools background. */

#include "exec/types.h"
#include "exec/memory.h"
#include "devices/audio.h"

/* audiotools.h  -- in original version, this was a separate module */

#define StartChannel(c) ControlChannel(c, CMD_START)
#define StopChannel(c)  ControlChannel(c, CMD_STOP)
#define ResetChannel(c) ControlChannel(c, CMD_RESET)
#define FlushChannel(c) ControlChannel(c, CMD_FLUSH)

#define  BIG_WAVE          256L   /* size of biggest waveform */
#define  NBR_WAVES         7L     /* number of waves per instrument */
#define  WAVES_TOTAL       1024L  /* alloc size for instrument's waves */

#define  DEFAULT_DURATION  500L	  /* 500/1000ths of a second default */
#define  AUDBUFFERS        20L	  /* iob msg packets before need to allot */
#define  YES               1L
#define  NO                0L

#define  BAD_CHANNEL_SELECTED -1L /* channel # out of range */
#define  NOT_YOUR_CHANNEL     -2L /* not owned by your task */
#define  OUT_OF_RANGE_FREQ    -3L /* frequency that we cannot play */
#define	 OUT_OF_RANGE_PRI     -4L /* priority value wont fit in 1 byte */

/* Note that the definition of ExtIOB has now been changed (added to) */

struct ExtIOB {
    struct 	IORequest ioa_Request;
    WORD	ioa_AllocKey;
    UBYTE	*ioa_Data;
    ULONG	ioa_Length;
    UWORD	ioa_Period;
    UWORD	ioa_Volume;
    UWORD	ioa_Cycles;
    struct	Message ioa_WriteMsg;	/* up to here, same as IOAudio */
    LONG	iob_Identifier;		/* This field is added */
};

/* a few forward declarations */

extern struct ExtIOB 	*GetIOB();
extern int 		FreeIOB();
extern int 		GetChannel();
extern int 		InitBlock();
extern struct MsgPort 	*CreatePort();
extern struct MsgPort   *InitAudio();

extern APTR 		AllocMem();
extern struct Message 	*GetMsg();
extern struct Task	*FindTask();
extern LONG		SetCycles();
extern int	TicksPerSec;	/* Clock ticks per second -- */
						/* NTSC (American) = 3579545, or */
						/* PAL (European) = 3546895 */

struct auMsg {
   struct Message au_Message;
   LONG aum_Identifier;   /* matches the bottom of ExtIOB */
};

/* these are used to keep track of allocated channels */

struct Unit	*unit[4];	/* global pointers to Units        */
WORD 		key[4];		/* global value for alloc keys     */
struct Task 	*usertask[4];	/* in preparation for making this
				 * a shared library of routines
				 * (loadable from disk), keep track
				 * of which user actually owns a
				 * channel currently. */
/* End of audiotools.h, start of former external module globals.c */

struct  IOAudio 	openIOB;      /* IOB to open and close the device */
struct  MsgPort 	*auReplyPort; /* temporary pointer */
struct  MsgPort 	*controlPort; /* Port for ControlChannel functions */

char    *globalname 	= "global";	/* the name for global IOB's  */
char    *dynamicname 	= "dynamic";	/* the name for dynamic IOB's */

UBYTE anychan[4] 	= { 1, 2, 4, 8 };	/* channel masks for mono */

/* Resolve most all externals */

struct ExtIOB	  	audbuffer[AUDBUFFERS];	/* globals to build-in       */
UBYTE 			*chipaudio[4];	/* pointers to waveforms in CHIP RAM */
struct Device 		*device;	/* global pointer to audio device  */
LONG 			datalength[4];	/* length of the data for a wave   */
struct MsgPort 		*replyPort[4];  /* one ReplyPort per channel       */
BYTE 			inuse[AUDBUFFERS]; /* keep track of globals in-use */
LONG			dynamix[4];	/* counters for how many
					 * dynamically allocated audio
					 * message I/O blocks */

/* Each waveform buffer contains 8 octaves of the wave.  
 * The offset values specify where in the buffer the
 * proper waveform table for that octave begins.
 */
int woffsets[] 	= { 0, 256, 384, 448, 480, 496, 504, 508, 510 };

/* Length of each waveform within a buffer */
int wlen[] 	= { 256, 128, 64, 32, 16, 8, 4, 2, 1 };

/* Period value to go with particular notes within an octave. */

int perval[] 	= { 428, 404, 381, 360, 339, 320, 
     		    302, 285, 269, 254, 240, 226, 214 };
UBYTE *w1, *w2, *w3;
/* End of globals.c */

struct MsgPort *    
InitAudio()
{
      LONG error,i;
      struct MsgPort *userport;	/* NEW item */

      LONG firstuser;	/* THIS WILL GET MOVED when shared library is made */
      firstuser = TRUE;

      /* Declare all message blocks available */
      for(i=0; i<AUDBUFFERS; i++)  {   inuse[i] = NO;   }

      /* Open device but don't allocate channels     */
      openIOB.ioa_Length = 0;   /* (no allocation table) */

      error = OpenDevice("audio.device",0,&openIOB,0);
      if(error) return((struct MsgPort *)0);

      /* Get the device address for later use */
      device = openIOB.ioa_Request.io_Device;
   
   /* Create ports for replies from each channel as well as
    * one port to be used for the control and synchonous functions */

   for(i=0; i<4; i++) 
   {   
      auReplyPort = CreatePort(0,0);
      replyPort[i] = auReplyPort;
      if(auReplyPort == 0) return((struct MsgPort *)0);
      chipaudio[i] = 0;  /* have not yet created the waves */

      datalength[i] = 1; /* might use for custom sound samples  */

      /* Also, zero out key values for each channel, as well as
       * unit value and usertask value (no channel owned by any task)*/
	/* When implemented as a shared library, "firstuser" will only 
 	 * be true when the library is first opened.*/
      if(firstuser)	
      {
	key[i]  = 0;
	unit[i] = 0;
	usertask[i] = 0;
      }
   }
   controlPort = CreatePort(0,0);
   if(controlPort == 0) return((struct MsgPort *)0);

   error = MakeWaves();
   if(error == -1) return((struct MsgPort *)0);

   for(i=0; i<4; i++)
   { dynamix[i] = 0; }   /* no dynamic I/O blocks allocated 
        	          * for any channel thus far */
   userport = CreatePort(0,0);
return(userport);
}

int 
CheckIOBDone()
{
   LONG i, status;

   status = 0;   /* means there are still some iob's in play */
         /* when status = 5, then everything is free */

   for(i=0; i<AUDBUFFERS; i++)
   {   if(inuse[i] == YES)
       {   
	 /* Sooner or later, this will catch both the statics & dynamics.
	* Note that this will only work if NO iob's sent off with a 
	* duration value of "0", because zero means "forever". */
         ReEmployIOB();
      }
   }
   /* Note to implementors... maintaining inuse[i] now seems
    * like a lousy idea, unless it is accompanied by a variable
    * statics_inplay that decrements to zero when all statics
    * are done.  That makes it much easier to check than going
    * through all of the inuse[]'s.*/

   for(i=0; i<4; i++)
   {   
      if(dynamix[i] > 0)  
	/* If this channel still playing a dynamically allocated block, */
	/* wait for all messages to return before the program exits. */
      {
	ReEmployIOB();  /* take another shot at freeing it all */  
      }
   }
   for(i=0; i<4; i++)   /* Check again as we nearly exit */
   {
      if(dynamix[i] == 0) status++;
   }
   if(status == 4)      /* All dynamics are free, now check the statics.
             * Any not free force an early return. */
   {
#ifdef DEBUG
      printf("ch0,1,2,3 have %ld,%ld,%ld,%ld dyn.blocks playing\n",
         dynamix[0], dynamix[1], dynamix[2], dynamix[3]);
#endif
      for(i=0; i<AUDBUFFERS; i++)
      {
         if(inuse[i] == YES)
         {
#ifdef DEBUG
         printf("iob still in use is: %ld\n",i);
#endif
         return(0);
         }
      }
#ifdef DEBUG
      printf("All global I/O blocks are done\n");
#endif
      return(1);   /* DONE! */
   }
   else
   {
      return(0);   /* still some out there! */
   }
}

FinishAudio(uport)
struct MsgPort *uport;
{
   LONG i;
   struct auMsg *aum;	    /* A little bigger than a standard message,
	* but this routine will not know (or care) about the difference.*/
   if(uport == 0)
   {
	goto no_init;	    /* InitAudio didn't work, bypass
			     * the usual port emptying and so on. */
   }
/* If the user says FinishAudio, IT MEANS FINISH AUDIO.  Flush anything
* that is still in play, NOW.  You can use "CheckIOBDone()" to see if
* everything is finished BEFORE you call FinishAudio.  If CheckIOBDone() is
* equal to zero (FALSE), it means that something is still playing. */
   for(i=0; i<4; i++)   FlushChannel(i);

   while(CheckIOBDone() == 0)
   {
	Delay(12);   /* Be a good multitasking neighbor;
			* sleep a little before trying again */
   }
	/* Empty the port if the user has not yet done so */
   while((aum = (struct auMsg *)GetMsg(uport)) != NULL)
   {
      aum->au_Message.mn_ReplyPort = 0;   /* let system deallocate it */

/* *** THIS WILL CHANGE --will be based on the identifier field instead.*/ 
   }
   ReEmployIOB();   /* free all static and dynamic messages */

   for(i=0; i<4; i++)   FreeChannel(i);

   DeletePort(uport);

no_init:
   if(device) CloseDevice(&openIOB);
#ifdef DEBUG
   printf("closed the device\n");
#endif
   for(i=0; i<4; i++)
   {
	if(chipaudio[i]) FreeMem(chipaudio[i],WAVES_TOTAL);
	if(replyPort[i]) 
	DeletePort(replyPort[i]);
   }
   if(controlPort) DeletePort(controlPort);
   return(0);      /* no errors */
}
 
 
int
ControlChannel(channel, command)
   WORD channel;
   WORD command;
{
   LONG rtn;
   struct ExtIOB *iob, controlIOB;

   if(channel < 0 || channel > 3)	return(BAD_CHANNEL_SELECTED);
   if(usertask[channel] != FindTask(0))	return(NOT_YOUR_CHANNEL);

   iob = &controlIOB;
   iob->ioa_Request.io_Device    = device;
   iob->ioa_Request.io_Message.mn_ReplyPort = controlPort;

   InitBlock(iob,channel);   /* init it for CMD_WRITE, then change */

   iob->ioa_Request.io_Command = command;
   iob->ioa_Request.io_Flags   = IOF_QUICK;

   BeginIO(iob);
   WaitIO(iob);
   rtn = ((LONG)(iob->ioa_Request.io_Error));
   return(rtn);
}
 
struct ExtIOB *
GetIOB(ch)
   LONG ch;
{
   WORD i,use_reply;
   struct ExtIOB *iob;  /* in case we need to allocate one */
   ReEmployIOB();    /* find already used ones and free them */
              /* so that when we do a get... */
   if(ch == -1)  use_reply = 0;  /* which reply port to use */
   else          use_reply = ch;

   for(i=0; i<AUDBUFFERS; i++)
   {   
	if(inuse[i] == NO)
       	{   
		inuse[i] = YES;

		audbuffer[i].ioa_Request.io_Device    = device;
		audbuffer[i].ioa_Request.io_Message.mn_ReplyPort = 
                  				replyPort[use_reply];
		audbuffer[i].ioa_Request.io_Message.mn_Length = i;
		audbuffer[i].ioa_Request.io_Message.mn_Node.ln_Name = 
                  				globalname;
#ifdef DEBUG
      printf("Using global iob\n");
#endif
      return(&audbuffer[i]);
       }
   }
   /* if all globals are in use, have to allocate one */
   iob = (struct ExtIOB *)AllocMem(sizeof(struct ExtIOB),
                     MEMF_CLEAR );
   /* BUG FIX - was MEMF_FAST only, and would not run on 512k machine!! */

   if(iob == 0) return(0);   /* out of memory */
   else
   {   
	iob->ioa_Request.io_Device = device;
      	iob->ioa_Request.io_Message.mn_ReplyPort = 
               replyPort[use_reply];
      	iob->ioa_Request.io_Message.mn_Node.ln_Name = 
               dynamicname;
      	iob->ioa_Request.io_Message.mn_Length = dynamix[use_reply];
      	dynamix[use_reply] += 1; /* add one to number allocated
        		          * for a specific channel */
#ifdef DEBUG
      	printf("Using dynamic iob\n");
#endif
      	return(iob);
	}
return(0);
}
 
 
/* Free a global or an allocated IOB */
int
FreeIOB(iob, ch)
   struct ExtIOB *iob;
   LONG ch;   /* which channel was it attached to? */
{
   WORD i;

   if(iob->ioa_Request.io_Message.mn_Node.ln_Name == dynamicname)
   {   
	FreeMem(iob, sizeof(struct ExtIOB));
      	if(dynamix[ch]) dynamix[ch] -= 1; /* subtract one if nonzero */
      	return(0L);
   }
   else if(iob->ioa_Request.io_Message.mn_Node.ln_Name == globalname)
   {   	
	i = iob->ioa_Request.io_Message.mn_Length;

      	if(i < AUDBUFFERS)
      	{   
		inuse[i] = NO;   /* frees this one for reuse */
      	}
      	return(0L);
   }
   /* if get here, the names don't match... something is wrong.*/
   else 
   {   
		printf("FreeIOB: names don't match...unknown error\n");
   		return(-1);   /* unknown source of IOB fed to routine. */
   }
return(0);
}
 
/* Initialize an audio I/O block for default CMD_WRITE operation. */
int
InitBlock(iob, channel)
     struct ExtIOB *iob;
   WORD channel;
{
   /* Device and ReplyPort fields have been initialized by GetIOB */
   iob->ioa_Request.io_Unit = unit[channel];

   /* Allocation key */
   iob->ioa_AllocKey = key[channel];

   /* Where is the waveform?  Just be sure is in MEMF_CHIP!!! */

   /* USER initializes datalength[ch] before calling this;    */
   /* for sampled sound command write operation.              */
   iob->ioa_Data    = chipaudio[channel];
   iob->ioa_Length = datalength[channel];

   /* Another routine, must initialize:
	period      ioa_Period	volume	ioa_Volume
	cycles      ioa_Cycles	message	ioa_WriteMsg */
   /* Default command type is CMD_WRITE */
   iob->ioa_Request.io_Command = CMD_WRITE;

   /* If IOF_QUICK is zeroed, this would affect the
    * period and volume.  If a CMD_WRITE, it queues if
    * another note is already playing.  We queue CMD_WRITES. */
   iob->ioa_Request.io_Flags = ADIOF_PERVOL;
   return(0);
}
 
/* To request "any" channel, use ch = -1;
 * To request a specific channel, use ch = {0, 1, 2 or 3};
 * Again NOTE, this returns two globals as well as the channel number! */

int
GetChannel(ch)
   LONG ch;
{
   int error, value;
   struct ExtIOB *iob, controlIOB;

   iob = &controlIOB;
   iob->ioa_Request.io_Device    = device;
   iob->ioa_Request.io_Message.mn_ReplyPort = controlPort;

   InitBlock(iob,0);   /* init it for CMD_WRITE, then change */

   iob->ioa_Request.io_Message.mn_Node.ln_Pri = 20;
   iob->ioa_Request.io_Command = ADCMD_ALLOCATE;

   if(ch == -1)
   {   
	iob->ioa_Data = (UBYTE *)anychan;
      	iob->ioa_Length = 4;
   }
   else if(ch >=0 && ch <= 3)
   {   	
	/* Test to be sure that this channel is now free.  If
	 * usertask[i] is not zero, either the current task 
	 * has already allocated this channel, or a different
	 * task is now using it. */ 

	if(usertask[ch] != 0) return(-1);

	iob->ioa_Data = (UBYTE *)(&anychan[ch]);
      	iob->ioa_Length = 1;
   }
   else   /* chose a bad channel number; cannot allocate it */
   {   
	return(-1);
   }
   iob->ioa_Request.io_Flags = ADIOF_NOWAIT | IOF_QUICK;
   BeginIO(iob); 
   error = WaitIO(iob);  /* returns nonzero if error */
   if(!(iob->ioa_Request.io_Flags & IOF_QUICK))
   {   
	GetMsg(iob->ioa_Request.io_Message.mn_ReplyPort);
   }
   if(error)
   {   
	return(-1);
   }
   switch((LONG)(iob->ioa_Request.io_Unit))
   {   
	case  1:   value = 0;   break;
      	case  2:   value = 1;   break;
      	case  4:   value = 2;   break;
      	case  8:   value = 3;   break;
      	default:   value = -1;  break;
   }
   if(value == -1) return(-1L);

   unit[value]     = (iob->ioa_Request.io_Unit);
   key[value]      = (iob->ioa_AllocKey);
   usertask[value] = FindTask(0);	/* THIS user task owns it now */

   return(value);
}

int
FreeChannel(channel)
   LONG channel;
{
   int error;
   struct ExtIOB *iob, controlIOB;

   if(channel < 0 || channel > 3)	return(BAD_CHANNEL_SELECTED);
   if(usertask[channel] != FindTask(0))	return(NOT_YOUR_CHANNEL);

   iob = &controlIOB;
   iob->ioa_Request.io_Device    = device;
   iob->ioa_Request.io_Message.mn_ReplyPort = controlPort;

   InitBlock(iob,channel);   	/* init it for CMD_WRITE, then change it */
		        	/* (pick up unit, key value for channel) */
   iob->ioa_Request.io_Command = ADCMD_FREE;
   iob->ioa_Request.io_Flags = ADIOF_NOWAIT | IOF_QUICK;
   BeginIO(iob); 
   error = WaitIO(iob);  /* returns nonzero if error */

   if(!(iob->ioa_Request.io_Flags & IOF_QUICK))
   {   	
	GetMsg(iob->ioa_Request.io_Message.mn_ReplyPort);
   }
   usertask[channel] = 0;	/* free again... */
   if(error)
   {   
	return(error);
   }
   return(0);
}
 
/* SOME OF THE FOLLOWING ROUTINES ARE PARAPHRASED FROM A USENET and BIX
 * POSTING MADE IN 1985 BY STEVEN A. BENNETT. */ 
/* I have modified his routines to queue the audio commands in 
 * place of starting forever-duration and canceling each note.
 * Many of his original comments have been incorporated into
 * the article.*/ 

/* ******************************************************************* */
/* NOTE: There are some differences in PlayNote as compared to the article.
 * See the audiotools.DOC for details.*/
/* ******************************************************************* */

/* Starts a sound on the channel with specified period and volume. */
/* This nice little routine takes a note and plays it on the given
 * voice.  The note is basically an integer from
 * 0 to 11 (c to b) plus 12 per octave above the first and lowest. 
 *
 * The waveform to use is determined by adding an index (woffsets[]) 
 * dependent on the octave.
 *
 * The length of the waveform (in wlen[]) is likewise dependent on
 * the octave.  Note that octaves start with zero, not one.
 */
int 
/*PlayNote(channel, note, wf, vol, duration, priority, messageport, id)*/
PlayNote(channel, note, vol, duration)
/*   char *wf;   /* waveform to use */
/*   LONG vol, channel, duration, note;   /* specific note number */
/*   LONG priority;
   struct MsgPort *messageport;
   LONG id; */
   LONG  channel, note, vol, duration;
{
   LONG per, len, oct;   /* period, length of waveform, which octave */
   char *wavepointer;   /* where to find start of waveform */
   struct ExtIOB *iob;

   if(channel < 0 || channel > 3)	return(BAD_CHANNEL_SELECTED);
   if(usertask[channel] != FindTask(0))	return(NOT_YOUR_CHANNEL);
   if(note < 0 || note > 95) 		return(OUT_OF_RANGE_FREQ);
/*   if(priority < -128 || priority > 127)return(OUT_OF_RANGE_PRI);*/

   iob = GetIOB(channel);
 
   if(iob != 0)
   {
	InitBlock(iob, channel);   /* set up for CMD_WRITE */
   
	oct = note / 12;
/*   	wavepointer = wf + woffsets[oct];*/
   	wavepointer = w1 + woffsets[oct];     /* Always waveform #1 */
   	len = wlen[oct];
   	per = perval[note % 12];

   	/* Set the parameters */
   	iob->ioa_Data = (UBYTE *)wavepointer;
   	iob->ioa_Length = len;
   	iob->ioa_Period = per;
   	iob->ioa_Volume = vol;

/*	iob->ioa_Request.io_Message.mn_Node.ln_Pri = priority;*/
	iob->ioa_Request.io_Message.mn_Node.ln_Pri = 0;  /* always pri=0*/
   	/* additions for support of tell-me-when-note-starts */

/*    	iob->iob_Identifier = id;*/
      	iob->iob_Identifier = 0;     /* Always id = 0 */

	/* Initialize message port.  If 0, then no pushing back
	 * of a message.  If nonzero, message gets recirculated
	 * by ReEmployIOB until the user finally acknowledges it
	 * by using MayGetNote.
 	 */
/*	iob->ioa_WriteMsg.mn_ReplyPort = messageport;*/
	iob->ioa_WriteMsg.mn_ReplyPort = 0;   /* Always messageport = 0 */

/*	if(messageport != 0)
   	{  */
		/* If 0, no sending message when note plays;
		* if nonzero, user gets a message that can
		* be read by MayGetNote.  uport, received from 
		 * InitAudio, is where the message goes */

	    /* Tell the audio device to "reply" to this message */
/*	    iob->ioa_Request.io_Flags |= ADIOF_WRITEMESSAGE;
        }  */
 
   /* Calculate cycles from duration in 1000ths of a second */
   /* Multiply all-in-one to maintain max precision possible */
   /* (all integer arithmetic.) */

   iob->ioa_Cycles = SetCycles(duration,len,per);
   BeginIO(iob);
   return(0L);      /* all went ok */
   }
return(-99L);	/* (else-part) iob was zero, couldn't do the above. */
}

/* SetWaves(w1, w2, w3): create first sawtooth, triangle and square wave */

SetWaves(w1, w2, w3)
   UBYTE *w1, *w2, *w3;
{
   int i, increment, value, sqvalue;
   value = 0; increment = 2;
   sqvalue = 127;

   for (i = 0; i < BIG_WAVE; ++i)
   {
   w1[i] = i;   /* do the sawtooth */

   if(i > 62 && i < 180) increment = -2;
   else
   if(i >= 180) increment = 2;

   w2[i] = value;  value += increment;  /* triangle wave */

   if(i > 126) sqvalue = -127;

   w3[i] = sqvalue;
   }
return(0L);
}
  
/* ExpandWave(wfp) - replicate waves in decreasing sample sizes */
 
ExpandWave(wfp)
   BYTE *wfp;
   {
   int i, j, rate;
   BYTE *tptr;
 
   rate = 1;
   tptr = wfp + BIG_WAVE;
   for (i = 0; i < NBR_WAVES - 1; ++i)
      {
      rate *= 2;
      for (j = 0; j < BIG_WAVE; j += rate)
         *tptr++ = wfp[j];
      }
   return(0L);
   }
  
/*  MakeWaves --  Just makes a sawtooth, triangle and square wave
 *   in chip mem and expands them.*/

int 
MakeWaves()
{
   /* allocate the memory for the waveforms.
    */
   w1 = (UBYTE *)AllocMem(WAVES_TOTAL, MEMF_CHIP);
   w2 = (UBYTE *)AllocMem(WAVES_TOTAL, MEMF_CHIP);
   w3 = (UBYTE *)AllocMem(WAVES_TOTAL, MEMF_CHIP);

   if (w1 == NULL || w2 == NULL || w3 == NULL)
   return(-1L);   /* ran out of memory! */
 
   /* get and expand the waveforms    */

   SetWaves(w1, w2, w3);
   ExpandWave(w1);   chipaudio[0]=w1;
   ExpandWave(w2);   chipaudio[1]=w2;
   ExpandWave(w3);   chipaudio[2]=w3;
   return(0L);
}

/* ReEmployIOB is not new (as compared to article) but it
 * has changed a lot since the article was written.*/

/* ReEmployIOB - look at ALL of the reply ports and if any IOBs
	hanging around with nothing to do, free them.*/
 
ReEmployIOB()
{
   LONG i;
   struct MsgPort *mp;
   struct ExtIOB *iob;

   /* now declare a pointer to "the first iob pushed back onto the port" */

   struct ExtIOB *pushback;	

   /* What happens here is that iob's are removed from the message port
    * when they come back from the audio device.   If YOU have set the
    * messageport nonzero, it means that you wanted to know when

	*** THIS WILL CHANGE --- will be based on setting
	    the priority value to nonzero.

    * this note began to play.  The WriteMsg part of the iob is then
    * linked, as a message, onto your user port.  So this routine here
    * cannot free the iob until it is certain that YOU have finished
    * using it.  The iob_Priority field is READ here.  If it still
    * nonzero, the iob is pushed back onto the message port (on the
    * end of the message queue) to be read again.  We hold a pointer
    * named pushback that lets us keep track of when we see that
    * again.  If we see it twice, it means we have completed a full
    * circle through the queue of messages and have freed everything
    * that we can this time.  Therefore, we examine it and either
    * free it or push it back again, then exit.*/

   for(i=0; i<4; i++)   /* remove all iob's from ALL ports */
			/* (that is, unless we have to push one back) */
   {   
      mp = replyPort[i];

      pushback = (struct ExtIOB *)0;	/* nothing pushed back so far */

      while((iob = (struct ExtIOB *)GetMsg(mp)) != 0)
      { 
	    /* Here is what triggers the Identifier message for MayGetNote */

	    /* First see if messageport in WriteMsg is greater than zero;
	     * if so, audio device is done, but user hasnt acknowledged
	     * this message yet (by using MayGetNote).*/
	    if(iob->ioa_WriteMsg.mn_ReplyPort != 0)
	    {
		/* If we get here, we already know we will push it back */
		/* because User has not yet freed it (using MayGetNote) */

		PutMsg(mp, iob);

		if(iob == pushback) /* If so, we saw it already */
		{
			break;	   /* Go out to empty next port */
		}
		if(pushback == 0)
		{
			pushback = iob;	/* Remember FIRST one pushed back */
		}
	    }
	    else	/* messageport value is zero, can free the iob */
	    {
#ifdef DEBUG
	        printf("freeing %ls iob; ID=%ld\n",
        	      iob->ioa_Request.io_Message.mn_Node.ln_Name,
              	      iob->ioa_Request.io_Message.mn_Length);
#endif
            	FreeIOB(iob,i);
	    }
      }

   }
   return(0L);
}

LONG
SetCycles(duration, len, per)
   LONG len, per;   /* period, length of waveform, which octave */ 
   LONG duration;
{
   /* Calculate cycles from duration in 1000ths of a second */
   /* Multiply all-in-one to maintain max precision possible */
   /* (all integer arithmetic.) */

   long cycles, ipart;
   long dividend, divisor;

   if(duration == 0) return(0);  /* forever is forever */
   ipart = 0;

   if(duration > 1000)
   {
	ipart = duration/1000;
        duration = duration - (ipart * 1000);
   }

   /* Fool it a little so we don't get integer overflow...
    * 3.5 million times 1000 is about all we can take in a 32 bit word*/
	/* TicksPerSec: either 3579545 (NTSC) or 3546895 (PAL) */
   dividend = (TicksPerSec * ipart) + 
					((long)TicksPerSec * (long)duration)/(long)1000 ;
   divisor  = len * per;

   cycles   = (((long)dividend *10/ (long)divisor) + 5) / 10; /* Round */

   if(duration != 0 && cycles == 0) cycles = 1;
   return(cycles);
}

struct MsgPort *myport;

atool31(v)
   int v;
{
   LONG i, channel, error;

   if (v > 0) goto Fin;

   myport = InitAudio();
   if(myport == 0) 
   {
	printf("Problem in InitAudio!");
	FinishAudio(myport);
   }
   for(i=0; i<4; i++) 
   {   
      channel = GetChannel(-1);
      if(channel == -1)
      {
	   printf("cannot get a channel!\n");
	   FinishAudio(myport);
      }

      error = StopChannel(channel);
      if(error) 
      {  
	 printf("error in stopping channel = %ld\n",error);
         FinishAudio(myport);
      }
   }

   return(0);

Fin:
   FinishAudio(myport);   /* NEW parameter for FinishAudio */

/*   printf("Done!\n");*/
   return(0);
}         /* end of atool31() */

StartChans()
{
 int i, error;
 for(i=0; i<4; i++)
 {   
	error = StartChannel(i);
	if(error)  
	{
		printf("error starting channel = %ld\n",error);
		FinishAudio(myport);
	}
 }
}
