[comp.sources.amiga] v02i006: Audio tools, release 2.

rap@dana.UUCP (Rob Peck) (07/24/87)

    Here is the audiotools library that you may have read about in AmigaWorld.
Written by Rob Peck, this is the latest release that will compile under both
Aztec (+L option needed) and Lattice.  Also be sure to note the differences
in some of the routines from the article.
    Enjoy!
	-Doc
    
#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:	Shell Archiver
#	Run the following text with /bin/sh to create:
#	README
#	audiotools.doc
#	auToolDemo1.c
#	audiotools2.c
# This archive created: Fri Jul 24 13:28:01 1987
# By:	Craig Norborg (Purdue University Computing Center)
cat << \SHAR_EOF > README
As I promised, for usenet posting, here is the package
audiotools (release 2) enhanced from that published in
Amiga World July August 87.  New parameters for FinishAudio;
InitAudio returns a parameter now; new routine MayGetNote
for synchronizing with audio device against a queued note.
One file to include.  Works with Manx (cc +L... ln c32.lib...).
Works even if two tasks both linked with audio tools start
simultaneously.  (As written, only one gets the channel, other
dies cause it can't get the channel).

Future plans - make it into a regular audio.library; rethink
some of the globals (but still keep the user in mind re isolating
user from details of mem allocation and message passing).
Implement more of what article suggestions there were, including
ADSR method presented in IFF specs.

(Also, priority field redefined from that shown in article...
now matches priority defined for audio device itself; user must
explicitly flush channel to get effect that article suggested
for priority value.  PlayNote and PlayChannel both received one
new parameter - identifier - for identifying note requested.)

Regards,

Rob Peck
SHAR_EOF
cat << \SHAR_EOF > audiotools.doc


RELEASE 2 - AUDIO TOOLS - DOCUMENTATION
---------------------------------------

The RELEASE.2 directory contains the source and executable program
for the second release of the audio tools, called audiotools2.c.  
The example program is called rel2.demo.c.

It includes the following functions that are designed to be used by
the casual user as well as a developer.  A one-line description is
provided here, with complete documentation for each following later
in this document.

The functions:

  port = InitAudio()		initialize the audio routines, get a port
				for receiving messages from audio routines
  channel = GetChannel(type)	request a channel to use.
  error = FreeChannel(channel)	free a channel that you have been given.
  FinishAudio(port)		end the use of audio routines and return
				the port you received from audio routines

  error = StopChannel(channel)	temporarily halt a channel you own
  error = StartChannel(channel) start your channel up again.
  error = FlushChannel(channel) empty the queue of a channel from any notes
				it is playing or is going to play.
  error = ResetChannel(channel) reset a channel

  PlayNote( ... parameters ...) queue up a specific note number to be played
				by one of your channels.
  PlayFreq( ... parameters ...) queue up a specific frequency instead of note.

  IsThatMyChan(channel)		see if your task still owns a particular
				channel (audio system allows stealing of
				channels if they are not locked at a high
				enough priority).  

  note = MayGetNote(port, flag) see if a note you marked has begun to play.
				possibly put task to sleep if it has not.
				good for synchronizing graphics and sound.

  SetPV(channel,per,vol)	Set the period and/or volume of a channel
				that is already playing a waveform.  Limited
				use (except for volume, that is; see the
				article text to see why you may not want to
				use this routine to set the period.  In fact,
				I will probably add routines that do volume
				and period separately - routine has to
				be able to read what is now playing to
				be able to modify it properly.)


USING THE ROUTINES
------------------

To use the routines, the proper minimal code sequence is:

	1. Declare a pointer to a message port:

		struct MsgPort *myport;

	2. Declare a LONG integer variable to hold a channel number:

		LONG channel;

	3. Initialize the audio routines, if successful, the init
	   returns a pointer to a message port:

		myport = InitAudio();

	4. Get a channel to use for your output:

		channel = GetChannel(-1); 

	   where -1 means "any" available channel,
	   0, 1, 2, or 3 means a specific channel.

	   If a value of 0, 1, 2, or 3 is returned,
	   you have that channel to use.

	5. Choose a note or frequency to play, and its duration,
	   the waveform to use, and so on (see detailed
	   descriptions of PlayNote and PlayFreq).

		error = PlayNote(... parameters ...);

		    or

		error = PlayFreq(... parameters ...);



SHAR_EOF
cat << \SHAR_EOF > auToolDemo1.c
#define DEBUG 1

#include "exec/types.h"
#include "exec/memory.h"
#include "devices/audio.h"
#include "ram:audiotools2.c"

main()
{
   LONG i, channel, error, note;

   struct MsgPort *myport;   /* CHANGE from article */

   myport = InitAudio();   /* now returns address of message port */
   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);
      }
   }
   /*  (channel, note, waveform, vol, duration, priority,messageport, id) */

   for(i=0; i<49; i++)
   {
      /* frequency of "zero" is not useful */

      PlayFreq(0, (i+1)*10, w1, 32, 125, 0, 0, 0); /* all notes, 1/8 sec. */
      PlayNote(1, 95-i,     w2, 32, 125, 0, 0, 0); 
   }

   /* SMACK IN THE MIDDLE HERE, INSTALL TEST OF MESSAGING */
   /* cause it to flash the screen when this note begins to play */
   /* Make the note distinctive so we can tell for sure */

   PlayNote(1, 3,           w3, 63, 500, 0, myport, 3);  /* very LOUD and low */
   
   for(i=50; i<95; i++)
   {
      PlayFreq(0, (i+1)*10, w1, 32, 125, 0, 0, 0); /* all notes, 1/8 sec. */
      PlayNote(1, 95-i,     w2, 32, 125, 0, 0, 0); 
   }

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

   /* Now SLEEP while waiting for that note to play */

   printf("Going to sleep now - the prioritized note will wake me up\n");

   note = MayGetNote(myport, TRUE);	/* FALSE means don't sleep */

   printf("\n\n\n****************************************************\n");
   printf("Hey!  Note I identified as: %ld just started to play\n",note);
   printf("\n****************************************************\n\n\n");

   /* When we hit the PlayNote, it calls GetIOB, which in turn
    * begins to remove iob's from the reply port, freeing them
    * for future use.  You might consider changing the ReEmployIOB
    * routine to simply free 1 or 10 or whatever number, just so 
    * you could begin to play another note before ALL of the already
    * played note's iob's had been freed.  But when DEBUG is turned
    * off, the freeing goes a lot faster.
    */

   Delay(250);	/* waits 5 seconds so that all of these synchronize */

   PlayNote(0, 23, w1, 32, 2000, 0, 0);
   PlayNote(1, 27, w2, 32, 2300, 0, 0);
   PlayNote(2, 30, w3, 32, 2600, 0, 0);
   PlayNote(3, 35, w1, 32, 2900, 0, 0);

   Delay(150);	/* Waits 3 seconds after letting the last note begin
		 * (because last note is 2900/1000ths long).
		 * If you take out this delay, you'll see that
		 * FinishAudio means FINISH AUDIO... it cuts off
		 * the notes right in the middle if necessary!
		 */
   FinishAudio(myport);   /* NEW parameter for FinishAudio */

   printf("Done!\n");
   return(0);
}         /* end of main() */
SHAR_EOF
cat << \SHAR_EOF > audiotools2.c
/* audiotools.h built in here so that audiotools is a package deal */

#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 */

/* REDEFINITION from article - now contains one new field at the bottom
 * of the data structure.
 */
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 		GetStereoPair();
extern int 		InitBlock();
extern struct MsgPort 	*CreatePort();

extern APTR 		AllocMem();
extern struct Message 	*GetMsg();
extern struct Task	*FindTask();

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

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

/* forward declaration */
extern struct MsgPort *InitAudio();

/* 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.
				 */

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 stereostuff[4] 	= { 3, 5, 10, 12 };	/* channel masks for stereo */
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;
BYTE *owptr[4] 	= { NULL, NULL, NULL, NULL };

/* InitAudio is different from that published in the article -
 
InitAudio now returns a value, an address of a message port at which
your task receives a message when a particular note BEGINS to play.
You must save this value somewhere, and use it to call MayGetNote
or FinishAudio.  MayGetNote is the name of the routine that you call
to check if a note has begun to play.

InitAudio also has been modified to return to its caller when there
are problems, rather than calling finishup routines itself.  This is better
programming practice.

*/

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 and dynamics.  Note that
          * this will only work if NO (REPEAT: 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 DEBUG
      for(i=0; i<AUDBUFFERS; i++)
      {
         if(inuse[i] == YES)
         {
#ifdef DEBUG
         printf("iob still in use is: %ld\n",i);
#endif DEBUG
         return(0);
         }
      }
      printf("All global I/O blocks are done\n");

      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 really 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 */
   }
   ReEmployIOB();   /* free all static and dynamic messages */

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

no_init:
   if(device) CloseDevice(&openIOB);
   printf("closed the device\n");

   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 DEBUG
      return(&audbuffer[i]);
       }
   }
   /* if all globals are in use, have to allocate one */
   iob = (struct ExtIOB *)AllocMem(sizeof(struct ExtIOB),
                     MEMF_CLEAR | MEMF_FAST );
   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 DEBUG
      	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. 
	 */

	/* NOTE ***** ENHANCEMENT COMING HERE ***** */

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

	/* Enhancement might be: look at the running priority
 	 * of the current task as compared to the running priority
	 * of the task in usertask[i].  If not same task and if
	 * the current task has a higher priority, STEAL the channel!
	 * Alternative (seems better) is to have a global variable
	 * called audPriority to be set by a new function SetAudPriority
	 * (for a given task only), and that global priority value
	 * would be used for GetChannel and LockChannel requests.
	 */
	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 */

   /* Educational note - the docs indicate that audio, even though 
    * told to do an io item as "quickio", can, if it chooses, not do
    * it quick.  In that case, the reply is queued to the
    * reply port and it has to be removed.  That is why
    * the following status check has been installed here.
    * (Actually this check probably can be removed because
    * WaitIO, just above, removes the message from the port
    * if it does arrive there after all.)
    */
   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. 
 */

/* PlayNote(...) */

/* ******************************************************************* */
/* 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[]) 
 * dependant on the octave.
 *
 * The length of the waveform (in wlen[]) is likewise dependant on
 * the octave.  Note that octaves start with zero, not one.
 */
int 
PlayNote(channel, note, wf, vol, duration, priority, messageport, id)
   char *wf;   /* waveform to use */
   LONG vol, channel, duration, note;   /* specific note number */
   LONG priority;
   struct MsgPort *messageport;
   LONG id;
{
   LONG per, len, oct;   /* period, length of waveform, which octave */
   char *wavepointer;   /* where to find start of waveform */
   struct ExtIOB *iob;
   int frequency;

   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];
   	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;

   	/* additions for support of tell-me-when-note-starts */

      	iob->iob_Identifier = id;

	/* 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;

	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;
        }
 
   /* Look at the frequency that it is to play by backwards calc. */
   frequency = 3579545 / (len * per);

   /* Calculate cycles from duration in 1000ths of a second */
   /* Multiply all-in-one to maintain max precision possible */
   /* (all integer arithmetic.) */

   iob->ioa_Cycles = ((LONG)(frequency * duration)/1000);
   BeginIO(iob);
   return(0);      /* all went ok */
   }
return(-1);	/* (else-part) iob was zero, couldn't do the above. */
}

  
/* SetPV(channel, per, vol)
 *   int channel, per, vol;
 */
int 
SetPV(channel, per, vol)
   int channel, per, vol;
   {
   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);   /* set up for CMD_WRITE */
   
   iob->ioa_Period = per;
   iob->ioa_Volume = vol;
   iob->ioa_Request.io_Command = ADCMD_PERVOL;
   iob->ioa_Request.io_Flags   = IOF_QUICK | ADIOF_PERVOL;
   BeginIO(iob);   /* This one will be synchronous; affects whatever
          * is playing on this channel at this time.
          */
   error = WaitIO(iob);   /* OK to wait, since it will return */
   return(error);      /* copy of io_Error field; should be 0 */
}
 
/* 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(0);
}
  
/* ExpandWave(wfp) - replicate waves in decreasing sample sizes
 *   BYTE *wfp;
 */
 
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(0);
   }
  
/* 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(-1);   /* 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(0);
}

/* =================================================================== */
/* Here are NEW routines that did not appear in the AmigaWorld article.
 * These are in addition to the changes noted above for InitAudio and
 * FinishAudio, and changes in main() to accommodate them.
 */

/* PlayFreq in this version is for INTEGER values of frequency only.
 *
 * To be more precise, you'll have to convert the integer calculations
 * to floating point.   Almost identical to PlayNote, however, had to
 * add some error checking to the parameters AHEAD OF GetIOB because
 * if the frequency is out of range of what we have in our wave tables
 * currently, we have to reject the command.  If it got rejected
 * after Getting an IOB, then we'd have to free it.
 */
int 
PlayFreq(channel, freq, wf, vol, duration, priority, messageport, id)
   char *wf;   /* waveform to use */
   LONG vol, channel, duration, freq;   /* specific integer frequency */
   LONG priority;
   struct MsgPort *messageport;
   LONG id;
{
   LONG per, len, oct;   /* period, length of waveform, which octave */
   char *wavepointer;   /* where to find start of waveform */
   struct ExtIOB *iob;
   LONG i, isave, accept;

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

   /* see if we CAN represent this frequency, if not, reject it */

   for(i=0; i<9; i++)
   {
	   /* Now figure out which waveform to use...
    	    * start with the first wlen value because
    	    * we want to use the longest waveform we can.
    	    */
   	isave = i;   

   	accept = FALSE;

   	per = 3579545 / (freq * wlen[i]);

   	if(per > 127 && per < 500)
   	{
      		accept = TRUE;
      		break;
   	}
   }
   if(accept != TRUE) 			return(OUT_OF_RANGE_FREQ);

   iob = GetIOB(channel);

   if(iob != 0)
   {
   	InitBlock(iob, channel);   /* set up for CMD_WRITE */

   	oct = isave;         /* show the octave we are in */
   	wavepointer = wf + woffsets[oct];   /* point to longest wave */
   	len = wlen[oct];      /* show its length */

      	/* 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;

   	/* additions for support of tell-me-when-note-starts */
      	iob->iob_Identifier = id;

	/* see note on same line for PlayNote above */
	iob->ioa_WriteMsg.mn_ReplyPort = messageport;

	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 what should be the value of
		 * messageport. 
		 */ 

	    /* 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 = ((LONG)(freq * duration)/1000);
   BeginIO(iob);
   return(0);      /* all went ok */
   }
return(-1L);	/* (else-part) iob value was zero, out of memory probably */
}


/* Another new routine called MayGetNote.  Its syntax is:
 *
 *      note = MayGetNote(uport, flag)
 *
 * where uport is the address of the port you received from InitAudio.
 *
 *       flag  is a value of 0 or 1.
 *
 *          when flag = 0, the routine returns immediately, with
 *               a note value of 0 (no note available), or
 *               the value of the first note notification 
 *               to arrive at the port.
 *
 *          when flag = nonzero, the routine will wait if (and only if)
 *               there is no note to be had.  In other words,
 *               you can cause your task to go to sleep until
 *               the next note begins to play.  You decide
 *               what to do for a specific note value.
 *
 *     note  is the value you placed into the PlayNote or PlayFreq
 *               identifier field.  A report of this type
 *               is ONLY generated when "messageport" is nonzero. 
 *   
 *   CAUTION - if there are no more notes with messageport nonzero in
 *         the queue and you specify TRUE for the flag, you can cause your
 *         task to sleep forever!!
 */

LONG
MayGetNote(uport, flag)
   struct MsgPort *uport;
   LONG flag;
{
   struct auMsg *aum;
   LONG wakemask;
   struct Message *writemsg;
  
grabmessage:
   aum = (struct auMsg *)GetMsg(uport);   /* is a message there? */

   if(aum)   /* There was a message! */ 
   {
      /* The user has seen this msg, so the system can deallocate
       * the iob in which it occurs anytime in the future.
       * Now that we have received the message at our own reply
       * port, it belongs to us and we can do whatever we want
       * to it.  Set the reply port value to zero now, as a signal
       * to FreeIOB that it can really do that! 
       */
      aum->au_Message.mn_ReplyPort = 0;

      return(aum->aum_Identifier);  /* return the value */
   }
   if(flag) /* If nonzero, we cause caller to sleep while
             * waiting for any identified iob to appear.
             */
   {
      wakemask = WaitPort(uport);  /* not using wakemask */
      flag = 0;
      goto grabmessage;   /* a goto!  aaarrrggghhh! */
   }
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.
 *
 *       In the article, this routine removed iob's from the
 *       reply ports via the usual GetMsg function.  This
 *       was not practical in this case when adding the support
 *       for audio messages from the device.  
 *
 *  Problem was this:
 *       Audio may still be playing the waveform as we get a
 *       message through MayGetNote.  MayGetNote marks the
 *       iob message block as free-able, (when it finds that
 *       the identifier field is set to zero) but we have to have a
 *       way of recirculating in this list of messages.
 *
 *       In other words, if something is free-able, free it,
 *       otherwise leave it on the list.  So rather than
 *       removing things from the front of the list, lets just
 *       walk through the message list, Remove (dequeue) what is 
 *       freeable and leave the rest there to look at the next time.
 */

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 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.
    */

   /* Tiny drawback to the pushback that we do is that there can
    * be more than one dynamic IOB with the same identifier value.
    * The id value is not used anywhere, execept for the debug
    * reporting, so it is not critical.  Note that this is not
    * a problem with a pushback of a static iob because the inuse[]
    * array is being maintained for the statics.
    */
   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 DEBUG
            	FreeIOB(iob,i);
	    }
      }

   }
   return(0);
}

/* Use this next function to determine if you (still) own a particular channel.
 * The audio device has an arrangement by which a higher priority request
 * for a channel than the one that already owns it can be made.  The higher
 * priority request can actually cause a channel to be stolen from a user.
 * This feature may be implemented in a future version of audiotools,
 * (shared library version), in which, depending on the task's running
 * priority itself, a higher priority task could succeed at GetChannel
 * for a channel that is already owned by another task.
 */

int
IsThatMyChan(channel)	/* returns nonzero if YOU still own the channel */
	LONG channel;
{
	if(channel < 0 || channel > 3) 		return(0);
	if(usertask[channel] == FindTask(0)) 	return(TRUE);
	return(0);
}


SHAR_EOF
#	End of shell archive
exit 0