;/* interplay.c -- execute me to compile me sc DATA=NEAR NMINC STRMERGE NOSTKCHK IGNORE=73 interplay.c slink FROM LIB:c.o + interplay.o TO interplay LIBRARY lib:sc.lib+lib:amiga.lib quit */ /* (c) Copyright 1993-1999 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. */ /* Interlay.c - Run from Shell (CLI) only. Given two file names of IFF ** 8SVX 8-bit sampled audio data, plays the data from both files using just ** one channel. This demonstrates how virtual audio channels can be ** implemented. ** ** The program supports two different methods for virtual voices. Method 1 ** (the default method) interleaves bytes from each file so that the data words ** fed into the Amiga's audio hardware contain one byte each from the given ** files. The samples are then played back at twice their normal speed. Since ** each sample only gets half of the playback bandwidth, the speed sounds ** correct. To the listener, it sounds as if both samples are playing ** simultaneously even though only one channel is used. ** ** Normally the maximum playback rate with the Amiga's audio hardware is about ** 28K bytes/sec. Since interleaving requires doubling the nominal sampling ** rate, it will only work with audio data created at a sampling rate of 14K ** bytes/sec or less. ** ** Method 2, takes one byte from each file, sums them and divides by two. ** The resulting byte value is sent to the Amiga's audio hardware. No speed ** increase is required for this technique, however some noise is introduced ** by the averaging of the byte values. To use method 2, inlcude the SUM ** keyword as the last argument typed on the command line. Examples: ** ** interplay talk.8svx music.8svx SUM (Uses method 2, averaging) ** interplay talk.8svx music.8svx (Uses method 1, interleaving) ** interplay talk.8svx (Normal single file 8SVX playback) ** ** For an example of conventional IFF 8SVX audio see the "Amiga ROM Kernel ** Reference Manual: Devices", 3rd edition (ISBN 0-201-56775-X), page 28 and ** page 515. */ #include <exec/types.h> #include <exec/devices.h> #include <exec/memory.h> #include <devices/audio.h> #include <dos/dos.h> #include <iff/iff.h> #include <iff/8svx.h> #include <clib/exec_protos.h> #include <clib/dos_protos.h> #include <clib/alib_protos.h> #include <stdio.h> #include <string.h> #include <dos.h> /* This is the dos.h file from SAS/C not Commodore */ #ifdef SASC int CXBRK(VOID) { return(0); }; int chkabort(VOID) { return(0); }; #endif #define BUF_SIZE 1024 /* Prototypes for functions defined in this program */ struct IOAudio *SiezeChannel( VOID ); VOID ReleaseChannel( struct IOAudio * ); char *Parse8svx(char *, struct InterPlay * ); VOID EndParse( struct InterPlay * ); VOID FillAudio(struct InterPlay *, struct IOAudio * ); struct InterPlay /* This is the main structure used for */ { /* storage of playback state info. */ ULONG sample_done; /* 0=Keep playing, 1=all done playing. */ UBYTE *sample_byte; /* Pointer for going through the data. */ UBYTE *sample_loc; /* Start of 8SVX BODY data in memory. */ ULONG sample_size; /* and total size of file for freeing.*/ struct InterPlay *next_iplay; /* Link to second data set. NULL means */ /* no second file name was given. */ LONG offsetBody; /* Offset into the file of BODY Chunk. */ UWORD sample_speed; /* Value for audio period register. */ BOOL USE_SUMMING; /* TRUE means use averaging, */ }; /* FALSE means use interleaving. */ /* Version string for AmigaDOS VERSION command. */ UBYTE versiontag[] = "$VER: Interplay 1.0 (2.2.93)"; /*----------------------------------------- ** ** main() ** **----------------------------------------- */ VOID main(int argc, char **argv) { struct InterPlay mainplay,otherplay; /* Two instances of the InterPlay */ /* structure, one for each file. */ struct IOAudio *pIOA_1=NULL, *pIOA_2=NULL, /* Two IOAudio pointers, plus one */ *pIOA =NULL; /* for switching back and forth */ struct MsgPort *mport1=NULL, /* during double-buffering. */ *mport2=NULL, /* Two MsgPort pointers, plus one */ *mport =NULL; /* for switching back and forth. */ struct Message *msg; /* For the GetMsg() call. */ LONG aswitch = 0L; /* Double-buffering logical switch. */ static BYTE chip playbuffer1[BUF_SIZE]; /* Two buffers, one for each IOAudio */ static BYTE chip playbuffer2[BUF_SIZE]; /* request. Play out of one while */ /* the other is being set up. */ char *errormsg; /* For error returns */ ULONG wakemask=0L; /* For Wait() call */ /* Give an AmigaDOS style help message */ if( (argc == 2) && !strcmp(argv[1],"?\0") ) printf("8SVX-FILES/M,SUM/S\n"); else if(argc>=2) /* OK got at least one argument. */ { /* Get an audio channel at the highest priority */ if( pIOA_1=SiezeChannel() ) { mport1 = pIOA_1->ioa_Request.io_Message.mn_ReplyPort; pIOA_1->ioa_Data = playbuffer1; /* Get a 2nd MsgPort and 2nd IOAudio structure for double-buffering */ pIOA_2 = AllocMem(sizeof(struct IOAudio),MEMF_PUBLIC | MEMF_CLEAR ); mport2 = CreatePort(0,0); if( pIOA_2 && mport2 ) { /* The 2 IOAudio requests should be initialized the same */ /* except for the buffer and the reply port they use. */ *pIOA_2 = *pIOA_1; pIOA_2->ioa_Request.io_Message.mn_ReplyPort = mport2; pIOA_2->ioa_Data = playbuffer2; /* Default is to use interleaving, not averaging */ mainplay.USE_SUMMING = FALSE; /* Parse the 8SVX file and fill in the InterPlay structure */ errormsg = Parse8svx( argv[1] , &mainplay ); /* If a second file name was given by the user then this is */ /* an interleave request, so parse the 2nd 8SVX file. */ if( argc>=3 && !errormsg ) { errormsg = Parse8svx( argv[2] , &otherplay ); mainplay.next_iplay = &otherplay; /* If the SUM keyword was given in the command line, set the */ /* SUMMING flag so that averaging, not interleaving, is used.*/ if( (argc == 4) && ( !strcmp(argv[3],"SUM\0") || !strcmp(argv[3],"sum\0") ) ) mainplay.USE_SUMMING = TRUE; } else otherplay.sample_done = 1; if(!errormsg) /* File names given parsed OK? */ { /* Fill up the buffer for the first request. */ FillAudio( &mainplay, pIOA_1); /* Is there enough data to double-buffer ? */ if(!mainplay.sample_done || !otherplay.sample_done) { /* OK, enough data to double-buffer; fill up 2nd request */ FillAudio( &mainplay, pIOA_2 ); BeginIO((struct IORequest *) pIOA_1 ); BeginIO((struct IORequest *) pIOA_2 ); /* Initial state of double-buffering variables */ aswitch=0; pIOA=pIOA_2; mport=mport1; /*---------------------*/ /* M A I N L O O P */ /*---------------------*/ while(!mainplay.sample_done || !otherplay.sample_done) { wakemask=Wait( (1 << mport->mp_SigBit) | SIGBREAKF_CTRL_C ); if( wakemask & SIGBREAKF_CTRL_C ) { otherplay.sample_done = 1; mainplay.sample_done = 1; } while((msg=GetMsg(mport))==NULL){} /* Toggle double-buffering variables */ if (aswitch) {aswitch=0;pIOA=pIOA_2;mport=mport1;} else {aswitch=1;pIOA=pIOA_1;mport=mport2;} FillAudio( &mainplay, pIOA ); BeginIO((struct IORequest *) pIOA ); } wakemask=Wait( 1 << mport->mp_SigBit ); while((msg=GetMsg(mport))==NULL){} if (aswitch) {aswitch=0;pIOA=pIOA_2;mport=mport1;} else {aswitch=1;pIOA=pIOA_1;mport=mport2;} wakemask=Wait( 1 << mport->mp_SigBit ); while((msg=GetMsg(mport))==NULL){} } else { /* Only enough data to fill up one buffer */ BeginIO((struct IORequest *) pIOA_1 ); wakemask=Wait( 1 << mport1->mp_SigBit ); while((msg=GetMsg(mport1))==NULL){} } } else /* One or the other of the files had a problem in Parse8svx() */ printf(errormsg); /* Free the memory used for the 8SVX files in Parse8svx() */ if(mainplay.next_iplay) EndParse( &otherplay ); EndParse( &mainplay ); } else printf("Couldn't get memory for a second IOAudio and MsgPort\n"); /* Free the ports and memory used by the 2 IOAudio requests */ if(mport2) DeletePort(mport2); if(pIOA_2) FreeMem( pIOA_2, sizeof(struct IOAudio) ); ReleaseChannel(pIOA_1); } else printf("Couldn't get a channel on the audio device\n"); } else printf("Enter one or two 8SVX filenames.\n"); } /*---------------------------------------------------------------------- ** struct IOAudio *res = SiezeChannel( VOID ) ** ** Allocates any channel at the highest priority. Once allocated, ** the hardware registers of the given channel can be hit directly ** without interfering with normal audio.device operation. ** ** Retruns NULL on failure ** or returns the address of the IOAudio used to get the channel. ** If the call to this function succeeds, ReleaseChannel() should ** be called later to free the channel and memory used for the IOAudio. **----------------------------------------------------------------------- */ struct IOAudio * SiezeChannel( VOID ) { struct IOAudio *myAIOreq=NULL; struct MsgPort *myAIOreply=NULL; UBYTE chans[] = {1,2,4,8}; /* Try to get one channel, any channel */ BYTE dev = -1; myAIOreq=(struct IOAudio *)AllocMem(sizeof(struct IOAudio),MEMF_PUBLIC ); if(myAIOreq) { myAIOreply=CreatePort(0,0); if(myAIOreply) { myAIOreq->ioa_Request.io_Message.mn_ReplyPort = myAIOreply; myAIOreq->ioa_Request.io_Message.mn_Node.ln_Pri = 127; myAIOreq->ioa_Request.io_Command = ADCMD_ALLOCATE; myAIOreq->ioa_AllocKey = 0; myAIOreq->ioa_Data = chans; myAIOreq->ioa_Length = sizeof(chans); dev=OpenDevice("audio.device",0L,(struct IORequest *)myAIOreq,0L); if(! dev) return( myAIOreq ); /* Successful exit */ DeletePort( myAIOreply ); } FreeMem( myAIOreq, sizeof(struct IOAudio) ); } return( NULL ); } /*--------------------------------------------------------------- ** VOID ReleaseChannel(struct IOAudio *rel ); ** ** Frees the channel and any asociated memory allocated earlier ** with SiezeChannel(). **--------------------------------------------------------------- */ VOID ReleaseChannel(struct IOAudio *rel) { if(rel) { CloseDevice( (struct IORequest *) rel ); if(rel->ioa_Request.io_Message.mn_ReplyPort) { DeletePort(rel->ioa_Request.io_Message.mn_ReplyPort); } FreeMem( rel, sizeof(struct IOAudio) ); } } /*-------------------------------------------------------------------- ** ** char *Parse8svx( char *filename, struct InterPlay *play_state) ** ** Pass this function the name of an 8svx file. It opens the file and ** finds the VHDR and BODY Chunks. Playback information is stored ** in the InterPlay structure. ** ** A NULL return indicates the parse was completely successful. ** A non-NULL return means the file cannot be played back for ** some reason. In that case the return value is a pointer to ** an error message explaining what went wrong. ** ** After calling Parse8svx(), End Parse() should be called ** to free any memory used. ** **---------------------------------------------------------------------- */ char * Parse8svx(char *fname, struct InterPlay *play) { BYTE iobuffer[12]; LONG rdcount=0L; Chunk *pChunk=NULL; GroupHeader *pGH=NULL; Voice8Header *pV8Head = NULL; char *error = NULL; BPTR filehandle=NULL; BOOL NO_BODY = TRUE; BOOL NO_VHDR = TRUE; /* Under normal operation, this function leaves the file positioned */ /* at the BODY Chunk. However, for some degenerate 8SVX files, one */ /* additional seek is needed at the end. In that case this field */ /* (play->offsetBody) will be changed to the seek offset. */ play->offsetBody = 0; play->sample_loc = NULL; /* Set to non-NULL if memory is allocated */ play->next_iplay = NULL; /* Default is no successors, no interleave */ play->sample_done= 0L; /* Will be set to 1 when playback is done */ filehandle= NULL; /* Set to non-NULL if the file opens */ NO_BODY=TRUE; NO_VHDR=TRUE; /* This section just makes sure that the first 12 bytes of the */ /* file conform to the IFF FORM specification, sub-type 8SVX. */ filehandle = Open( fname, MODE_OLDFILE ); if(filehandle) { /* Next, read the first 12 bytes to check the type */ rdcount =Read( filehandle, iobuffer, 12L ); if(rdcount==12L) { /* Make sure it is an IFF FORM type */ pGH = (GroupHeader *)iobuffer; if(pGH->ckID == FORM) { /* Make sure it is an 8SVX sub-type */ if(pGH->grpSubID != ID_8SVX) error="Not an 8SVX file\n"; } else error="Not an IFF FORM\n"; } else error="Read error or file too short1\n"; } else error="Couldn't open that file. Try another.\n"; /* Read through all Chunks until BODY and VHDR */ /* Chunks are found or until an error occurs. */ while( !error && (NO_BODY || NO_VHDR) ) { /* Read the first 8 bytes of the Chunk to get the type and size */ rdcount =Read( filehandle, iobuffer, 8L ); if(rdcount==8L) { pChunk=(Chunk *)iobuffer; switch(pChunk->ckID) { case ID_VHDR: /* AllocMem() ckSize rounded up and read */ /* the VHDR, filling in the InterPlay */ if(pChunk->ckSize & 1L) pChunk->ckSize++; pV8Head = AllocMem(pChunk->ckSize, MEMF_PUBLIC); if(pV8Head) { rdcount=Read(filehandle,pV8Head,pChunk->ckSize); if(rdcount==pChunk->ckSize ) { if(pV8Head->sCompression==sCmpNone) { /* Set the playback speed */ play->sample_speed = (UWORD) (3579545L / pV8Head->samplesPerSec); /* Set up start, end of sample data */ play->sample_size = pV8Head->oneShotHiSamples + pV8Head->repeatHiSamples; } else error="Can't read compressed file\n"; } else error="Read problem in header\n"; FreeMem(pV8Head, pChunk->ckSize ); } else error="Couldn't get header memory\n"; NO_VHDR = FALSE; break; case ID_BODY: /* Technically, a VHDR could come after a BODY.*/ /* This is a pretty unlikely occurence though. */ if(NO_VHDR) { if(pChunk->ckSize & 1L) pChunk->ckSize++; rdcount = Seek(filehandle, pChunk->ckSize, OFFSET_CURRENT); if(rdcount==-1) error="Problem during BODY-skipping seek\n"; else play->offsetBody=rdcount; } NO_BODY = FALSE; break; default: /* Ignore other Chunks, skipping over them */ if(pChunk->ckSize & 1L) pChunk->ckSize++; rdcount = Seek(filehandle, pChunk->ckSize, OFFSET_CURRENT); if(rdcount==-1) error="Problem during chunk-skipping seek\n"; break; } } else error = "Read error or file too short2\n"; } if(!error) { /* In case the VHDR came after the BODY, seek back to the BODY */ if(play->offsetBody) { rdcount = Seek(filehandle, play->offsetBody, OFFSET_BEGINNING); if(rdcount==-1) error="Couldn't seek to BODY\n"; } /* OK now get the BODY data into a memory block */ play->sample_loc = AllocMem( play->sample_size, MEMF_PUBLIC ); if(play->sample_loc) { rdcount = Read(filehandle, play->sample_loc, play->sample_size); if(rdcount!=play->sample_size) error = "Error during BODY read\n"; else play->sample_byte=play->sample_loc; } else error="Couldn't get memory for BODY Chunk\n"; } if(filehandle) Close(filehandle); return(error); } /*--------------------------------------------------------------- ** ** VOID EndParse( struct InterPlay * ); ** ** This function simply frees any memory used by an earlier ** call to Parse8svx(). ** **--------------------------------------------------------------- */ VOID EndParse( struct InterPlay *play ) { if(play->sample_loc) FreeMem(play->sample_loc, play->sample_size ); } /*----------------------------------------------------------------------------- ** ** VOID FillAudio(struct InterPlay *, struct IOAudio * ); ** ** This function gets 512 bytes each from 2 BODY buffers and interleaves ** the bytes in the audio playback buffer. **------------------------------------------------------------------------------ */ VOID FillAudio(struct InterPlay *inplay, struct IOAudio *ioa ) { struct InterPlay *play1,*play2; ULONG remainder1,remainder2,x; UWORD speedfac; WORD value; if(ioa->ioa_Request.io_Command != CMD_WRITE) /* For 1st time callers only */ { /* When two files are played at once, their speeds must match. Use */ /* whichever speed is fastest. Interleaved requests also require the */ /* speed to be doubled (period is halved). However, the period */ /* cannot be lower than 124 or audio DMA bandwidth will be exceeded. */ speedfac = inplay->sample_speed; if(inplay->next_iplay) { if(inplay->next_iplay->sample_speed < inplay->sample_speed) speedfac = inplay->next_iplay->sample_speed; if ( !(inplay->USE_SUMMING) ) speedfac /= 2; } if(speedfac < 124) speedfac = 124; ioa->ioa_Request.io_Command = CMD_WRITE; ioa->ioa_Request.io_Flags = ADIOF_PERVOL; ioa->ioa_Volume = 63; ioa->ioa_Period = speedfac; ioa->ioa_Length = BUF_SIZE; ioa->ioa_Cycles = 1; } if(inplay->next_iplay) { play1=inplay; play2=inplay->next_iplay; remainder1 = play1->sample_size - (play1->sample_byte - play1->sample_loc); remainder2 = play2->sample_size - (play2->sample_byte - play2->sample_loc); if(play1->USE_SUMMING) { /* ** AVERAGING LOGIC for playing TWO samples on ONE channel */ for(x=0; x<BUF_SIZE ;x++) { value = 0; if( x<remainder1 ) { value += *( (BYTE *)(play1->sample_byte) ); play1->sample_byte++; } else if( x==remainder1 ) play1->sample_done=1; if( x<remainder2 ) { value += *( (BYTE *)(play2->sample_byte) ); play2->sample_byte++; } else if( x==remainder2 ) play2->sample_done=1; *(ioa->ioa_Data + x) = (UBYTE) (value/2); } } else { /* ** INTERLEAVE LOGIC for playing TWO samples on ONE channel */ /* If there are more bytes in the 1st sample data file, place them in */ /* the EVEN positions in the playback buffer of this IOAudio request. */ for(x=0; (x<BUF_SIZE) && (x<2*remainder1); x+=2 ) { *(ioa->ioa_Data + x) = *(play1->sample_byte); play1->sample_byte++; } /* If there are no more bytes then mark the 1st sample as done */ if(x<BUF_SIZE) play1->sample_done=1L; while(x<BUF_SIZE) /* Pad the playback buffer with zeroes. */ { *(ioa->ioa_Data + x) = 0; x+=2; } /* If there are more bytes in the 2nd sample data file, place them in */ /* the ODD positions in the playback buffer of this IOAudio request. */ for(x=1; (x<BUF_SIZE) && (x<2*remainder2);x+=2) { *(ioa->ioa_Data + x) = *(play2->sample_byte); play2->sample_byte++; } /* If there are no more bytes then mark the 2nd sample as done */ if(x<BUF_SIZE) play2->sample_done=1L; while(x<BUF_SIZE) /* Pad the playback buffer with zeroes. */ { *(ioa->ioa_Data + x) = 0; x+=2; } } } else { /* ** REGULAR LOGIC for playing a single sample on a single channel. */ remainder1= inplay->sample_size - (inplay->sample_byte-inplay->sample_loc); if(remainder1 > BUF_SIZE) { CopyMem(inplay->sample_byte,ioa->ioa_Data,BUF_SIZE); inplay->sample_byte+=BUF_SIZE; } else { CopyMem(inplay->sample_byte,ioa->ioa_Data,remainder1); ioa->ioa_Length=remainder1; inplay->sample_done=1L; } } }