(c)  Copyright 1989 Commodore-Amiga, Inc.   All rights reserved.
The information contained herein is subject to change without notice, and 
is provided "as is" without warranty of any kind, either expressed or implied.  
The entire risk as to the use of this information is assumed by the user.






                            A SMUS Player
                            
                             by Dan Baker




     SMUS is the name of the IFF standard which provides for the storage of
musical scores.  Most score editors, such as Deluxe Music, allow the user
to store and read music data in the SMUS format.  By supporting  SMUS,
applications from different vendors can share music files directly without
special handling.

     SMUS files contain note and duration information.  A second IFF file
type, called 8SVX, contains information on the instruments for a score.
8SVX files contain an 8-bit sample for an instrument, usually at several
octaves, and some play back information.  Together, SMUS and 8SVX are
capable of representing most audio information.  This article presents
brief overview of the SMUS format and a program which will play back
SMUS scores on the Amiga.


SMUS Header Chunk

There are many chunk types in the SMUS form but only 3 need to be
considered for play back: SHDR, INS1 and TRAK.  SHDR is the score header
chunk and has the following structure:

     typedef struct { UWORD tempo;     /* tempo, 128th quarter notes/min */
                      UBYTE volume;    /* overall volume                 */
                      UBYTE ctTrack;   /* number of tracks               */
                    } SScoreHeader;

SHDR gives the overall tempo and volume.  It also tells how many seperate
tracks are contained in the file.  Typically, there is a track for each
channel or voice of the host computer.  For instance, Amiga SMUS files
often have four tracks - one for each Amiga audio channel.




SMUS Instrument Chunk

     For most SMUS files, the SHDR chunk is followed by one or more INS1
chunks.  The INS1 chunks identify instruments used in the score and have
the following structure:

     typedef struct { UBYTE register;     /* instrument register #  */
                      UBYTE type;         /* type: 0=named 1=midi # */
                      UBYTE data1,data2;  /* midi numbers           */
                      CHAR  name[];       /* instrument name        */
                    } RefInstrument;

INS1 chunks allow a playback program to load and set up instrument samples
from 8SVX files ahead of time.  The instrument samples are used each time a
note is played on the Amiga's audio hardware.  Note that INS1 chunks may be
ignored and a default instrument sample used instead.



SMUS TRAK Chunk

Instrument chunks are usually followed by 4 TRAK chunks.  Each TRAK
chunk consists of a stream of SEvents (score events) which describe the
frequency and duration of each note.  An SEvent is a 2-byte structure:

                                      /*  0-127 = note, 128=rest,    */
     typedef struct { UBYTE sID;      /*  > 128 = other SEvent type  */
                      UBYTE data;     /*  Note or rest duration      */
                    } SEvent;

     An SEvent sID value in the range 0-127 represents the corresponding
midi note number.  Middle C has a sID of 60.  Concert A has a sID of 69.
An SEvent sID of 128 indicates a rest.  An SEvent sID of greater than 128
indicates a TRAK command such as change instruments, change volume or 
change tempo.

The duration of the note or rest in an SEvent is given by the data field.  
Calcualting a duration from the data field is not straight forward - the
data byte is sub-divided into five bit-fields.  The bits are assigned as 
follows:


    MSB                                                     LSB

    +---+      +---+     +---+---+       +---+     +---+---+---+
    | 7 |      | 6 |     | 5 | 4 |       | 3 |     | 2 | 1 | 0 |
    +---+      +---+     +---+---+       +---+     +---+---+---+
    0=no      0=no tie  0 = no n-tuple   0=not       0=whole note
     chord    1=tied    1 = 2/3            dotted    1=half note
    1=chord             2 = 4/5          1=dotted    2=qrtr note
                        3 = 6/7                           :
                                                     7=128th note

To compute the duration you use:

          {1, 2/3, 4/5, 6/7}  *  {1, 3/2}  *  2 ! -{0, 1, 2...7}

where the numbers in brackets are indexed by the lower six bits.  To see
how this works, consider an example.  An SEvent data byte of 18 would be 
"0 0 01 0 010" which gives a duration of  {2/3} * {1} * {2 ! -2}.  That is
the 2/3 n-tuple, not dotted, times a quarter note.

The chord bit and tie bit do not affect the calculated duration.  Instead
they give information on how to handle the note.  The chord bit is typically 
not used.  Chords are normally recorded as simultaneous notes on different 
tracks not the same track.  The tie bit indicates that the current note 
should continue to play for the given duration instead of starting a new note.

The TRAK chunks are the main part of a SMUS file giving the tone and
duration for each note in the score.  The SMUS player spends most of its time
decoding TRAK chunk SEvents and playing them for the duration indicated.




The SMUS Player

The program shown below will play an IFF SMUS file on the Amiga's audio
hardware from the CLI.  To use the program, type "sread smusfile" at the 
CLI prompt.

The SMUS player uses the 8SVX instruments specified by the file.  The 8SVX
file is decoded into several sets of samples, an attack or one-shot part
and a sustain or repeating part at each of 8 octaves.  If the 8SVX file
has less than 8 octaves the missing ones are "filled in" by using the 
closest octave available.  A period table for each instrument gives the
playback sampling speeds to use for each of the 12 tones in an octave.

The SMUS player does not handle every aspect of the IFF specification. 
For instance, the ADSR chunks of an 8SVX voice files are skipped. Likewise,
tied notes and 1-TRAK chords are not supported.  The tied notes will be
played separately and only the first note of 1-TRAK chords will be played.
A default volume of 60 is used for all notes.  SEvents which give TRAK 
commands such as change instrument, change volume or change tempo are also
skipped.  

Each TRAK is decoded and played back in real-time without syncing them
to the system clock.  Two AudioIOBs are kept going for each channel so the
next note always begins as soon as the last one is done.  However since the 
SMUS player uses the timer device, there may be delays on a heavily loaded 
system.  In that case adjust the priority of the AudioIOBs and the task.  
The player works well with DMCS SMUS files.  However, scores that use Sonix
instrument files will not work since Sonix instruments use a proprietary 
format - they are not IFF.

SMUS and 8SVX are the only IFF standards that deal with audio.  These two 
file standards are described in detail in the IFF documentation and disk
available through CATS.  To order, send a check for $20 made out to
Commodore to:
                  CATS Orders
                  1200 Wilson Dr.
                  West Chester, PA 19380

Indidcate on the check or letter "IFF".  Allow 2-6 weeks for processing.


----------------------CODE STARTS HERE----------------------
#include "exec/types.h"
#include "exec/memory.h"
#include "libraries/dosextens.h"
#include "devices/audio.h"
#include "iff/iff.h"
#include "iff/smus.h"

#define SMUS MakeID('S','M','U','S')
#define SHDR MakeID('S','H','D','R')
#define INS1 MakeID('I','N','S','1')
#define TRAK MakeID('T','R','A','K')


struct FileHandle *Open();
extern APTR        AllocMem();
extern void        notes();      /* Plays the SEvents  */
extern void        killaudio();  /* Finish for notes() */
extern ULONG       setaudio();   /* Set up for notes() */
extern ULONG       get8();       /* Gets 8SVX sample   */
       void        kill();       /* Finish for main()  */
       LONG        keepout();    /* Break handling     */

/*---------------*/
/* G L O B A L S */
/*---------------*/
extern ULONG       ttable[];      /* To scale tempo     */
extern LONG        scount;        /* No. of samples     */
extern LONG        instrumentno;  /* No. of instruments */

struct FileHandle  *playhandle;      /* File handle for SMUS            */
       UBYTE       *psdata;          /* Pointer to start of SMUS data   */
       ChunkHeader *pChunkHeader;    /* Pointer to SMUS Header data     */
       SEvent      *ptrak[8];        /* 1st and last events for 4 traks */
       LONG         trakcount;       /* Number of traks */

/*-----------*/
/*  M A I N  */
/*-----------*/
LONG
main(argc,argv)
LONG argc;
char *argv[];

{
/*-------------*/
/* L O C A L S */
/*-------------*/
SEvent            *pSEvent;            /* Pointers for SMUS parts,    */
RefInstrument     *pRefInstrument;     /* buffers for instrument name */
SScoreHeader      *pSScoreHeader;      /* and FORM ID and counters.   */
Chunk             *pChunk;
char              *playname,inname[60],iobuffer[8];
LONG               x,rdcount,datacount,tscale;
ULONG              stat;
/*-------------*/
/*   C O D E   */
/*-------------*/
onbreak(&keepout);                     /* Lattice 4.0 Break Handling */

/*-----------------*/
/* Check Arguments */
/*-----------------*/
playhandle=0;scount=0;instrumentno=0;stat=1;
if(argv[1]==NULL) kill("No file name given.\n");

/*---------------*/
/* Open the File */
/*---------------*/
playname=argv[1];
playhandle=Open(playname,MODE_OLDFILE);
if(playhandle==0)  kill("Cannot open file.\n");

/*---------------*/
/* Read the File */
/*---------------*/
rdcount=Read(playhandle,iobuffer,8);
if(rdcount==-1) kill ("Bad file during read.\n");
if(rdcount<8)   kill ("Not an IFF file, too short.\n");

/*-----------------*/
/* Evaluate Header */
/*-----------------*/
pChunkHeader=(ChunkHeader *)iobuffer;
if( pChunkHeader->ckID != FORM ) kill("Cannot handle this IFF.\n");
if(pChunkHeader->ckSize > 65536) kill("SMUS file too big.\n");

/*------------------*/
/* Get the IFF data */
/*------------------*/
psdata=(UBYTE *) AllocMem(pChunkHeader->ckSize , MEMF_PUBLIC | MEMF_CLEAR);
if(psdata==0)    kill("Memeory allocate failed.\n");

rdcount=Read(playhandle,psdata,pChunkHeader->ckSize);
if(rdcount==-1)
   kill ("Choked on file during read.\n");
if(rdcount<pChunkHeader->ckSize)
   kill ("Malformed IFF, too short.\n");

/*-------------------*/
/* Evaluate IFF Type */
/*-------------------*/
if(MakeID( *psdata, *(psdata+1) , *(psdata+2) , *(psdata+3) ) != SMUS )
  kill("Sorry, this is not IFF SMUS.\n");

/*--------------*/
/* Find the IDs */
/*--------------*/
datacount=4;
trakcount=0;
while( datacount < pChunkHeader->ckSize)
  {

  pChunk=(Chunk *)(psdata+datacount);
  switch(pChunk->ckID)
    {
    case SHDR:
      /*----------------------*/
      /* Process Score Header */
      /*----------------------*/
      pSScoreHeader=(SScoreHeader *)(psdata+datacount+8L);
      tscale=( 8929 / ( pSScoreHeader->tempo >> 7) );
      for(x=0;x<64;x++)ttable[x]*=(ULONG)tscale;
      break;
    case INS1:
      /*------------------------------------*/
      /* Call get8() to Process Instrument  */
      /*------------------------------------*/
      pRefInstrument=(RefInstrument *)(psdata+datacount+8L);
      for(x=0;x<=pChunk->ckSize;x++) inname[x] = pRefInstrument->name[x];
      inname[pChunk->ckSize-4L]=0;
      stat=get8(inname);
      if(stat!=0)kill("Instrument problem\n");
      break;
    case TRAK:
      /*------------------------------------*/
      /* Set up pointers for TRAK handling  */
      /*------------------------------------*/
      if(trakcount<4)
        {
        pSEvent=(SEvent *)(psdata+datacount+8L);
        ptrak[trakcount  ]=pSEvent;
        ptrak[trakcount+4]=(SEvent *)(psdata+datacount+8L+pChunk->ckSize);
        trakcount++;
        }
      else puts("More than 4 tracks. Skipping...\n");
      pSEvent = (SEvent *)(psdata+datacount+8L+pChunk->ckSize);
      break;

    default:
      break;
    }
    /* end switch */

  datacount += 8L + pChunk->ckSize;        /* Go back for more Chunks */
  if(pChunk->ckSize&1L ==1) datacount++;
  }

/*-------------------------------*/
/*  Play SMUS File with notes()  */
/*-------------------------------*/
if(stat==0 && instrumentno != 0) /* Must have one 8svx instrument */
  {
  stat=setaudio();

  if(stat==0)notes(ptrak);

  /*---------------*/
  /*   Finish it   */
  /*---------------*/
  killaudio();
  kill8svx();
  }

/*-----------------------*/
/* Normal end of program */
/*-----------------------*/
if(playhandle!=0L)  Close(playhandle);
if(psdata    !=0L)  FreeMem(psdata,pChunkHeader->ckSize);
return(0L);

}



/*----------------------*/
/* Kill: Abort the Read */
/*----------------------*/
void
kill(killstring)
char *killstring;
{
   puts(killstring);
   if(playhandle!=0L)Close(playhandle);
   if(psdata !=0L)   FreeMem(psdata,pChunkHeader->ckSize);
   exit(0L);
}

/*-------------------------*/
/*    For Break Handling   */
/*-------------------------*/
LONG
keepout()
{                      /* Pointer to keepout() used in onbreak().  */
return (0);            /* This disables Lattice 4.0 break handling */
}




-------------------------MORE CODE STARTS HERE---------------------
#include "exec/types.h"
#include "exec/memory.h"
#include "graphics/gfxbase.h"
#include "libraries/dosextens.h"
#include "iff/iff.h"
#include "iff/8svx.h"

#define I8SVX MakeID('8','S','V','X')
#define VHDR  MakeID('V','H','D','R')
#define BODY  MakeID('B','O','D','Y')

#define INLIMIT 8               /* 4 Instrument limit (!)          */

struct GfxBase      *OpenLibrary();
struct GfxBase      *GfxBase;
       APTR          AllocMem();
struct FileHandle   *Open();
struct FileHandle   *inshandle;

       ULONG         get8();     /* Process instruments */
       void          kill8svx(); /* Finish up get8()    */

/*---------------*/
/* G L O B A L S */
/*---------------*/
      LONG         data8count,clock;        /* Counter and clock constant */
      UBYTE        *p8data;                 /* Pointers to 8SVX data      */
      Voice8Header *pVoice8Header;
      Chunk        *p8Chunk;
      LONG          fsize,instrumentno,scount;
      UBYTE        *sbase,*sbases[INLIMIT];   /* Pointers to sample block */
      LONG         *ptabptr,                  /* and calculated periods   */
                   *ptabptrs[INLIMIT],        /* for 12 tones.  Table of  */
                    ssize,ssizes[INLIMIT],    /* sample sizes for FreeMem */
                    length[16*INLIMIT];    /* Lengths and pointers to the */
      BYTE         *psample[16*INLIMIT];   /* attack and main parts of 8  */
                                           /* octaves for each instrument */

                  /* Table used to calculate periods for 12 tones.        */
LONG maxfreq[] =  { 41860, 44540, 46986, 49780, 52740, 55860,
                    59200, 62720, 66448, 70400, 74586, 79022 }; /* C to B */
/*-----------*/
/*  G E T 8  */
/*-----------*/
ULONG
get8(fname)
char *fname;

{
/*-------------*/
/* L O C A L S */
/*-------------*/
char              iobuffer[8];
LONG              rd8count,ifreq,freq,
                  x,y,midin,ocyc,both,hic;
UBYTE             *tempptr;

/*-------------*/
/*   C O D E   */
/*-------------*/

/*-----------------*/
/* Check Arguments */
/*-----------------*/
if  (instrumentno>=INLIMIT) return(0L);

if(fname==NULL) {puts("No file name given.\n");
                 return(1L);}
/*-----------------------------------*/
/* Initialize and Set Clock Constant */
/*-----------------------------------*/
if(instrumentno==0L)
  {
   for(x=0;x<INLIMIT;x++){ptabptrs[x]=0L;sbases[x]=0L;}

   GfxBase=(struct GfxBase *)OpenLibrary("graphics.library",0L);
   if(GfxBase==0L){puts("Can't open graphics library\n");return(0L);}
   if(GfxBase->DisplayFlags & PAL) clock=35468950L;      /* PAL clock */
   else                            clock=35795450L;      /* NTSC clock */
   if(GfxBase)CloseLibrary(GfxBase);
  }

inshandle=0;p8data=0;data8count=0;

/*---------------*/
/* Open the File */
/*---------------*/
inshandle=Open(fname,MODE_OLDFILE);
if(inshandle==0)
  {  kill8svx("Cannot open 8SVX file.\n");  return(1L);  }

/*---------------*/
/* Read the File */
/*---------------*/
rd8count=Read(inshandle,iobuffer,8);
if(rd8count==-1){kill8svx ("Read error, 8SVX file.\n");return(1L);}
if(rd8count<8)  {kill8svx ("Not an IFF 8SVX file, too short\n");return(1L);}

/*-----------------*/
/* Evaluate Header */
/*-----------------*/
p8Chunk=(Chunk *)iobuffer;
if( p8Chunk->ckID != FORM ) {kill8svx("Not an IFF FORM.\n");return(1L);}
if(p8Chunk->ckSize > 65536) {kill8svx("8SVX file too big\n");return(1L);}

/*------------------*/
/* Get the IFF data */
/*------------------*/
p8data=(UBYTE *) AllocMem(fsize=p8Chunk->ckSize , MEMF_PUBLIC|MEMF_CLEAR);
if(p8data==0)    {kill8svx("Memory allocate failed.\n");return(1L);}

rd8count=Read(inshandle,p8data,p8Chunk->ckSize);
if(rd8count==-1)
   {kill8svx ("Error during read.\n");return(1L);}
if(rd8count<p8Chunk->ckSize)
   {kill8svx ("Malformed IFF, too short.\n");return(1L);}

/*-------------------*/
/* Evaluate IFF Type */
/*-------------------*/
if(MakeID( *p8data, *(p8data+1) , *(p8data+2) , *(p8data+3) ) != I8SVX )
  {kill8svx("Sorry, this is not IFF 8SVX.\n");return(1L);}

/*--------------*/
/* Find the IDs */
/*--------------*/

data8count=4;

while( data8count < fsize )
  {
  p8Chunk=(Chunk *)(p8data+data8count);

  switch(p8Chunk->ckID)
    {
    case VHDR:
      pVoice8Header=(Voice8Header *)(p8data+data8count+8L);

      ocyc  = pVoice8Header->samplesPerHiCycle;

      ifreq = pVoice8Header->samplesPerSec / ocyc;

      /*-------------------------*/
      /* Convert given frequency */
      /* to a MIDI note number   */
      /* ifreq = input frequency */
      /* midin = the MIDI note   */
      /* Works only 27.5-12544 Hz*/
      /* MIDI notes 21  - 127    */
      /*-------------------------*/
      y=0;x=1;ifreq*=10;
      while(ifreq > 535*x) {x*=2;y++;}
      freq=275*x;
      freq=(freq*103)/100;
      midin=21+(12*y);
      for(x=0;x<11;x++)
        {
        if(ifreq>freq)
          {
          freq=(freq*106)/100;
          midin++;
          }
        else x=12;
        }
      ifreq=ifreq/10;
 
      ptabptr=(LONG *) AllocMem( 64 , MEMF_PUBLIC|MEMF_CLEAR);
      if(ptabptr==0)  {kill8svx("Memory allocate failed.\n");return(1L);}
      ptabptrs[instrumentno]=ptabptr;
      /*-----------------------------------*/
      /* MIDI octaves go from C to B.      */
      /* Find the octave of the hi sample. */
      /*-----------------------------------*/
      x=120;
      while(midin<x)x=x-12;
      hic=x;
      if (hic> 108) {kill8svx("Sample out of range.");return(1L);}

      /*--------------------------------------------*/
      /* Compute ticks per cycle for each midi note */
      /*--------------------------------------------*/
      x=(108-hic)/12;
      y=1; while (x>0) { y*=2; x--; }
      for(x=0; x<12; x++)  ptabptr[x]= ( clock * y) / maxfreq[x] ;

      /*-------------------------------*/
      /* Factor in # samples per cycle */
      /*-------------------------------*/
      for(x=0; x<12; x++) ptabptr[x]/=ocyc;

      break;


    case BODY:
      /*--------------------------------*/
      /* Allocate chip memory for all   */
      /* octaves and copy samples to it.*/
      /*--------------------------------*/
      sbase=(UBYTE *)AllocMem( ssize=p8Chunk->ckSize ,
                               MEMF_CHIP | MEMF_CLEAR);
      if(sbase==0)   {kill8svx("Memory allocate failed.\n");return(1L);}
      ssizes[instrumentno]=ssize;
      sbases[instrumentno]=sbase;

      tempptr=p8data + data8count + 8L;
      for(x=0;x<ssize;x++) *(sbase+x) = *(tempptr+x);
      /*-------------------------------*/
      /* Now add the pointers to our   */
      /* list of pointers in psample[] */
      /*--------------------------------*/
      if(pVoice8Header->ctOctave > 8)
        {kill8svx("Too many samples.\n");return(1L);}
      y=1;
      for(x=0; x < 8; x++)
        {
        both=pVoice8Header->oneShotHiSamples +
             pVoice8Header->repeatHiSamples;
        both = (both*(y-1)) + (LONG)sbase;
        psample[scount]  =(BYTE *)both;
        psample[scount+1]=(BYTE *)both+(y*pVoice8Header->oneShotHiSamples);
        length[scount  ]=y*pVoice8Header->oneShotHiSamples;
        length[scount+1]=y*pVoice8Header->repeatHiSamples;
        if(length[scount  ]==0) length[scount  ]=length[scount+1];
        if(length[scount+1]==0){length[scount+1]=length[scount  ];
                               psample[scount+1]=psample[scount];}
        scount++;scount++;
        if( (9-x <= hic/12) && (9-x > hic/12-pVoice8Header->ctOctave+1) )
                y*=2;
        }
      break;
    default:
      break;
    }
    /* end switch */

  data8count += 8L + p8Chunk->ckSize;
  if(p8Chunk->ckSize&1L ==1) data8count++;
  }
instrumentno++;
if(inshandle!=0){Close(inshandle);inshandle=0;}
if(p8data !=0)  {FreeMem(p8data,fsize);p8data=0;data8count=0;}
return(0L);

}
/*----------------*/
/* Abort the Read */
/*----------------*/
void
kill8svx(kill8svxstring)
char *kill8svxstring;
{
   UBYTE x;
   puts(kill8svxstring);
   if(inshandle!=0)Close(inshandle);
   if(p8data !=0)FreeMem(p8data,fsize);
   for(x=0;x<INLIMIT;x++)
     {
     if(ptabptrs[x]!=0) FreeMem (ptabptrs[x]  , 64);
     if(sbases[x]  !=0) FreeMem (sbases[x], ssizes[x]);
     }
}



-----------------------------MORE CODE STARTS HERE------------------

/*----------------*/
/*   INCLUDES     */
/*----------------*/
#include "exec/types.h"
#include "exec/memory.h"
#include "devices/timer.h"
#include "devices/audio.h"
#include "libraries/dos.h"
#include "iff/smus.h"

/*----------------*/
/*  SUB ROUTINES  */
/*----------------*/
extern APTR       AllocMem();
struct MsgPort   *CreatePort();
struct Message   *GetMsg();
struct IORequest *CreateExtIO();
struct Task      *FindTask();
BYTE              SetTaskPri();
ULONG             OpenDevice();
ULONG             Wait();

void              notes();        /* Play the SMUS events         */
ULONG             compute();      /* Fill in the attack/main IOBs */
void              setime();       /* Set the time out period      */
ULONG             setaudio();     /* Set up for notes()           */
void              killaudio();    /* Finish up notes()            */
void              userkill();     /* Our own break handling       */
 
/*--------------*/
/*    GLOBALS   */
/*--------------*/
extern LONG      *ptabptr,*ptabptrs[];
extern LONG       instrumentno;
extern LONG       length[];
extern LONG       trakcount;
extern BYTE      *psample[];

struct IOAudio     *AudioIOBptr[20];/* 2 attack parts, 2 main parts and  */
struct Message     *msg;            /* 1 stop part x 4 channels = 20 IOB */
struct MsgPort     *port[4];        /* 4 ports for attack/main AIOBs     */ 
struct MsgPort     *tport[4];       /* 4 ports for timeouts              */
ULONG               device[4];      /* Treat as 4 seperate audio devices */
struct timerequest *treq[8];        /* 2 x 4 timer request IOBs          */
ULONG               timer[4];       /* Treat as 4 seperate timers        */

UBYTE               inreg[4];              /* 4 intrument registers    */
UBYTE               chan1[]  = {  1 };     /* Audio channel allocation */
UBYTE               chan2[]  = {  2 };     /* arrays                   */
UBYTE               chan3[]  = {  4 };
UBYTE               chan4[]  = {  8 };
UBYTE              *chans[] = {chan1,chan2,chan3,chan4};

            /*------------------------------------------------------------*/
            /*  Tick Table for SMUS Players by Dan Baker. Use the lower 7 */
            /*  bits of a note SEvent->data field as an index into this   */
            /*  table. Scale to local hardware requirements.              */
            /*  whole / half / quartr / 8th / 16th / 32nd  / 64th / 128th */
            /*  ------ ------- ------ ------ ------ ------- ------ ------ */
ULONG ttable[]={26880,  13440,  6720,  3360,  1680,   840,   420,   210,
/* dotted */    40320,  20160, 10080,  5040,  2520,  1260,   630,   315,
/* 2/3    */    17920,   8960,  4480,  2240,  1120,   560,   280,   140,
                26880,  13440,  6720,  3360,  1680,   840,   420,   210,
/* 5/6    */    21504,  10752,  5376,  2688,  1344,   672,   336,   168,
                32256,  16128,  8064,  4032,  2016,  1008,   504,   252,
/* 6/7    */    23040,  11520,  5760,  2880,  1440,   720,   360,   180,
                34560,  17280,  8640,  4320,  2160,  1080,   540,   270  };


/*===============================*/
/*             NOTE              */
/*===============================*/
void
notes(trak)
SEvent **trak;
{
/*-------------*/
/* NOTE locals */
/*-------------*/
ULONG        stat,wakebit,waitmask;
ULONG        mbits[8];
UBYTE        x,chn,donecount,odev[4];
BYTE         oldpri;
struct Task *mt;

/*--------------------------------*/
/* Set up the Signals, Initialize */
/*--------------------------------*/
if(trakcount>4)trakcount=4;
for(chn=0;chn<4;chn++)inreg[chn]=chn;
if(instrumentno<4)
  {
  for(chn=instrumentno;chn<4;chn++)inreg[chn]=0;
  }

/* Time out signals for 4 channels */
mbits[0] = (1 << tport[0]->mp_SigBit);
mbits[1] = (1 << tport[1]->mp_SigBit);
mbits[2] = (1 << tport[2]->mp_SigBit);
mbits[3] = (1 << tport[3]->mp_SigBit);

/* Attack-part-done signals        */
mbits[4] = (1 <<  port[0]->mp_SigBit);
mbits[5] = (1 <<  port[1]->mp_SigBit);
mbits[6] = (1 <<  port[2]->mp_SigBit);
mbits[7] = (1 <<  port[3]->mp_SigBit);


waitmask = mbits[0] | mbits [1] | mbits[2] | mbits [3] |
           mbits[4] | mbits [5] | mbits[6] | mbits [7] | SIGBREAKF_CTRL_C;

mt=FindTask(NULL);
oldpri=SetTaskPri(mt,22);              /* Bump our priority a bit. */

/*-----------------------------*/
/* Compute the 1st 8 AudioIOBs */
/*-----------------------------*/
for(chn=0;chn<trakcount;chn++)
  {
  odev[chn]=0;
  stat=compute(trak,chn,odev[chn]);

  /*------------------------------------*/
  /* Initialize 20 AIOBs.  These fields */
  /* only need to be filled once.       */
  /*------------------------------------*/
  AudioIOBptr[chn]->ioa_Request.io_Command                =CMD_WRITE;
  AudioIOBptr[chn]->ioa_Request.io_Flags                  =ADIOF_PERVOL;
                                                 /* Main part, 1st IO  */
  *AudioIOBptr[chn+4]=*AudioIOBptr[chn];         /* Main part, 2nd IO  */
  *AudioIOBptr[chn+8]=*AudioIOBptr[chn];         /* Attack part, 1st IO */
  *AudioIOBptr[chn+12]=*AudioIOBptr[chn];        /* Attack part, 2nd IO */

  *AudioIOBptr[chn+16]=*AudioIOBptr[chn];
  AudioIOBptr[chn+16]->ioa_Request.io_Command              =ADCMD_FINISH;
  AudioIOBptr[chn+16]->ioa_Request.io_Flags                =IOF_QUICK;
  }

/*-------------------------*/
/*  Compute second 8 AIOBs */
/*-------------------------*/
for(chn=0;chn<trakcount;chn++)
  {
  odev[chn]=4;
  stat=compute(trak,chn,odev[chn]);
  }
/*---------------------------*/
/* Start the 1st 8 AudioIOBs */
/*---------------------------*/
for(chn=0;chn<trakcount;chn++)
  {
  BeginIO(AudioIOBptr[chn+8]);
  BeginIO(AudioIOBptr[chn]);
  }
for(chn=0;chn<trakcount;chn++)  SendIO(treq[chn]);

/*-------------------------------------------*/
/* Main Loop: while there is more trak data, */
/* Wait() for a main note to finish, then    */
/* Begin() the next one.  Compute() successor*/
/* Always has 2 notes, 4 AudioIOBs ready.    */
/*-------------------------------------------*/
donecount=0;
while(donecount<trakcount)
  {
  wakebit=0L;
  while(wakebit==0L)
    {
     /*---------------------------------------*/
     /* Wake up from attack part. Just start  */
     /* the main part playing (by GetMsg) Its */
     /* already in the queue waiting to begin */
     /*---------------------------------------*/
     wakebit=Wait(waitmask);
     for(x=0;x<trakcount;x++)
       {
       if(wakebit&mbits[x+4])
            {
            msg=GetMsg(port[x]);
            wakebit&=~mbits[x+4];
            }
       }
    }

  /*-------------------------------------------------------*/
  /* Wake up from a main part time-out.  Now kill both the */
  /* attack and main part of the current note.  Start the  */
  /* attack and main of the next note, and the timer.      */
  /*-------------------------------------------------------*/
  for(x=0;x<trakcount;x++)
    {
    if(wakebit&mbits[x]){chn=x+odev[x];
                         BeginIO(AudioIOBptr[16+x]);
                         BeginIO(AudioIOBptr[16+x]);
                         BeginIO(AudioIOBptr[chn+8]);
                         BeginIO(AudioIOBptr[chn]);
                         SendIO(treq[chn]);}
    }

  /*-----------------------------------------------------------*/
  /* Dispose of messages and compute() the successor notes for */
  /* any that finished. As signals bits are processed, they    */
  /* are removed from the wakeup mask (up to 4 bits to handle).*/
  /*-----------------------------------------------------------*/
  while(wakebit!=0)
    {
    if     (wakebit&mbits[0]) {chn=0;wakebit&=~mbits[0];}
    else if(wakebit&mbits[1]) {chn=1;wakebit&=~mbits[1];}
    else if(wakebit&mbits[2]) {chn=2;wakebit&=~mbits[2];}
    else if(wakebit&mbits[3]) {chn=3;wakebit&=~mbits[3];}

    else if(wakebit&SIGBREAKF_CTRL_C) {userkill();return(0);}
    else   {puts("Wake up bits unknown!\n");userkill();return(0);}

    msg=GetMsg(tport[chn]);
    msg=GetMsg(port[chn]);

    if  (odev[chn]==4)odev[chn]=0;     /* odev[] determines which of */
    else              odev[chn]=4;     /* the 2 AudioIOBs to re-use. */

    stat=compute(trak,chn,odev[chn]);
    if(stat==1) donecount++;
    /* Done with this wake up bit */

    }
  /* All wake up bits done*/

  }
/* All traks done */

/*--------------------------*/
/* Finish the last 4 notes. */
/*--------------------------*/

while( donecount < (2*trakcount) )
  {
  wakebit=Wait(waitmask);
  for(x=0;x<trakcount;x++)
    {
    if(wakebit&mbits[x+4]) msg=GetMsg(port[x]);
    }

  for(x=0;x<trakcount;x++)
    {
    if(wakebit&mbits[x]){ BeginIO(AudioIOBptr[16+x]);
                          BeginIO(AudioIOBptr[16+x]);
                          donecount++;}
    }
  }
for(chn=0;chn<trakcount;chn++){ msg=GetMsg( tport[chn]);
                                msg=GetMsg( port[chn]); }
}



/*----------------------------------*/
/* Compute: calculates SMUS note    */
/* and duration values and fills in */
/* the attack and main AudioIOBs.   */
/*----------------------------------*/
ULONG
compute(track,ch,odev)

SEvent          **track;                /* Track pointer          */
UBYTE             ch;                   /* Channel to use (0-4)   */
UBYTE             odev;                 /* AIOB set to use 0 or 4 */

{
SEvent            *cevent;               /* Local SEvent          */
UBYTE              psamin;               /* Index into psample    */
UBYTE              aiob;                 /* Local AIOB index      */
UBYTE              note;                 /* MIDI note number      */
UBYTE              dur;                  /* duration in secs      */
struct IOAudio     *AAptr;               /* Local attack AudioIOB */
struct IOAudio     *Aptr;                /* Local main AudioIOB   */
struct timerequest *tr;                  /* Local time request    */
ULONG              micro,sec;            /* Timer variables       */   

/*-------------------*/
/* Initialize locals */
/*-------------------*/
aiob  =ch+odev;
AAptr =AudioIOBptr[aiob+8];       /* 8-15 */
Aptr  =AudioIOBptr[aiob];         /* 0-7  */
tr    =treq[aiob];                /* 0-7  */
cevent=track[ch];

while(cevent->sID >128 || cevent->data >127)
           {
           /* Handle instrument change */
           if(cevent->sID==129 & cevent->data<instrumentno)
               inreg[ch]=cevent->data;
           /* Ignore other non-note events, 1-trak chords */
           track[ch]++; cevent++;
           /* If we're out of notes, drop out of loop with a rest */
           if(track[ch]>=track[ch+4]) {note=128;goto DONE;}
           }
note  =cevent->sID;

/*------------------------------*/
/* Fill in the Audio IOB fields */
/*------------------------------*/
DONE:
       dur   =cevent->data;
           
      /*--------*/
      /* Volume */
      /*--------*/
      Aptr->ioa_Volume=60; dur&=63;
      if(note==128){Aptr->ioa_Volume=0;note=90;}
      AAptr->ioa_Volume=Aptr->ioa_Volume;

      /*-------------*/
      /* Data/Length */
      /*-------------*/
      psamin=9;while(note>11){note-=12;psamin--;}
      psamin=(psamin<<1) + (inreg[ch]<<4) ;

      Aptr->ioa_Data                    =(UBYTE *)psample[psamin+1];
      Aptr->ioa_Length                  =(length [psamin+1]);
      AAptr->ioa_Data                   =(UBYTE *)psample[psamin];
      AAptr->ioa_Length                 =(length [psamin]);

      /*--------*/
      /* Period */
      /*--------*/
      ptabptr          =ptabptrs[inreg[ch]];
      Aptr->ioa_Period =ptabptr[note];
      AAptr->ioa_Period=ptabptr[note];

      /*------------*/
      /* Set Timer  */ 
      /*------------*/ 
      sec=0;
      micro=ttable[dur];
      while(micro>1000000){sec++;micro-=1000000;}
      tr->tr_node.io_Command=TR_ADDREQUEST;
      tr->tr_time.tv_secs=sec;
      tr->tr_time.tv_micro=micro;

      /*--------*/
      /* Cycles */
      /*--------*/
      Aptr->ioa_Cycles =0;   /* Main part plays forever  (until timeout) */
      AAptr->ioa_Cycles=1;   /* Attack part plays only once              */

      track[ch]++;
      if(track[ch] > track[ch+4]) return(1L);
      else                         return(0L);
}


/*========================*/
/*      KILL  AUDIO       */
/*========================*/
void
killaudio()
{
UBYTE c;
for(c=0;c<4;c++)
  {
    if(device[c]==0)     CloseDevice(AudioIOBptr[c]);

    if(timer[c] ==0)     CloseDevice(treq[c]);

    if( port[c]  !=0)    DeletePort(port[c]);
    if(tport[c]  !=0)    DeletePort(tport[c]);

    if(treq[c  ]!=0)     DeleteExtIO(treq[c  ]);
    if(treq[c+4]!=0)     DeleteExtIO(treq[c+4]);

    if(AudioIOBptr[c  ]!=0)
       FreeMem( AudioIOBptr[c  ],sizeof(struct IOAudio) );
    if(AudioIOBptr[c+4]!=0)
       FreeMem( AudioIOBptr[c+4],sizeof(struct IOAudio) );
    if(AudioIOBptr[c+8]!=0)
       FreeMem( AudioIOBptr[c+8],sizeof(struct IOAudio) );
    if(AudioIOBptr[c+12]!=0)
       FreeMem( AudioIOBptr[c+12],sizeof(struct IOAudio) );
    if(AudioIOBptr[c+16]!=0)
       FreeMem( AudioIOBptr[c+16],sizeof(struct IOAudio) );
  }
}

/*=========================*/
/*     SET  UP  AUDIO      */
/*=========================*/
ULONG setaudio()
{
UBYTE c;
for(c=0;c<20;c++)AudioIOBptr[c]=0;
for(c=0;c<4;c++){treq[c]=0;treq[c+4]=0;
                 port[c]=0;tport[c]=0;
                 timer[c]=1;device[c]=1;}
for(c=0;c<20;c++)
  {
  /*------------------------------*/
  /* Allocate audio I/O blocks    */
  /*------------------------------*/
  AudioIOBptr[c]=(struct IOAudio *)
         AllocMem( sizeof(struct IOAudio),MEMF_CHIP|MEMF_PUBLIC|MEMF_CLEAR);
  if(AudioIOBptr[c]==0) {return(1L);}
  }

for(c=0;c<4;c++)
  {
  port[c]=CreatePort(0,0);
  if(port[c]==0) {return(1L);}

  /*-----------------------------------------------*/
  /* Set up audio I/O block for channel allocation */
  /*-----------------------------------------------*/
  AudioIOBptr[c]->ioa_Request.io_Message.mn_ReplyPort   = port[c];
  AudioIOBptr[c]->ioa_Request.io_Message.mn_Node.ln_Pri = 21;
  AudioIOBptr[c]->ioa_AllocKey                          = 0;
  AudioIOBptr[c]->ioa_Data                              = chans[c];
  AudioIOBptr[c]->ioa_Length                            = 1;

  /*------------------------------------------------------------*/
  /* Open the audio device and allocate once for each channel   */
  /*------------------------------------------------------------*/
  device[c]=OpenDevice("audio.device",0,AudioIOBptr[c],0);
  if(device[c]!=0) {return(1L);}
  }

for(c=0;c<4;c++)
  {
  tport[c]=CreatePort(0,0);
  if(tport[c]==0) {return(1L);}

  treq[c]=(struct timerequest *) CreateExtIO( tport[c] ,
                                   sizeof(struct timerequest) );
  if(treq[c]==0)  {return(1L);}

  treq[c+4]=(struct timerequest *) CreateExtIO( tport[c] ,
                                   sizeof(struct timerequest) );
  if(treq[c+4]==0)  {return(1L);}

  /*---------------------------*/
  /*  Open the timer device    */
  /*---------------------------*/
  timer[c]=OpenDevice(TIMERNAME , UNIT_MICROHZ , treq[c] , 0);
  if(timer[c]!=0) {return(1L);}

  *treq[c+4]=*treq[c];
  }
return(0L);
}

/*=========================*/
/* Userkill Break Handling */
/*=========================*/
void
userkill()
{
UBYTE x;
/* Kill 4 notes on 4 channels, 4 timer requests */
for(x=16;x<16+trakcount;x++)BeginIO(AudioIOBptr[x]);
for(x=16;x<16+trakcount;x++)BeginIO(AudioIOBptr[x]);
for(x=16;x<16+trakcount;x++)BeginIO(AudioIOBptr[x]);
for(x=16;x<16+trakcount;x++)BeginIO(AudioIOBptr[x]);

for(x=0;x<trakcount  ;x++)AbortIO(treq[x]);
for(x=4;x<4+trakcount;x++)AbortIO(treq[x]);

for(x=0;x<trakcount;x++){msg=GetMsg(tport[x]);
                         msg=GetMsg( port[x]);}
}
