home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Amiga Developer CD v1.2
/
amidev_cd_12.iso
/
reference
/
amiga_mail_vol2
/
vi-1
/
interplay.c
< prev
next >
Wrap
C/C++ Source or Header
|
1996-01-30
|
23KB
|
670 lines
;/* 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 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. */
/* 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;
}
}
}