home *** CD-ROM | disk | FTP | other *** search
- ;/* 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;
- }
- }
- }
-
-