(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.



                   8SVX: Playing Samples Larger Than 128K

                               by Dan Baker


The Amiga's audio hardware contains four digital-to-analog converters 
(DACs) capable of playing back digital sound samples with 8-bit resolution.  
On the Amiga sound samples are usually stored in the 8SVX format which is 
the IFF file standard for 8-bit samples.  The 8SVX standard allows samples 
up to 2 gigabytes long.  However, the length registers of the DACs only 
allow samples up to 128K bytes.  In order to play back a sample larger than 
128K, you break it up into smaller pieces and send multiple requests to the 
audio device.  The program listed below shows you how to use this technique.

The register map in Appendix B of the Hardware Manual shows that all the 
audio length registers are 16 bits which gives a maximum length of 65,536.
However, since the audio hardware can DMA two bytes at a time, the length 
register is set up to represent the word count of the sample, not the 
byte count.  Hence, the maximum length for a sample is 2 times 65,536 or 
128K bytes.

Audio sample data in the 8SVX format is stored as a standard IFF Chunk
of type BODY.  The Chunk size is given by the field ckSize which is a
LONG variable.  So the maximum sample size in an IFF Chunk is just over 
2 gigabytes.  Even larger samples might be possible by using a CAT
or LIST.  See the IFF manual for more details.

The original Amiga had 512K of chip RAM, much of which would be busy doing
graphics work, so the 128K audio sample size limit was a good design decision.
Because of memory limits, most early Amiga samples were short sound
effects less than 128K.

But the newest Amigas have 1 megabyte of chip RAM and sometimes additional
expansion RAM.  This trend will probably continue as memory prices fall.
So the idea of supporting 2 gigabyte samples under IFF is not far-fetched.

The program listed below shows you how to play back 8SVX samples larger 
than 128K on the Amiga by breaking up the sample into smaller pieces.  
The program divides large samples into sections of 51200 bytes. 
The samples are sent to the audio device using double-buffering.  That is,
there are always two IO requests in the queue.  A Wait(), GetMsg() loop puts 
the progam to sleep until the current audio request finishes.  

When the program wakes up, the next request in the queue begins to play 
immediately.  While it plays, new sample data is copied into chip memory.  
The request that just ended is then reused by placing it back in the queue 
and the program goes through the Wait(), GetMsg() loop again.  Note that
there is nothing special about using 51200-byte samples.  Any size up to 
128K would work but 51200 is used to conserve chip memory. 

The IFF parsing used by the program is very simple.  The FORM is first
inspected for the file size and the file is read into memory.  A switch 
statement is used to cull the VHDR and BODY Chunks from the file and 
pointers are set up.  Other Chunks are skipped.  CATs, LISTs and nested 
FORMs are not supported.  If the BODY Chunk has both a one-shot and 
continuous part, only the one-shot part is used.  Similarly, only
the first ocatve part is played in a multi-octave sample.

The audio device is handled in the usual way.  Two AudioIO requests and a 
reply port are set up.  Only one audio channel is used.  The channel is 
allocated automatically when the device is opened.  The allocation key is 
set to accept any of the four channels.  The request priority is set to 128 
so that once we have the channel, it cannot be stolen by another task during 
playback.

An important part of the program is the setting of the clock constant to 
the right value for both PAL and NTSC systems.  The clock constant, which is 
used in calculating the period of an audio request, is different on PAL and 
NTSC systems.  To get the right value look at the DisplayFlags field of 
GfxBase in the graphics library.

By taking adavantage of the audio device's ability to queue up multiple 
IO requests, it is possible to extend the effective sample size limit of 
the Amiga beyond the 128K barrier.  The Amiga's DMA driven audio hardware
can smoothly play back samples of any arbitrary size.


/*----------------*/
/*   INCLUDES     */
/*----------------*/
#include "exec/types.h"
#include "exec/memory.h"
#include "devices/audio.h"
#include "libraries/dos.h"
#include "libraries/dosextens.h"
#include "graphics/gfxbase.h"
#include "iff/iff.h"
#include "iff/8svx.h"

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

       APTR        OpenLibrary();
struct FileHandle *Open();
       ULONG       Read();
       APTR        AllocMem();
struct GfxBase    *GfxBase;
struct MsgPort    *CreatePort();
struct Message    *GetMsg();
struct Task       *FindTask();
       BYTE        SetTaskPri();
       ULONG       OpenDevice();
       ULONG       Wait();

void              kill8svx();
void              kill8(); 

/*--------------------*/
/*   G L O B A L S    */
/*--------------------*/
struct IOAudio     *AudioIOBptr1,      /* Pointers to Audio IOBs      */
                   *AudioIOBptr2,
                   *Aptr;
struct Message     *msg;               /* Msg, port and device for    */
struct MsgPort     *port;              /* driving audio               */
       ULONG        device;
       UBYTE       *sbase,*fbase;      /* For sample memory allocation */
       ULONG        fsize,ssize;       /* and freeing                  */
struct FileHandle  *v8handle;
       UBYTE        chan1[]  = {  1 }; /* Audio channel allocation arrays */
       UBYTE        chan2[]  = {  2 };
       UBYTE        chan3[]  = {  4 };
       UBYTE        chan4[]  = {  8 };
       UBYTE       *chans[] = {chan1,chan2,chan3,chan4};

/*-----------*/
/*  M A I N  */
/*-----------*/
LONG
main(argc,argv)
LONG argc;
char *argv[];
{
/*-------------*/
/* L O C A L S */
/*-------------*/
       char         *fname;               /* File name and data pointer*/
       UBYTE        *p8data;              /* for file read.            */
       ULONG         clock;               /* Clock constant            */
       ULONG         length[2];           /* Sample lengths            */
       BYTE          iobuffer[8],         /* Buffer for 8SVX header    */
                    *psample[2];          /* Sample pointers           */
       Chunk        *p8Chunk;             /* Pointers for 8SVX parsing */
       Voice8Header *pVoice8Header;
       ULONG         x,y,rd8count,speed;  /* Counters, sampling speed   */
       ULONG         wakebit;             /* A wakeup mask              */
       BYTE          oldpri,c;            /* Stuff for bumping priority */
       struct Task  *mt;


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

/*------------------------------*/
/* Check Arguments, Initialize  */
/*------------------------------*/
fbase=0L;sbase=0L;
AudioIOBptr1=0L;AudioIOBptr2=0L;
port=0L;v8handle=0L;device=1L;


if(argv[1]==NULL) {kill8svx("No file name given.\n");return(1L);}
fname=argv[1];

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


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

/*-------------------------------------------*/
/* Read the 1st 8 Bytes of the File for Size */
/*-------------------------------------------*/
rd8count=Read(v8handle,iobuffer,8);
if(rd8count==-1){kill8svx ("Read error.\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);}

/*--------------------------*/
/* Allocate Memory for File */
/*  and Read it in.         */
/*--------------------------*/
fbase= (UBYTE *)AllocMem(fsize=p8Chunk->ckSize , MEMF_PUBLIC|MEMF_CLEAR);
if(fbase==0)    {kill8svx("No memory for read.\n");return(1L);}
p8data=fbase;

rd8count=Read(v8handle,p8data,p8Chunk->ckSize);
if(rd8count==-1)
   {kill8svx ("Read error.\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) ) != MY8S )
  {kill8svx("Not an IFF 8SVX file.\n");return(1L);}

/*----------------------*/
/* Evaluate 8SVX Chunks */
/*----------------------*/

p8data=p8data+4;

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

  switch(p8Chunk->ckID)
    {
    case VHDR:
      /*------------------------------------------------*/
      /* Get a pointer to the 8SVX header for later use */
      /*------------------------------------------------*/
      pVoice8Header=(Voice8Header *)(p8data+8L);
      break;
    case BODY:

      /*-------------------------------------------------*/
      /* Create pointers to 1-shot and continuous parts  */
      /* for the top octave and get length. Store them.  */
      /*-------------------------------------------------*/
        psample[0] = (BYTE *)(p8data + 8L);
        psample[1] = psample[0] + pVoice8Header->oneShotHiSamples;
        length[0] = (ULONG)pVoice8Header->oneShotHiSamples;
        length[1] = (ULONG)pVoice8Header->repeatHiSamples;
        break;

    default:
      break;
    }

    /* end switch */

  p8data   =p8data    + 8L + p8Chunk->ckSize;

  if(p8Chunk->ckSize&1L ==1) p8data++;
  }

/* Play either the one-shot or continuous, not both */
if  (length[0]==0)   y=1;
else                 y=0;

/*---------------------------------------*/
/* Allocate chip memory for samples and  */
/* copy from read buffer to chip memory. */
/*---------------------------------------*/
if(length[y]<=102400)ssize=length[y];
else                 ssize=102400;

sbase=(UBYTE *)AllocMem( ssize , MEMF_CHIP | MEMF_CLEAR);
if(sbase==0)   {kill8svx("No chip memory.\n");return(1L);}
CopyMem(psample[y],sbase,ssize);
psample[y]+=ssize;

/*----------------------------------*/
/* Calculate playback sampling rate */
/*----------------------------------*/
speed =  clock / pVoice8Header->samplesPerSec;

/*-------------------*/
/* Bump our priority */
/*-------------------*/
mt=FindTask(NULL);
oldpri=SetTaskPri(mt,21);

/*---------------------------------------------*/
/* Allocate audio I/O blocks and make a port   */
/*---------------------------------------------*/
AudioIOBptr1=(struct IOAudio *)
      AllocMem( sizeof(struct IOAudio),MEMF_CHIP|MEMF_PUBLIC|MEMF_CLEAR);
if(AudioIOBptr1==0) {kill8svx("No IO memory\n");return(1L);}

AudioIOBptr2=(struct IOAudio *)
      AllocMem( sizeof(struct IOAudio),MEMF_CHIP|MEMF_PUBLIC|MEMF_CLEAR);
if(AudioIOBptr2==0) {kill8svx("No IO memory\n");return(1L);}

port=CreatePort(0,0);
if(port==0){kill8svx("No port\n"); return(1L);}
c=0;

while(device!=0)
  {
  /*---------------------------------------*/
  /* Set up audio I/O block for channel    */
  /* allocation and Open the audio device  */
  /*---------------------------------------*/
  AudioIOBptr1->ioa_Request.io_Message.mn_ReplyPort   = port;
  AudioIOBptr1->ioa_Request.io_Message.mn_Node.ln_Pri = 128;
  AudioIOBptr1->ioa_AllocKey                          = 0;
  AudioIOBptr1->ioa_Data                              = chans[c];
  AudioIOBptr1->ioa_Length                            = 1;

  device=OpenDevice("audio.device",0,AudioIOBptr1,0);
  c++;
  }
if(device!=0){kill8svx("No channel\n"); return(1L);}

/*-------------------------------------------*/
/* Set Up Audio IO Blocks for Sample Playing */
/*-------------------------------------------*/
AudioIOBptr1->ioa_Request.io_Command                =CMD_WRITE;
AudioIOBptr1->ioa_Request.io_Flags                  =ADIOF_PERVOL;

/*--------*/
/* Volume */
/*--------*/
AudioIOBptr1->ioa_Volume=60;
/*---------------*/
/* Period/Cycles */
/*---------------*/
AudioIOBptr1->ioa_Period =(UWORD)speed;
AudioIOBptr1->ioa_Cycles =1;
*AudioIOBptr2 = *AudioIOBptr1;
/*--------*/
/*  Data  */
/*--------*/
AudioIOBptr1->ioa_Data            =(UBYTE *)sbase;
AudioIOBptr2->ioa_Data            =(UBYTE *)sbase + 51200;



Aptr=AudioIOBptr2;
/*-----------------*/
/*  Run the sample */
/*-----------------*/
if(length[y]<=102400)
  {
  AudioIOBptr1->ioa_Length=length[y];    /* No double buffering needed */
  BeginIO(AudioIOBptr1);                 /* Begin the sample, wait for */
  wakebit=0L;                            /* it to finish, then quit.   */
  wakebit=Wait(1 << port->mp_SigBit);
  msg=GetMsg(port);
  }
else
  {
  length[y]-=102400;                    /* It's a real long sample so  */
  AudioIOBptr1->ioa_Length=51200L;      /* double buffering is needed  */
  AudioIOBptr2->ioa_Length=51200L;
  BeginIO(AudioIOBptr1);                /* Queue up two samples and Wait */
  BeginIO(AudioIOBptr2);                /* for the first to finish.      */
  while(length[y]>0)                    /* Reuse the Audio IOB, queue it */
    {                                   /* up again and wait for the 2nd */
    wakebit=Wait(1 << port->mp_SigBit); /* Audio IOB to finish.  Reuse   */
    msg=GetMsg(port);                   /* the 2nd, queue it up, repeat. */

    if(Aptr==AudioIOBptr1)Aptr=AudioIOBptr2;
    else                  Aptr=AudioIOBptr1;
  
    if(length[y]<=51200)  Aptr->ioa_Length=length[y];
    else                  Aptr->ioa_Length=51200L;

    CopyMem(psample[y],Aptr->ioa_Data,Aptr->ioa_Length);

    length[y]-=Aptr->ioa_Length;
    psample[y]+=51200;
    BeginIO(Aptr);
    }
  wakebit=Wait(1 << port->mp_SigBit);
  msg=GetMsg(port);
  wakebit=Wait(1 << port->mp_SigBit);
  msg=GetMsg(port);
  }
kill8();
return(0L);

}


/*----------------*/
/* Abort the Read */
/*----------------*/
void
kill8svx(kill8svxstring)
char *kill8svxstring;
{
   puts(kill8svxstring);
   kill8();
}
/*-------------------------*/
/* Return system resources */
/*-------------------------*/
void
kill8()
{
   if(v8handle!=0)Close(v8handle);
   if(fbase !=0)  FreeMem(fbase,fsize);
   if(sbase !=0)  FreeMem (sbase, ssize);

   if(device ==0)      CloseDevice(AudioIOBptr1);
   if( port  !=0)      DeletePort(port);
   if(AudioIOBptr1!=0) FreeMem( AudioIOBptr1,sizeof(struct IOAudio) );
   if(AudioIOBptr2!=0) FreeMem( AudioIOBptr2,sizeof(struct IOAudio) );
}
