/* audiotools.c */

#define DEBUG 1

#include "exec/types.h"
#include "exec/memory.h"
#include "devices/audio.h"
#include "ram:audiotools.h"
#include "ram:globals.c"
extern APTR AllocMem();
extern struct Message *GetMsg();

main()
{
	LONG i, channel, error;

	InitAudio();

	for(i=0; i<4; i++) 
	{	channel = GetChannel(-1);
		if(channel == -1)  finishup("cannot get a channel!");

		/* At this point, must save globals from gotkey, gotunit */
		key[i] = gotkey;	/* save allocation key */
		unit[i] = gotunit;	/* save unit value     */

		error = StopChannel(channel);
		if(error) 
		{  printf("error in stopping channel = %ld\n",error);
		   finishup("StopChannel did not work as expected");
		}
	}
	/*  (channel, note, waveform, vol, duration, priority,message) */

	for(i=0; i<95; i++)
	{
		PlayNote(0, i, w1, 32, 250, 0, 0); /* all notes, 1/4 sec. */
	}
	error = StartChannel(0);
	Delay(800);	/* let most of them play... this waits 16 seconds */

	for(i=1; i<4; i++)
	{	error = StartChannel(i);
		if(error)  printf("error starting channel = %ld\n",error);
	}
	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);

	FinishAudio();
	return(0);
}			/* end of main() */
 
InitAudio()
{
   	int error,i;

	/* 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) finishup ("audio device won't open!");

   	/* 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) finishup("cannot create a port!");
		chipaudio[i] = 0;  /* have not yet created the waves */
		datalength[i] = 1; /* used for custom sound samples  */
	}
	controlPort = CreatePort(0,0);
	if(controlPort == 0) finishup("can't create control port");
	error = MakeWaves();
	if(error == -1)  finishup("waves won't fit in RAM!");

	for(i=0; i<4; i++)
	{ dynamix[i] = 0; }	/* no dynamic I/O blocks allocated 
			         * for any channel thus far */
	return(0);
}
 
FinishAudio()
{
	LONG i;
	struct ExtIOB *iob;
   	for(i=0; i<AUDBUFFERS; i++)
	{	if(inuse[i] == YES)
		{	/* make sure all global blocks are done */
			WaitIO(&audbuffer[i]);
		}
   	}
#ifdef DEBUG
	printf("All global I/O blocks are done\n");
	printf("channels 0,1,2,3 have %ld,%ld,%ld,%ld blocks in play\n",
		dynamix[0], dynamix[1], dynamix[2], dynamix[3]);
#endif DEBUG
	for(i=0; i<4; i++)

    	{   if(dynamix[i])  /* If this channel still playing a   */
			    /* dynamically allocated block, wait */
			    /* for all messages to return before */
			    /* the program exits.                */
	    {  
	emptyit:   
		iob = (struct ExtIOB *)GetMsg(replyPort[i]);
		if(iob == 0 && dynamix[i] != 0)	/* if no message arrived... */
		{  WaitPort(replyPort);  /* wait for  I/O done */
		   goto emptyit;	 /* and empty the port */
		}
		FreeIOB(iob,i);
		if(dynamix[i] != 0) goto emptyit;
	    }
	}
   	for(i=0; i<4; i++)   FreeChannel(i);
	finishup("Done!\n");
	return(0);
}
 
finishup(string)
char *string;
{
      	int i;
      	
	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);
	printf("%ls\n",string);
   	exit(0);
	return(0);
}
 
int
ControlChannel(channel, command)
	WORD channel;
	WORD command;
{
	LONG rtn;
	struct ExtIOB *iob, controlIOB;

	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 a 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);
	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("Allocated a new dynamic iob\n");
#endif DEBUG
		return(iob);
	}
return(0);
}
 
/* 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;

	for(i=0; i<4; i++)	/* remove all iob's from all ports */
	{	mp = replyPort[i];
		while( (iob = (struct ExtIOB *)GetMsg(mp)) != 0)
		{ 
#ifdef DEBUG
		  printf("type of iob freed is: %ls\n",
			  iob->ioa_Request.io_Message.mn_Node.ln_Name);
		  printf("its identifier value is: %ld\n",
			  iob->ioa_Request.io_Message.mn_Length);
#endif DEBUG
		  FreeIOB(iob, i);
		}
	}
	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;
#ifdef DEBUG
		printf("Freeing global buffer numbered %ld\n",i);
#endif DEBUG	
		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_WriteMessage
	*/
	/* 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" stereo pair, use pair = -1;
 * To request a specific stereo pair, use pair = {0, 1, 2 or 3}
 * corresponding to channels 0 and 1, 0 and 2, 1 and 2 or 1 and 3
 * respectively.
 */
int
GetStereoPair(pair)
	LONG pair;
{
	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 */

	/* set precedence of the request for a channel */
	iob->ioa_Request.io_Message.mn_Node.ln_Pri = 20;

	/* Type of command is ALLOCATE */
	iob->ioa_Request.io_Command = ADCMD_ALLOCATE;
	if(pair == -1)
	{	/* Point to the allocation map */ 
		iob->ioa_Data = (UBYTE *)stereostuff;
		
		/* It contains 4 entries */
		iob->ioa_Length = 4;
	}
	else if(pair >=0 && pair <= 3)
	{	iob->ioa_Data = (UBYTE *)(&stereostuff[pair]);
		iob->ioa_Length = 1;
	}
	else	/* chose a bad channel pair; cannot allocate it */
	{	
		return(-1);
	}
	/* Don't wait for allocation, channels
	 * should be available!  If we don't set
	 * ADIOF_NOWAIT, the task will idle waiting
	 * for a chance to allocate the channel, 
	 * looking again each time another task
	 * allocates or frees a channel.         
	 */
	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))
	{	/* if flag not set, then the message
		 * was appended to the reply port 
		 * (was not quick I/O after all)  */
		GetMsg(iob->ioa_Request.io_Message.mn_ReplyPort);
	}
	if(error)
	{	return(-1);
	}
	/* Save the values... freeing the IOB on exit */
	gotunit   = (iob->ioa_Request.io_Unit);
	gotkey	  = (iob->ioa_AllocKey);
		
	switch((LONG)(iob->ioa_Request.io_Unit))
	{	case  3:	value = 0;	break;
		case  5:	value = 1;	break;
		case 10:	value = 2;	break;
		case 12:	value = 3;	break;
		default:	value = -1;	break;
	}
	return(value);
}
 
/* To request "any" channel, use ch = -1;
 * To request a specific channel, use ch = {0, 1, 2 or 3};
 */
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)
	{	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);
	}
	gotunit	  = (iob->ioa_Request.io_Unit);
	gotkey    = (iob->ioa_AllocKey);
	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;
	}
	return(value);
}
 
int 
FreeChannel(ch)
	LONG ch;
{
	int error;
	struct ExtIOB *iob, controlIOB;

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

	InitBlock(iob,ch);	/* init it for CMD_WRITE, then change       */
				/* (pick up unit and 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);
	}
	if(error)
	{	return(-1);
	}
	return(0);
}
/* NOTE:  FreeChannel should work as FreeStereoPair(pair) too! */
 
/* 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(...) */
/* 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, message)
   char *wf;	/* waveform to use */
   LONG vol, channel, duration, note;	/* specific note number */
   LONG priority;
   struct Message *message;
   {
   LONG per, len, oct;	/* period, length of waveform, which octave */
   char *wavepointer;	/* where to find start of waveform */
   struct ExtIOB *iob;
   int frequency;
   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;
 
/* PlayNote (continued) */

	/* 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 */
   }
   else
   {	return(-1);		/* couldnt get IOB */
   }
return(0);
}
  
/* SetPV(channel, per, vol)
 *   int channel, per, vol;
 */
int 
SetPV(channel, per, vol)
   int channel, per, vol;
   {
   int error;
   struct ExtIOB *iob, controlIOB;

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