www.pudn.com > vmix_1.013.zip > WAVE.C
; /*\ ;---|*|----====< Play >====---- ;---|*| ;---|*| play small a list of wave files ;---|*| ;---|*| Copyright (c) 1993,1994 V.E.S.A, Inc. All Rights Reserved. ;---|*| ;---|*| VBE/AI 1.0 Specification ;---|*| April 6, 1994. 1.00 release ;---|*| ;---|*| ******************************************************************* ;---|*| This program is modified by Joel Chen to do mixing waves output. ;---|*| You are free to use it anyway you like. Giving me some credit ;---|*| would be nice if you use it. ;---|*| #include;---|*| Anything that can possibly happen is not my fault, including ;---|*| events such as your red hot CPU suddenly becomes a mini-bomb and ;---|*| blows up your entire house/building/apt/whatever. ;---|*| The vbe/ai sdk can be obtained from the game programming site ;---|*| x2ftp.oulu.fi: /pub/msdos/programming/mxlibs/vaisdk.zip ;---|*| joel... ;---|*| Jan 17, 1996 Note: This program and the original play.exe from the vbe/ai sdk failed to play waves correctly on my friend's computer with a Thunder Board. It looks like that the dma is not autoinit and no interrupt is generated. Strangely, I remember that it used to work. ; \*/ #include #include #include #include #include #include #include "vbeai.h" #include "vesa.h" #include "wave.h" ; /*\ ;---|*| Public Global Variables ; \*/ PUBLIC int SoundPlaying = 0; ; /*\ ;---|*|----------------------=======================--------------------------- ;---|*|----------------------====< Private stuff >====------------------------- ;---|*|----------------------=======================--------------------------- ; \*/ ; /*\ ;---|*| Private Local Variables ; \*/ #include "wavetype.h" PRIVATE int WAVEPoweredUp = 0; #if VERBOSE_MODE PRIVATE int WaveCallBackOccured = 0; #endif PRIVATE int InWaveCallBack = 0; PRIVATE int WaveErrorCallBackOccured = 0; PRIVATE VESAHANDLE hWAVE = 0; PRIVATE fpWAVServ wfn; // pointer to wave functions PRIVATE char far *DMAptr = 0; PRIVATE char far *dmaptr = 0; PRIVATE char far *WAVmemptr = 0; PRIVATE int MixBuf[MIX_LEN]; PRIVATE int SilentBuf[MIX_LEN]; PRIVATE int TotalSounds = -1; PRIVATE int MySoundPlaying = 0; PRIVATE int SoundDone = 0; PRIVATE Sound MixSnds[MIX_SNDS]; #define UseQueue 0 #if UseQueue typedef struct { int I; int rep; wave *Wav; } SoundQueue; PRIVATE SoundQueue MixQueue[MIX_SNDS]; PRIVATE int QueueIdx = 0; #endif ; /*\ ;---|*| Private prototypes ; \*/ void far pascal OurWAVECallback ( int, void far *, long, long ); void far pascal ErrorWAVECallback( int, void far *, long, long ); PRIVATE int PASCAL MixSounds( void ); PRIVATE int PASCAL InitSound( void ); PRIVATE VESAHANDLE PASCAL WAVEOpenTheDriver ( int ); PRIVATE int PASCAL ProcessWAVHeader ( int, wave *); PRIVATE int PASCAL WAVEDriverError ( void ); #if VERBOSE_MODE PRIVATE void PASCAL ReportWave ( void ); #endif // only allows us to run on this version of the VBE interface. This // will be removed after the interface is ratified. Changes may be made // that would cause this code to crash if run on other version. Again, // this will be removed in the final software. PRIVATE int WAVEVersionControl = 0x0100; ; /*\ ;---|*|------------------==============================------------------- ;---|*|------------------====< Public Subroutines >====------------------- ;---|*|------------------==============================------------------- ; \*/ #define TEST_WAVE 0 #if TEST_WAVE void DoExit( void ) { #if VERBOSE_MODE printf("Shuting down the wave driver.\n"); #endif ShutDownWAVE(); } main( int argc, char **argv ) { int i, n, quit = 0; wave Waves[10]; if ( argc < 2 ) { printf( "usage: wave list of wave files.\n"); return 0; } atexit(DoExit); if ( PowerUpWAVE() == -1 ) exit(-1); argc --; for( n = 0; n < argc; n ++ ) { // // Very important! The next line tells LoadWave don't try to free // the memory pointed to by wave.ptr because there isn't any yet. // Waves[n].ptr = 0; if ( LoadWave(argv[n+1], &Waves[n]) == 0 ) { printf("Loading wave %s failed.\n", argv[n+1]); exit(-2); } } // Play all sounds using parameters from this wave SetToWaveParam( &Waves[0] ); printf( "\nPress a number will play the corresponding wave.\n"); while(!quit) { // Keeps the WAVE driver alive. Specifically, stop playing if // SoundDone is true. Somehow the program crashes if I try to // do it in the timer call back function. Probably has something // to do with some registers. If you fix it and get it to work, // make sure you send me the changes. :) UpdateWAVE(); if ( kbhit()) { switch(i = getch()) { case 27: quit = 1; break; case 'r': case 'R': // Remove all the sounds playing for( i = 0; i < MIX_SNDS; i ++ ) { RemoveSound(i); } break; default: if ( isdigit(i)) { i -= '0'; if ( i < n ) { if ( AddSound( &Waves[i], 0 ) >= 0 ) printf("Playing %s.\n", argv[i+1]); } } break; } } } exit(0); } #endif PUBLIC int PASCAL PowerUpWAVE( void ) { if ( WAVEPoweredUp ) return 0; if ( !WAVEOpenTheDriver(0)) { return -1; } WAVEPoweredUp = 1; return 1; } ; ; /*\ ;---|*|----====< ShutDownWAVE >====---- ;---|*| ;---|*| Shut Wave player down ;---|*| ; \*/ PUBLIC void PASCAL ShutDownWAVE( void ) { // close the device if already opened if (wfn) { VESACloseDevice(hWAVE); // close the device wfn = 0; } // Free allocated memory if ( dmaptr ) { DelBuf(dmaptr); dmaptr = 0; } if ( WAVmemptr ) { DelBuf(WAVmemptr); WAVmemptr = 0; } } PUBLIC int PASCAL SetToWaveParam( wave *Wav ) { if ( !WAVEPoweredUp ) return 0; // set the sample rate, etc. (wfn->wsPCMInfo) ( Wav->ch, Wav->sr, Wav->cp, 0, Wav->sz ); return 1; } PUBLIC int PASCAL LoadWave( char *WAVFileName, wave *Wav ) { int fhan, siz; long i; char far *ptr; if ( !WAVEPoweredUp ) return 0; Wav->fname = WAVFileName; if ((fhan = open(WAVFileName, O_BINARY|O_RDONLY)) == -1) { perror("open wave file"); return 0; } lseek( fhan, 0, SEEK_SET ); if ( ProcessWAVHeader(fhan, Wav) != VALIDWAVE ) { printf("Invalid .WAV file: %s.\n", WAVFileName ); return 0; } if ( Wav->ptr ) DelBuf( Wav->ptr ); Wav->ptr = NewBuf(Wav->len); if ( Wav->ptr == 0 ) { printf( "Ran out of memory loading wave.\n"); return 0; } siz = readblock( fhan, Wav->ptr, (int)Wav->len ); close(fhan); // convert to sign format ptr = Wav->ptr; for( i = 0; i < Wav->len; i ++ ) { *ptr = (char)((byte)*ptr) - 128; ptr ++; } return siz; } PUBLIC int PASCAL AddSound( wave *wav, int rep ) { int I; if ( TotalSounds >= MIX_SNDS ) return -1; if ( TotalSounds < 0 ) InitSound(); for( I = 0; I < MIX_SNDS; I ++ ) { if ( MixSnds[I].valid == 0 ) break; } if ( MixSnds[I].valid ) return -1; // sanity TotalSounds ++; #if VERBOSE_MODE printf("TotalSounds: %d\n", TotalSounds ); #endif #if UseQueue if ( !MySoundPlaying ) { // printf("First sound.\n"); #endif MixSnds[I].ptr = wav->ptr; MixSnds[I].valid = 1; MixSnds[I].repeat = rep; MixSnds[I].len = wav->len; MixSnds[I].pos = 0; #if !UseQueue if ( !MySoundPlaying ) { #if VERBOSE_MODE printf("First sound.\n"); #endif #endif /* !UseQueue */ SoundDone = 0; MySoundPlaying = SoundPlaying = 1; // Start playing DMAptr = dmaptr; MixSounds(); // Mix into first half of the buffer DMAptr += MIX_LEN; MixSounds(); // Mix into second half of the buffer (wfn->wsPlayCont)( dmaptr, (MIX_LEN*2), MIX_LEN ); } #if UseQueue else { MixSnds[I].valid = 2; MixQueue[QueueIdx].Wav = wav; MixQueue[QueueIdx].I = I; MixQueue[QueueIdx].rep = rep; QueueIdx ++; } #endif return I; } PUBLIC int PASCAL RemoveSound( int which ) { if ( which >= 0 && which < MIX_SNDS ) { if ( MixSnds[which].valid ) { MixSnds[which].valid = 0; TotalSounds --; if ( TotalSounds == 0 ) { (wfn->wsStopIO)(0); MySoundPlaying = SoundPlaying = 0; } } } else return 0; return 1; } PUBLIC void PASCAL UpdateWAVE( void ) { #if VERBOSE_MODE if ( WaveCallBackOccured ) { WaveCallBackOccured --; ReportWave(); } #endif if ( SoundDone ) { (wfn->wsStopIO)(0); SoundDone = 0; } } ; /*\ ;---|*|----------------------=======================---------------------- ;---|*|----------------------====< Private Subroutines >====-------------- ;---|*|----------------------=======================---------------------- ; \*/ ; ; /*\ ;---|*|----====< WAVEDriverError >====---- ;---|*| ;---|*| Report any errors by the driver ;---|*| ; \*/ PRIVATE int PASCAL WAVEDriverError( void ) { int err; if ( (err = (wfn->wsGetLastError)()) ) { printf ("Driver reported an error! (code=%02X)\n",err); return (err); } return(0); } ; ; /*\ ;---|*|----====< ErrorWAVECallback >====---- ;---|*| ;---|*| If we get this, we received the wrong callback! ;---|*| ; \*/ void far pascal ErrorWAVECallback( han, fptr, len, filler ) int han; // device handle void far *fptr; // buffer that just played long len; // length of completed transfer long filler; // reserved... { // setup our data segment _asm { push ds mov ax,seg WaveErrorCallBackOccured mov ds,ax inc [WaveErrorCallBackOccured] pop ds } } ; ; /*\ ;---|*|----====< WAVEOpenTheDriver >====---- ;---|*| ;---|*| Find the driver with the highest user preference, and return it to ;---|*| the caller ;---|*| ; \*/ PRIVATE int PASCAL WAVEOpenTheDriver(pref) int pref; { long l; int try = 0; int driverpref = 256; // real low preference GeneralDeviceClass gdc; // receives a copy of the VESA driver info block // find a matching driver do { try ++; // Find one DAC device, else bail if non found if ((hWAVE = VESAFindADevice(WAVDEVICE)) == 0) continue; // get the device information if (VESAQueryDevice(hWAVE, VESAQUERY2 ,&gdc) == 0) { printf ("Cannot query the installed VAI devices!\n"); return 0; } // make sure its a wave device if (gdc.gdclassid != WAVDEVICE) { printf ("The VESA find device query returned a NON DAC device!\n"); return 0; } // make sure it's matches the beta version # if (gdc.gdvbever != WAVEVersionControl) { printf ("The VESA device version # does not match, cannot continue!\n"); return 0; } // get the drivers user preference level driverpref = gdc.u.gdwi.widevpref; // if the caller is not expressing a preference, then use this one if (pref == -1) break; } while (driverpref != pref && try < 4); if ( hWAVE == 0 ) { printf ("Cannot find any installed VBE/AI WAVE devices!\n"); return(0); } // get the memory needed by the device if (!(WAVmemptr = NewBuf(gdc.u.gdwi.wimemreq))) { printf ("We don't have memory for the wave device!\n"); return (0); } // if the DAC device doesn't open, bomb out... if ((wfn = (fpWAVServ)VESAOpenADevice(hWAVE,0,WAVmemptr)) == 0) { printf ("Cannot Open the installed wave devices!\n"); return (0); } // setup the record and playback callbacks wfn->wsApplPSyncCB = &OurWAVECallback; wfn->wsApplRSyncCB = &ErrorWAVECallback; // return the handle return(hWAVE); } PRIVATE int PASCAL MixSounds( void ) { register int I; long J; int *MixPtr; char far *DataPtr; // for( I = 0; I < MIX_LEN; I ++ ) MixBuf[I] = 128; memcpy( (void*)MixBuf, (const void*)SilentBuf, MIX_LEN*sizeof(int) ); for( I = 0; I < MIX_SNDS; I ++ ) { if ( MixSnds[I].valid == 1 ) { DataPtr = MixSnds[I].ptr + MixSnds[I].pos; if ( MixSnds[I].len - MixSnds[I].pos <= MIX_LEN ) { MixPtr = MixBuf; J = MixSnds[I].pos; for( ; J != MixSnds[I].len; J ++ ) { *MixPtr++ += (char)*DataPtr++; } // repeat here. if ( MixSnds[I].repeat ) { MixSnds[I].pos = 0; } else { MixSnds[I].valid = 0; TotalSounds --; } } else { // Play a chunk for( J = 0; J < MIX_LEN; J ++ ) { MixBuf[J] += (char)*DataPtr++; } MixSnds[I].pos += MIX_LEN; } } } // clip after all the samples are collected from each sound in the list for( I = 0; I < MIX_LEN; I ++ ) { if (MixBuf[I] < 0) (byte)DMAptr[I] = 0; else if ( MixBuf[I] > 255 ) (byte)DMAptr[I] = 255; else (byte)DMAptr[I] = (byte)MixBuf[I]; } #if UseQueue while( QueueIdx ) { wave *wav = MixQueue[--QueueIdx].Wav; I = MixQueue[QueueIdx].I; MixSnds[I].ptr = wav->ptr; MixSnds[I].valid = 1; MixSnds[I].repeat = MixQueue[QueueIdx].rep; MixSnds[I].len = wav->len; MixSnds[I].pos = 0; } #endif return 1; } ; ; /*\ ;---|*|----====< MakeZeroOffset >====---- ;---|*| ;---|*| Make the buffer address linear, with a zero offset, ;---|*| not crossing a 64k boundary! (whew!). The true address pointer must ;---|*| be pointing to a buffer of the length of len*2+16 ;---|*| ; \*/ char huge *MakeZeroOffset (addr,len) char huge *addr; long len; { long l; _asm { mov ax,word ptr [addr+0] mov dx,word ptr [addr+2] mov bx,0xFFF0 // get the linear address rol dx,4 and bx,dx xor dx,bx add ax,bx adc dx,0 and ax,0xFFF0 // go to the next 16 byte boundary add ax,0x0010 mov bx,dx // check to see if we wrap a mov cx,ax // 64k boundary add cx,word ptr [len+0] adc bx,word ptr [len+2] // bx = dx, or bx = dx + 1 sub bx,dx // bx = 0x0000 if same, else 0x0001 neg bx // bx = 0x0000 if same, else 0xFFFF sub dx,bx // add 1 if it wrapped not bx // bx = 0xFFFF if same, else 0x0000 and ax,bx // flush if wrapped, else save ror dx,4 // make a seg:off out of the linear add dx,ax // address sub ax,ax } } PRIVATE int PASCAL InitSound( void ) { int I; if ( TotalSounds < 0 ) { #if VERBOSE_MODE printf("Init sounds.\n"); #endif TotalSounds = 0; for( I = 0; I < MIX_LEN; I ++ ) SilentBuf[I] = 128; for( I = 0; I < MIX_SNDS; I ++ ) { MixSnds[I].ptr = 0; MixSnds[I].valid = 0; MixSnds[I].repeat = 0; MixSnds[I].len = MixSnds[I].pos = 0; } if ((dmaptr = NewBuf(MIX_LEN*2+16)) == 0) { printf ("Unable to allocate DMA buffer memory!\n"); return 0; } dmaptr = MakeZeroOffset(dmaptr,MIX_LEN*2); DMAptr = dmaptr; return 1; } else return 0; } ; /*\ ;---|*|----====< OurWAVECallback >====---- ;---|*| ;---|*| Block End callback. NOTE: No assumptions can be made about ;---|*| the segment registers! (DS,ES,GS,FS) ;---|*| ; \*/ void far pascal OurWAVECallback( han, fptr, len, filler ) VESAHANDLE han; // device handle void far *fptr; // buffer that just played long len; // length of completed transfer long filler; // reserved... { // setup our data segment _asm { push ds mov ax,seg InWaveCallBack mov ds,ax } if ( InWaveCallBack == 0 ) { #if VERBOSE_MODE _asm inc [WaveCallBackOccured]; #endif _asm inc [InWaveCallBack]; if ( TotalSounds == 0 ) { SoundDone = 1; MySoundPlaying = SoundPlaying = 0; } else { if ( DMAptr != dmaptr ) DMAptr = dmaptr; else DMAptr = dmaptr + MIX_LEN; MixSounds(); } _asm dec [InWaveCallBack]; } _asm pop ds; } ; ; /*\ ;---|*|----====< ProcessWAVHeader >====---- ;---|*| ;---|*| load the header from our WAV file format ;---|*| ; \*/ PRIVATE int PASCAL ProcessWAVHeader(int fh,wave *wav) { int n; RiffWave rw; // eat the RIFF portion of the header readblock ( fh, (void far *)&rw, sizeof (RiffWave) ); // make sure its says RIFF n = rw.riff.name[0] - 'R'; n += rw.riff.name[1] - 'I'; n += rw.riff.name[2] - 'F'; n += rw.riff.name[3] - 'F'; if (n) return(UNKNOWNFILETYPE); // make sure it says WAVE n = rw.wave.name[0] - 'W'; n += rw.wave.name[1] - 'A'; n += rw.wave.name[2] - 'V'; n += rw.wave.name[3] - 'E'; if (n) return(UNKNOWNFILETYPE); // make sure it says 'fmt ' n = rw.wave.fmt.name[0] - 'f'; n = rw.wave.fmt.name[1] - 'm'; n = rw.wave.fmt.name[2] - 't'; n = rw.wave.fmt.name[3] - ' '; if (n) return(UNKNOWNFILETYPE); wav->ch = rw.wave.fmt.info.nChannels; wav->sr = rw.wave.fmt.info.nSamplesPerSec; wav->sz = rw.wave.fmt.info.nBitsPerSample; wav->cp = 0; // make sure it says 'data' n = rw.wave.data.name[0] - 'd'; n = rw.wave.data.name[1] - 'a'; n = rw.wave.data.name[2] - 't'; n = rw.wave.data.name[3] - 'a'; if (n) return(UNKNOWNFILETYPE); wav->len = rw.wave.data.length; // return OKAY return(VALIDWAVE); } #if VERBOSE_MODE PRIVATE void PASCAL ReportWave( void ) { register char far *vidptr = (char far *)0xb8000140; char sstr[80],*s; static int cbcnt = 0; // create the string sprintf (sstr,"%5d %5d",cbcnt++, WaveCallBackOccured ); // blast it out... s = sstr; while (*s) { *vidptr++ = *s++; *vidptr++ = 0x3F; } } #endif ; /*\ ;---|*| end of WAVE.C ; \*/