[net.micro.amiga] scale.c by Steven A. Bennett from BIX

crunch@well.UUCP (John Draper) (01/11/86)

/* SCALES
 *
 *   by Steven A. Bennett
 *
 *   This program demonstrates the use of the Audio functions
 * in the "ROM" to produce four voice sound.  It uses a simple
 * waveform (sawtooth) with no amplitude control (ie, envelope)
 * or frequency variation (ie, vibrato), but these can easily be
 * implemented through the use of setpv(), below.
 * This program may be freely distributed.
 *
 * NOTES
 *
 *   - This program was written fairly quickly, without the aid
 * of the ROM Kernal Manual (save about 10 pages of notes copied
 * from my dealer's copy).  Therefore, if anyone sees anything too
 * much out of the ordinary, bear with it.  (Particularly in mind
 * is CreatePort(), of whose use I inferred from several programs
 * I have downloaded from both BIX and CompuServe)  Furthermore, this
 * particular implementation was written with a full blown music
 * driver in mind, therefore, some code may seem ineffecient, for
 * the sake of speed.  Finally, I tend to get a bit sloppy when i'm
 * working past midnight, so there may be some messy areas.
 *
 * REVISIONS
 *
 *   12/25/85 - Single voice driver written. (SAB)
 *   12/26/85 - Multi-voice capability added. (SAB)
 *   12/28/85 - Error handling refined (see FinishProg()) (SAB)
 *   12/30/85 - Fixed cleanup routine. (SAB)
 *    1/10/86 - Mod to work with Both Manx and Lattice - crunch@well.UUCP
 *
 */
 
#include "exec/types.h"
#include "exec/memory.h"
#include "devices/audio.h"
#include "stdio.h"
 
#define  PRIORITY          10L    /* Priority for Audio Channel usage */
#define  NBR_IOA_STRUCTS   10L    /* Number of IOAudio structures used */
#define  PV_IOA_STRUCT     0L     /* index to ioapv struct */
#define  FIN_IOA_STRUCT    9L     /* index to finishioa struct */
#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  YES               1L
#define  NO                0L
 
extern struct MsgPort *CreatePort();
extern void *AllocMem();
 
UBYTE aMap[] = { 0x0f };                  /* allocate four channels */
long  voiceMap[] = { 1, 2, 4, 8 };
struct IOAudio *ioa, *finishioa, *ioapv;
struct IOAudio *ioainuse[4];
struct IOAudio *freeioa[4];
long unitno = 1;
int error;
int waiting[4] = { NO, NO, NO, NO };
int woffsets[] =
   { 0, 256, 384, 448, 480, 496, 504, 508, 510 };
int wlen[] =
   { 256, 128, 64, 32, 16, 8, 4, 2, 1 };
int perval[] =
   { 428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226, 214 };
BYTE *wptr;
BYTE *owptr[4] = { NULL, NULL, NULL, NULL };
 
char *portstring[] = {
   "Audio one",
   "Audio two",
   "Audio three",
   "Audio four",
   "Audio five",
   "Audio six",
   "Audio seven",
   "Audio eight" };              /* names for the CreatePorts */
 
/* InitIOA()
 *   This function initializes all IOAudio structures used by this
 * program.
 *
 * NOTES
 *   This version sets up enough IOAudio structures for four voices, plus
 * a ADCMD_FINISH and an ADCMD_PERVOL, which are both synchronous.
 * The ioainuse array is assumed to be the IOAudio structure which is
 * being used by the current CMD_WRITE command for each voice.  The freeioa
 * array is there so that no <click> is made when switching CMD_WRITEs.
 * (ie., for speed)  Since the pointers are swapped when the switch occurs,
 * one can always make the same assumption.  Note that ALL of the
 * asynchronous (ie., ioainuse and freeioa) structures ought to have a
 * unique ReplyPort (or so I think).  The IOAudio structure used to
 * Open the Device must also have a ReplyPort, but it may not have to be
 * unique.  I am taking no chances, however.
 */
 
InitIOA()
   {
   int i;
 
   /* alloc the IOAudio structures
    */
   ioa = (struct IOAudio *)AllocMem((NBR_IOA_STRUCTS * (long)sizeof(*ioa)),
      MEMF_PUBLIC | MEMF_CLEAR);
   if (ioa == NULL)
      FinishProg(1);
 
   /* set the various IOAudio structure pointers
    */
   for (i = 0; i < 4; ++i)
      {
      ioainuse[i] = &ioa[i + 1];
      freeioa[i] = &ioa[i + 5];
      }
   ioapv = &ioa[PV_IOA_STRUCT];
   finishioa = &ioa[FIN_IOA_STRUCT];
 
   /* Open the Audio Device.  This requires a ReplyPort, so we'll
    * make it now, and reuse it later as part of ioapv, although it
    * doesn't need one.  aMap[] is an array containing sets of
    * bitmaps which allow various combinations of voices to be allocated.
    * since we want all four voices, there is only one combination which
    * will serve.
    */
   ioa->ioa_Request.io_Message.mn_Node.ln_Pri = PRIORITY;
   ioa->ioa_Request.io_Message.mn_ReplyPort =
      CreatePort("Audio zero", 0L);
   if (ioa->ioa_Request.io_Message.mn_ReplyPort == NULL)
      FinishProg(2);
   ioa->ioa_Data = aMap;
   ioa->ioa_Length = (long)sizeof(aMap);
   error = OpenDevice(AUDIONAME, 0L, ioa, 0L);
   if (error)
      FinishProg(3);
 
   /* setup the finishioa and ioapv structs.  The IOF_QUICK flag
    * makes them synchronous in all cases.
    */
   *finishioa = *ioa;
   finishioa->ioa_Request.io_Flags = IOF_QUICK;
   ioapv->ioa_Request.io_Flags = IOF_QUICK;
 
   finishioa->ioa_Request.io_Command = ADCMD_FINISH;
   ioapv->ioa_Request.io_Command = ADCMD_PERVOL;
 
   /* setup the ioainuse and freeioa struct arrays.  All eight of
    * them must have unique ReplyPorts, hence the CreatePort()s
    * below.  portstring contains different port names for the
    * eight ports.  (I have no idea as to what the zero in the
    * CreatePort is used for, so don't ask.)
    */
   for (i = 0; i < 4; ++i)
      {
      *freeioa[i] = *ioa;
      *ioainuse[i] = *ioa;
      freeioa[i]->ioa_Request.io_Message.mn_ReplyPort =
         CreatePort(portstring[i], 0L);
      ioainuse[i]->ioa_Request.io_Message.mn_ReplyPort =
         CreatePort(portstring[i + 4], 0L);
      }
   for (i = 0; i < 4; ++i)
      if (freeioa[i]->ioa_Request.io_Message.mn_ReplyPort == NULL ||
         ioainuse[i]->ioa_Request.io_Message.mn_ReplyPort == NULL)
         FinishProg(4);
   }
 
/* FinishProg(finishcode)
 *    int finishcode;
 *
 *    displays an error message, if necessary, based on the value
 * of finishcode, frees all allocated stuff (again based on finishcode)
 * and then exits.
 *
 * NOTES
 *   This is basically a tidy little "clean up" routine used by all exit
 * routines in this program.  Since all the allocated "stuff" is supposed
 * to be in global variables, freeing it up in one global location is
 * much easier then doing it elsewhere.
 *
 */
 
char *errormsgs[] = {
   "Finished!\n",
   "Cannot allocate memory for IOAudio structures\n",
   "Cannot create ReplyPort for OpenDevice call\n",
   "Cannot open Audio Device\n",
   "Cannot create ReplyPort(s) for remaining IOAudio structures\n",
   "Cannot allocate memory for waveform\n",
   "If you see this during execution, execute the programmer.\n" };
 
FinishProg(finishcode)
   int finishcode;
   {
   int i;
 
   printf(errormsgs[finishcode]);
   switch(finishcode)
      {
   case 0:
      /* free up the WaveNode list
       * (currently, just wptr)
       */
      FreeMem(wptr, WAVES_TOTAL);
 
   case 4:
   case 5:
      /* free up all ReplyPorts save the first.  Since we could be
       * here due to an error in allocating same, we check first.
       * (And yes, Virginia, we do fall through here)
       */
      for (i = 0; i < 4; ++i)
         {
         if (freeioa[i]->ioa_Request.io_Message.mn_ReplyPort)
            DeletePort(freeioa[i]->ioa_Request.io_Message.mn_ReplyPort);
         if (ioainuse[i]->ioa_Request.io_Message.mn_ReplyPort)
            DeletePort(ioainuse[i]->ioa_Request.io_Message.mn_ReplyPort);
         }
 
      /* Close the Audio Device
       */
      CloseDevice(ioa);
 
   case 3:
      /* Delete the first ReplyPort
       */
      DeletePort(ioa->ioa_Request.io_Message.mn_ReplyPort);
 
   case 2:
      /* Free the ioa memory
       */
      FreeMem(ioa, (NBR_IOA_STRUCTS * (long)sizeof(*ioa)));
 
      }
   if (finishcode)
      exit(1L);
   exit(0L);
   }
 
/* setwpv()
 *
 *   starts a sound on the channel specified by the global variable
 * unitno.  By swapping between two IOAudio structures, this routine
 * is able to accomplish it's task without a noticeable delay.
 *
 * NOTES
 *   This routine should only be used when a waveform change is
 * required, either due to frequency going out of the range of the
 * existing waveform, or a different waveform has been chosen.  In
 * most cases, setpv() will be sufficient.  Also, don't complain about
 * the usage of io_Unit.  I didn't like it either.
 *
 */
 
setwpv(wf, len, per, vol, voice)
   char *wf;
   int len, per, vol, voice;
   {
   struct IOAudio *tmpioa;
 
   /* the next three lines are probably unnecessary and can be
    * done instead in InitIOA, but why take chances?
    */
   freeioa[voice]->ioa_Request.io_Command = CMD_WRITE;
   freeioa[voice]->ioa_Request.io_Flags = ADIOF_PERVOL | IOF_QUICK;
   freeioa[voice]->ioa_Cycles = 0;
 
   /* Assign the unit numbers to the (<ahem>) pointer?
    */
   freeioa[voice]->ioa_Request.io_Unit = (struct Unit *)unitno;
   finishioa->ioa_Request.io_Unit = (struct Unit *)unitno;
 
   /* Set the parameters
    */
   freeioa[voice]->ioa_Data = (UBYTE *)wf;
   freeioa[voice]->ioa_Length = len;
   freeioa[voice]->ioa_Period = per;
   freeioa[voice]->ioa_Volume = vol;
 
   /* Terminate the old request, if there is one.  waiting[] is an
    * boolean (well, pseudo-boolean) array stating if there was an
    * old request.  The old CMD_WRITE must be finished, and the reply
    * received, before the new CMD_WRITE will work.  I am not certain
    * why this is necessary.  Originally, I tried a
    *               BeginIO(freeioa[voice]);
    *               BeginIO(finishioa);
    *               WaitIO(ioainuse[voice]);
    * series, but the net effect was that the freeioa request was
    * ignored (without error), and the ioainuse request continued
    * blindly onwards.  I had assumed that it would act as a FIFO queue,
    * but apparently not.
    */
   if (waiting[voice])
      {
      BeginIO(finishioa);
      WaitIO(ioainuse[voice]);
      waiting[voice] = NO;
      }
 
   /* now start up the new voice
    */
   BeginIO(freeioa[voice]);
   error = CheckIO(freeioa[voice]);
   if (error)
      {
      printf("Error on CMD_WRITE\n");
      WaitIO(freeioa[voice]);
      }
   waiting[voice] = YES;
 
   /* swap the pointers.  That way, the next time we pass through, we
    * will still work.
    */
   tmpioa = ioainuse[voice];
   ioainuse[voice] = freeioa[voice];
   freeioa[voice] = tmpioa;
   }
 
/* setpv(per, vol)
 *   int per, vol;
 *
 *   Changes the period and volume of the currently executing
 * CMD_WRITE on the specified unitno.
 *
 * NOTES
 *   This routine is perfect <ahem> as it is, both for single and
 * multiple voices.  It can be used to change frequency, within limits,
 * change volume, and simulate vibrato and envelope controls.
 * For those of you wondering where I got my period values
 * from, look in the ABASIC manual, page 138.  Or, if you wish, you
 * can calculate it out for yourself using the formula:
 *     period = C/(ns*hz)
 *       - where C is the clock rate (3579545)
 *               ns is the number of samples in a cycle of the wave
 *               hz is the number of cycles per second.
 * Thus, for middle A on the piano (440 hz) with 32 samples in the
 * cycle of the waveform, one would get:
 *     period = 3579545 / (32 * 440)
 *            = 3579545 / 14080
 *            = 254.229 (or pretty close)
 * The period must be rounded to the nearest integer, which can result
 * in a maximum frequency error of about .25%, assuming one uses the
 * octave for frequency between period 226 and period 428.  (This comes
 * out to be less than a twentieth step at the shortest period)
 * period values of less than 127 are illegal, as there aren't enough
 * cycles set aside for audio DMA for anything less.  period values of
 * greater than 500 or so aren't recommended as the anti-aliasing
 * filter isn't of much use then, and actually could cause a possible
 * high pitched overtone, which I'm sure nobody wants.  Thus I am
 * only going to use this to handle a single octave's range.
 * Changes of octave are accomplished by doubling or halving the number
 * of samples in one cycle of the waveform, and must, therefore, call
 * setwpv().
 */
 
setpv(per, vol)
   int per, vol;
   {
   ioapv->ioa_Period = per;
   ioapv->ioa_Volume = vol;
   ioapv->ioa_Request.io_Unit = (struct Unit *)unitno;
   BeginIO(ioapv);
   }
 
/* StopVoices()
 *
 *   Terminates all CMD_WRITE routines in progress, effectively
 * ending all sound.  Volume is set to zero first, just to be tidy.
 *
 */
 
StopVoices()
   {
   int voice;
 
   for (voice = 0; voice < 4; ++voice)
      {
      if (waiting[voice])
         {
         /* to stop a voice, we first set it's volume to zero
          * (probably unnecessary) and then finish the CMD_WRITE.
          */
         unitno = voiceMap[voice];
         setpv(128, 0);
         finishioa->ioa_Request.io_Unit = (struct Unit *)unitno;
         BeginIO(finishioa);
         WaitIO(ioainuse[voice]);
         waiting[voice] = NO;
         }
      }
   }
 
/* setwave(wfp)
 *   BYTE wfp;
 *
 *   this routine makes the first 256 bytes (lowest octave) of the
 * sawtooth wave's waveform table.  wfp must have already been
 * allocated and in CHIP MEMORY!!!  This is necessary later for the
 * CMD_WRITE commands to work.
 *   a sawtooth wave is a simple waveform, very easy to use.  It
 * basically looks like this:
 *
 *     /|      /|      /|      /|
 *    / |     / |     / |     / |
 *   /  |    /  |    /  |    /  |
 *  /   |   /   |   /   |   /   |
 *      |  /    |  /    |  /    |  /
 *      | /     | /     | /     | /
 *      |/      |/      |/      |/
 *
 */
 
setwave(wfp)
   UBYTE *wfp;          /* This is a sneaky way of making a sawtooth */
   {
   int i;
 
   for (i = 0; i < BIG_WAVE; ++i)
      wfp[i] = i;
   }
 
/* xpandwave(wfp)
 *   BYTE *wfp;
 *
 * xpandwave expands a wave with only the base BIG_WAVE specified,
 * into a set of NBR_WAVES waveforms, each for one octave.
 * All of the waveforms are left in contiguous memory after the
 * first wave, in order of decending sizes (256, 128, 64, ...)
 *
 * NOTES
 *   Be forewarned that this function makes two assumptions.  The
 * first is that there is enough memory in the wfp buffer to hold the
 * expansion, and two, that the stuff is in chip mem.  (Actually,
 * makewaves() makes that assumption instead.)  This function ought to
 * work for any BIG_WAVE which is a power of two greater than 2^NBR_WAVES
 *
 */
 
xpandwave(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];
      }
   }
 
/* makewaves()
 *
 *   just makes a sawtooth waveform in chip mem and expands it without
 * the pretty list control and file IO stuff.
 *
 */
 
makewaves()
   {
   /* allocate the memory for the waveform.
    */
   wptr = (BYTE *)AllocMem(WAVES_TOTAL, MEMF_CHIP);
   if (wptr == NULL)
      FinishProg(5);
 
   /* get and expand the waveform
    */
   setwave(wptr);
   xpandwave(wptr);
   }
 
/* strike(note, voice)
 *   int note, voice;
 *
 * This nice little routine takes a note and plays it on the given
 * voice.  At a fixed amplitude.  The note is basically an integer from
 * 0 to 11 (c to b) plus 12 per octave above the first and lowest.  It
 * uses a pointer to the last waveform used by the voice specified to
 * determine if it needs to call setwpv() or just setpv().  The waveform
 * is used 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.
 */
 
strike(note, voice)
   int note, voice;
   {
   int per, oct, len;
   BYTE *wfp;
 
   unitno = voiceMap[voice];
   if (note >= 100)           /* play a rest. */
      {
      if (waiting[voice])
         setpv(200, 0);
      return;
      }
   oct = note / 12;
   per = perval[note % 12];
   wfp = wptr + woffsets[oct];
 
   /* if the waveform hasn't changed since the last strike,
    * then only change the period.
    */
   if (wfp == owptr[voice])
      setpv(per, 32);         /* fixed volume */
   else
      {
      setwpv(wfp, wlen[oct], per, 32, voice);
      owptr[voice] = wfp;
      }
   }
 
main()
   {
   int i, j;
 
   InitIOA();
   makewaves();
 
   /* simple scale
    */
   for (i = 0; i < 24; ++i)
      {
      strike(i, 0);
      Delay(10L);
      }
 
   /* crossed scales
    */
   for (j = 48; i < 48; ++i, --j)
      {
      strike(i, 0);
      strike(j, 1);
      Delay(10L);
      }
 
   /* crossed scales with lower in sync
    */
   for (j = 48; i < 72; ++i, --j)
      {
      strike(i, 0);
      strike(i - 48, 1);
      strike(j, 2);
      Delay(10L);
      }
 
   /* slow c chord...
    */
   strike(36, 0);
   strike(100, 1);
   strike(100, 2);
   Delay(60L);
   strike(40, 1);
   Delay(60L);
   strike(43, 2);
   Delay(120L);
 
   /* ...and into a minor with the fourth voice
    */
   strike(46, 3);
   Delay(200L);
 
   /* and two quick c chords to finish it up
    */
   restall();
   Delay(20L);
   cchord();
   Delay(20L);
   restall();
   Delay(1L);
   cchord();
   Delay(200L);
   StopVoices();
   FinishProg(0);
   }
 
restall()
   {
   int i;
 
   for (i = 0; i < 4; ++i)
      strike(100, i);
   }
 
cchord()
   {
   strike(36, 0);
   strike(40, 1);
   strike(43, 2);
   strike(48, 3);
   }