/* WinWave.c * * Windows Wave Sound Devicee * * $Id: winwave.c,v 1.8 1997/01/16 18:41:59 pekangas Exp $ * * Copyright 1996,1997 Housemarque Inc. * * This file is part of the MIDAS Sound System, and may only be * used, modified and distributed under the terms of the MIDAS * Sound System license, LICENSE.TXT. By continuing to use, * modify or distribute this file you indicate that you have * read the license and understand and accept it fully. */ #include #include #include #include "lang.h" #include "mtypes.h" #include "errors.h" #include "mglobals.h" #include "sdevice.h" #include "mmem.h" #include "dsm.h" RCSID(char const *winwave_rcsid = "$Id: winwave.c,v 1.8 1997/01/16 18:41:59 pekangas Exp $";) //#define DUMPBUFFER #define WINWVERSION 1.00 #define WINWVERSTR "1.00" /* Maximum number of buffer blocks: (FIXME) */ #define MAXBUFBLOCKS 32 /* Number of bits of accuracy in mixing for 8-bit output: */ #define MIX8BITS 12 /* Sound Device information */ /* Sound Card names: */ static char *winwCardName = "Windows Wave output"; /* Sound Device internal static variables */ static unsigned mixRate, outputMode; static unsigned bufferLen, numBlocks, blockLen; static unsigned mixElemSize; static unsigned amplification; static unsigned updateMix; /* number of elements to mix between two updates */ static unsigned mixLeft; /* number of elements to mix before next update */ static HWAVEOUT waveHandle; static HANDLE blockHandles[MAXBUFBLOCKS]; static uchar *blocks[MAXBUFBLOCKS]; static HANDLE blockHeaderHandles[MAXBUFBLOCKS]; static WAVEHDR *blockHeaders[MAXBUFBLOCKS]; static int blockPrepared[MAXBUFBLOCKS]; static unsigned blockNum; /* current mixing block number */ static unsigned blockPos; /* mixing position inside block */ static uchar *ppTable; /* post-processing table for 8-bit output */ #ifdef DUMPBUFFER static FILE *buff; #endif /****************************************************************************\ * enum winwFunctIDs * ----------------- * Description: ID numbers for Windows Wave Sound Device functions \****************************************************************************/ enum winwFunctIDs { ID_winwDetect = ID_winw, ID_winwInit, ID_winwClose, ID_winwGetMode, ID_winwOpenChannels, ID_winwSetAmplification, ID_winwGetAmplification, ID_winwSetUpdRate, ID_winwStartPlay, ID_winwPlay }; /* Local prototypes: */ int CALLING winwSetAmplification(unsigned _amplification); int CALLING winwGetAmplification(unsigned *_amplification); int CALLING winwSetUpdRate(unsigned updRate); /****************************************************************************\ * * Function: static int winwError(MMRESULT error) * * Description: Converts a Windows multimedia system error code to MIDAS * error code * * Input: MMRESULT error Windows multimedia system error code * * Returns: MIDAS error code * \****************************************************************************/ static int winwError(MMRESULT error) { switch ( error ) { case MMSYSERR_NOERROR: return OK; case MMSYSERR_ERROR: return errUndefined; case MMSYSERR_BADDEVICEID: case MMSYSERR_INVALHANDLE: case MMSYSERR_NOTENABLED: return errInvalidDevice; case MMSYSERR_ALLOCATED: case MMSYSERR_HANDLEBUSY: return errDeviceBusy; case MMSYSERR_NODRIVER: return errDeviceNotAvailable; case MMSYSERR_NOMEM: return errOutOfMemory; case MMSYSERR_NOTSUPPORTED: return errUnsupported; case MMSYSERR_INVALFLAG: case MMSYSERR_INVALPARAM: return errInvalidArguments; case WAVERR_BADFORMAT: return errBadMode; case WAVERR_STILLPLAYING: return errDeviceBusy; case WAVERR_UNPREPARED: return errInvalidArguments; case WAVERR_SYNC: return errInvalidDevice; } return errUndefined; } /* Error code passing macros for multimedia system errors - similar to PASSERROR in errors.h: */ #ifdef DEBUG #define PASSWINERR(error_, functID) { error = winwError(error_); \ errAdd(winwError(error_), functID); return error; } #else #define PASSWINERR(error_, functID) return winwError(error_); #endif /****************************************************************************\ * * Function: static unsigned CALLING (*postProc)(unsigned numElements, * uchar *bufStart, unsigned mixPos, unsigned *mixBuffer, * uchar *ppTable); * * Description: Pointer to the actual post-processing routine. Takes * DSM output elements from dsmMixBuffer and writes them to * output buffer at *bufStart in a format suitable for the Sound * Device. * * Input: unsigned numElements number of elements to process * (guaranteed to be even) * uchar *bufStart pointer to start of output buffer * unsigned mixPos mixing position in output buffer * unsigned *mixBuffer source mixing buffer * uchar *ppTable pointer to post-processing table * * Returns: New mixing position in output buffer. Can not fail. * \****************************************************************************/ static unsigned CALLING (*postProc)(unsigned numElements, uchar *bufStart, unsigned mixPos, unsigned *mixBuffer, uchar *ppTable); /****************************************************************************\ * * Function: unsigned pp16Mono(); * * Description: 16-bit mono post-processing routine * \****************************************************************************/ unsigned CALLING pp16Mono(unsigned numElements, uchar *bufStart, unsigned mixPos, unsigned *mixBuffer, uchar *ppTable); /****************************************************************************\ * * Function: unsigned pp8Mono(); * * Description: 8-bit mono post-processing routine * \****************************************************************************/ unsigned CALLING pp8Mono(unsigned numElements, uchar *bufStart, unsigned mixPos, unsigned *mixBuffer, uchar *ppTable); /****************************************************************************\ * * Function: unsigned pp16Stereo(); * * Description: 16-bit stereo post-processing routine * \****************************************************************************/ unsigned CALLING pp16Stereo(unsigned numElements, uchar *bufStart, unsigned mixPos, unsigned *mixBuffer, uchar *ppTable); /****************************************************************************\ * * Function: unsigned pp8Stereo(); * * Description: 8-bit stereo post-processing routine * \****************************************************************************/ unsigned CALLING pp8Stereo(unsigned numElements, uchar *bufStart, unsigned mixPos, unsigned *mixBuffer, uchar *ppTable); /****************************************************************************\ * * Function: int winwDetect(int *result) * * Description: Detects a Windows Wave Sound Device * * Input: int *result pointer to detection result * * Returns: MIDAS error code. Detection result (1 if detected, 0 if not) * is written to *result. * \****************************************************************************/ int CALLING winwDetect(int *result) { /* Check that we have at least one wave output device: */ if ( waveOutGetNumDevs() < 1 ) *result = 0; else *result = 1; return OK; } /****************************************************************************\ * * Function: int winwInit(unsigned mixRate, unsigned mode) * * Description: Initializes Windows Wave Sound Device * * Input: unsigned mixRate mixing rate in Hz * unsigned mode output mode * * Returns: MIDAS error code * \****************************************************************************/ int CALLING winwInit(unsigned _mixRate, unsigned mode) { unsigned i; MMRESULT mmError; int error; WAVEHDR *header; WAVEFORMATEX format; int mixMode; mixRate = _mixRate; /* Determine the actual output mode: */ if ( mode & sdMono ) outputMode = sdMono; else outputMode = sdStereo; if ( mode & sd8bit ) outputMode |= sd8bit; else outputMode |= sd16bit; /* Calculate one mixing element size: */ if ( outputMode & sd16bit ) mixElemSize = 2; else mixElemSize = 1; if ( outputMode & sdStereo ) mixElemSize <<= 1; /* Limit number of blocks to MAXBUFBLOCKS: */ numBlocks = mBufferBlocks; if ( numBlocks > MAXBUFBLOCKS ) numBlocks = MAXBUFBLOCKS; /* Calculate required buffer block length: (must be a multiple of 16 bytes) */ blockLen = mixRate * mixElemSize * mBufferLength / 1000 / numBlocks; blockLen = (blockLen + 15) & (~15); bufferLen = numBlocks * blockLen; blockNum = blockPos = 0; /* Set up wave output format structure: */ format.wFormatTag = WAVE_FORMAT_PCM; if ( outputMode & sdStereo ) format.nChannels = 2; else format.nChannels = 1; format.nSamplesPerSec = mixRate; format.nAvgBytesPerSec = mixElemSize * mixRate; format.nBlockAlign = mixElemSize; if ( outputMode & sd16bit ) format.wBitsPerSample = 16; else format.wBitsPerSample = 8; format.cbSize = 0; /* Open wave output device using the format just set up: */ if ( (mmError = waveOutOpen(&waveHandle, WAVE_MAPPER, &format, 0, 0, 0)) != 0 ) PASSWINERR(mmError, ID_winwInit); /* Allocate and lock memory for all mixing blocks: */ for ( i = 0; i < numBlocks; i++ ) { /* Allocate global memory for mixing block: */ if ( (blockHandles[i] = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, blockLen)) == NULL ) { ERROR(errOutOfMemory, ID_winwInit); return errOutOfMemory; } /* printf("Block %i handle %08X\n", i, blockHandles[i]); */ /* Lock mixing block memory: */ if ( (blocks[i] = GlobalLock(blockHandles[i])) == NULL ) { ERROR(errUnableToLock, ID_winwInit); return errUnableToLock; } } /* Allocate and lock memory for all mixing block headers: */ for ( i = 0; i < numBlocks; i++ ) { /* Allocate global memory for mixing block: */ if ( (blockHeaderHandles[i] = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, sizeof(WAVEHDR))) == NULL ) { ERROR(errOutOfMemory, ID_winwInit); return errOutOfMemory; } /* Lock mixing block memory: */ if ( (header = blockHeaders[i] = GlobalLock(blockHeaderHandles[i])) == NULL ) { ERROR(errUnableToLock, ID_winwInit); return errUnableToLock; } /* Reset wave header fields: */ header->lpData = blocks[i]; header->dwBufferLength = blockLen; header->dwFlags = WHDR_DONE; /* mark the block is done */ header->dwLoops = 0; /* Block header is not prepared: */ blockPrepared[i] = 0; } /* Allocate memory for post-processing table if necessary: */ if ( outputMode & sd8bit ) { /* Allocate memory for 8-bit output mode post-processing table: */ if ( (error = memAlloc((1 << MIX8BITS), &ppTable)) != OK ) PASSERROR(ID_winwInit); } else ppTable = NULL; /* Check correct mixing mode: */ if ( outputMode & sdStereo ) mixMode = dsmMixStereo; else mixMode = dsmMixMono; /* Initialize Digital Sound Mixer: */ if ( outputMode & sd16bit ) { if ( (error = dsmInit(mixRate, mixMode, 16)) != OK ) PASSERROR(ID_winwInit) } else { if ( (error = dsmInit(mixRate, mixMode, MIX8BITS)) != OK ) PASSERROR(ID_winwInit) } /* Set update rate to 50Hz: */ if ( (error = winwSetUpdRate(5000)) != OK ) PASSERROR(ID_winwInit) /* Point postProc() to correct post-processing routine: */ switch ( outputMode ) { case (sd16bit | sdMono): postProc = &pp16Mono; break; case (sd8bit | sdMono): postProc = &pp8Mono; break; case (sd16bit | sdStereo): postProc = &pp16Stereo; break; case (sd8bit | sdStereo): postProc = &pp8Stereo; break; default: ERROR(errInvalidArguments, ID_winwInit); return errInvalidArguments; } amplification = 64; #ifdef DUMPBUFFER buff = fopen("buffer.raw", "wb"); #endif return OK; } /****************************************************************************\ * * Function: winwClose(void) * * Description: Uninitializes Windows Wave Sound Device * * Returns: MIDAS error code * \****************************************************************************/ int CALLING winwClose(void) { int error; MMRESULT mmError; unsigned i; int allDone; DWORD lasterror; #ifdef DUMPBUFFER fclose(buff); #endif /* Uninitialize Digital Sound Mixer: */ if ( (error = dsmClose()) != OK ) PASSERROR(ID_winwClose) /* Deallocate post-processing table if necessary: */ if ( outputMode & sd8bit ) { if ( (error = memFree(ppTable)) != OK ) PASSERROR(ID_winwClose); } /* Reset wave output device, stop playback, and mark all blocks done: */ if ( (mmError = waveOutReset(waveHandle)) != 0 ) PASSWINERR(mmError, ID_winwClose); /* Make sure all blocks are indeed done: */ while ( 1 ) { allDone = 1; for ( i = 0; i < numBlocks; i++ ) { if ( (blockHeaders[i]->dwFlags & WHDR_DONE) == 0 ) allDone = 0; } if ( allDone ) break; Sleep(20); } /* Unprepare all mixing blocks: */ for ( i = 0; i < numBlocks; i++ ) { if ( blockPrepared[i] ) { if ( (mmError = waveOutUnprepareHeader(waveHandle, blockHeaders[i], sizeof(WAVEHDR))) != 0 ) PASSWINERR(mmError, ID_winwClose); } } /* Close wave output device: */ if ( (mmError = waveOutClose(waveHandle)) != 0 ) PASSWINERR(mmError, ID_winwClose); /* Unlock and deallocate all mixing blocks: */ for ( i = 0; i < numBlocks; i++ ) { /* printf("Unlock block %i\n", i); */ /* Unlock the mixing block handle: */ if ( (!GlobalUnlock(blockHandles[i])) && ((lasterror = GetLastError()) != NO_ERROR) ) { /* printf("GetLastError(): %u, Handle: %08X\n", lasterror, blockHandles[i]); */ ERROR(errHeapCorrupted, ID_winwClose); return errHeapCorrupted; } /* printf("Free block %i\n", i); */ /* Deallocate the mixing block: */ if ( GlobalFree(blockHandles[i]) != NULL ) { ERROR(errHeapCorrupted, ID_winwClose); return errHeapCorrupted; } } /* Unlock and deallocate all mixing block headers: */ for ( i = 0; i < numBlocks; i++ ) { /* printf("Unlock header %i\n", i); */ /* Unlock the mixing block header handle: */ if ( (!GlobalUnlock(blockHeaderHandles[i])) && ( GetLastError() != NO_ERROR) ) { ERROR(errHeapCorrupted, ID_winwClose); return errHeapCorrupted; } /* printf("Free header %i\n", i); */ /* Deallocate the mixing block: */ if ( GlobalFree(blockHeaderHandles[i]) != NULL ) { ERROR(errHeapCorrupted, ID_winwClose); return errHeapCorrupted; } } return OK; } /****************************************************************************\ * * Function: int winwGetMode(unsigned *mode) * * Description: Reads the current output mode * * Input: unsigned *mode pointer to output mode * * Returns: MIDAS error code. Output mode is written to *mode. * \****************************************************************************/ int CALLING winwGetMode(unsigned *mode) { *mode = outputMode; return OK; } /****************************************************************************\ * * Function: int winwOpenChannels(unsigned channels) * * Description: Opens sound channels for output. Prepares post-processing * tables, takes care of default amplification and finally opens * DSM channels. Channels can be closed by simply calling * dsmCloseChannels(). * * Input: unsigned channels number of channels to open * * Returns: MIDAS error code * \****************************************************************************/ int CALLING winwOpenChannels(unsigned channels) { int error; /* Open DSM channels: */ if ( (error = dsmOpenChannels(channels)) != OK ) PASSERROR(ID_winwOpenChannels) /* Take care of default amplification and calculate new post-processing table if necessary: */ /* if ( outputMode & sd8bit ) { */ if ( channels < 5 ) winwSetAmplification(64); else winwSetAmplification(14*channels); /* } */ return OK; } /****************************************************************************\ * * Function: void CalcPP8Table(void) * * Description: Calculates a new 8-bit output post-processing table using * current amplification level * \****************************************************************************/ static void CalcPP8Table(void) { uchar *tbl; int val; long temp; tbl = ppTable; /* tbl points to current table pos */ /* Calculate post-processing table for all possible DSM values: (table must be used with unsigned numbers - add (1 << MIX8BITS)/2 to DSM output values first) */ for ( val = -(1 << MIX8BITS)/2; val < (1 << MIX8BITS)/2; val++ ) { /* Calculate 8-bit unsigned output value corresponding to the current DSM output value (val), taking amplification into account: */ temp = 128 + ((((long) amplification) * ((long) val) / 64L) >> (MIX8BITS-8)); /* Clip the value to fit between 0 and 255 inclusive: */ if ( temp < 0 ) temp = 0; if ( temp > 255 ) temp = 255; /* Write the value to the post-processing table: */ *(tbl++) = (uchar) temp; } } /****************************************************************************\ * * Function: int winwSetAmplification(unsigned amplification) * * Description: Sets the amplification level. Calculates new post-processing * tables and calls dsmSetAmplification() as necessary. * * Input: unsigned amplification amplification value * * Returns: MIDAS error code * \****************************************************************************/ int CALLING winwSetAmplification(unsigned _amplification) { int error; amplification = _amplification; if ( outputMode & sd8bit ) { /* 8-bit output mode - do not set amplification level using DSM, but calculate a new post-processing table instead: */ CalcPP8Table(); } else { /* Set amplification level to DSM: */ if ( (error = dsmSetAmplification(amplification)) != OK ) PASSERROR(ID_winwSetAmplification) } return OK; } /****************************************************************************\ * * Function: int winwGetAmplification(unsigned *amplification); * * Description: Reads the current amplification level. (DSM doesn't * necessarily know the actual amplification level if * post-processing takes care of amplification) * * Input: unsigned *amplification pointer to amplification level * * Returns: MIDAS error code. Amplification level is written to * *amplification. * \****************************************************************************/ int CALLING winwGetAmplification(unsigned *_amplification) { *_amplification = amplification; return OK; } /****************************************************************************\ * * Function: int winwSetUpdRate(unsigned updRate); * * Description: Sets the channel value update rate (depends on song tempo) * * Input: unsigned updRate update rate in 100*Hz (eg. 50Hz * becomes 5000). * * Returns: MIDAS error code * \****************************************************************************/ int CALLING winwSetUpdRate(unsigned updRate) { /* Calculate number of elements to mix between two updates: (even) */ mixLeft = updateMix = ((unsigned) ((100L * (ulong) mixRate) / ((ulong) updRate)) + 1) & 0xFFFFFFFE; return OK; } /****************************************************************************\ * * Function: int winwStartPlay(void) * * Description: Prepares for playing - doesn't actually do anything here... * * Returns: MIDAS error code * \****************************************************************************/ int CALLING winwStartPlay(void) { return OK; } /****************************************************************************\ * * Function: int winwPlay(int *callMP); * * Description: Plays the sound - mixes the correct amount of data with DSM * and copies it to wave output buffer with post-processing. * Also takes care of sending fully mixed blocks to the wave * output device. * * Input: int *callMP pointer to music player calling flag * * Returns: MIDAS error code. If enough data was mixed for one updating * round and music player should be called, 1 is written to * *callMP, otherwise 0 is written there. Note that if music * player can be called, winwPlay() should be called again * with a new check for music playing to ensure the mixing buffer * gets filled with new data. * \****************************************************************************/ int CALLING winwPlay(int *callMP) { int error; MMRESULT mmError; unsigned blockLeft, numElems; unsigned dsmBufSize; unsigned oldPos; /* Calculate DSM mixing buffer size in elements: (FIXME) */ dsmBufSize = dsmMixBufferSize; #ifdef __32__ dsmBufSize >>= 2; #else dsmBufSize >>= 1; #endif if ( outputMode & sdStereo ) dsmBufSize >>= 1; /* Repeat while we have unused blocks left: */ while ( blockHeaders[blockNum]->dwFlags & WHDR_DONE ) { /* Check if the block is prepared - if so, unprepare it: */ if ( blockPrepared[blockNum] ) { if ( (mmError = waveOutUnprepareHeader(waveHandle, blockHeaders[blockNum], sizeof(WAVEHDR))) != 0 ) PASSWINERR(mmError, ID_winwPlay); blockPrepared[blockNum] = 0; } /* Calculate number of bytes of block left: */ blockLeft = blockLen - blockPos; /* Calculate number of mixing elements left: */ numElems = blockLeft / mixElemSize; /* Check that we won't mix more data than there is to the next update: */ if ( numElems > mixLeft ) numElems = mixLeft; /* Check that we won't mix more data than fits to DSM mixing buffer: */ if ( numElems > dsmBufSize ) numElems = dsmBufSize; /* Decrease number of elements before next update: */ mixLeft -= numElems; /* Mix the data to DSM mixing buffer: */ if ( (error = dsmMixData(numElems)) != OK ) PASSERROR(ID_winwPlay) /* Write the mixed data to output buffer: */ oldPos = blockPos; blockPos = postProc(numElems, blocks[blockNum], blockPos, dsmMixBuffer, ppTable); #ifdef DUMPBUFFER fwrite(&blocks[blockNum][oldPos], numElems * mixElemSize, 1, buff); #endif /* Check if the block is full - if so, write it to the wave output device and move to the next one: */ if ( blockPos >= blockLen ) { blockHeaders[blockNum]->dwFlags = 0; /* Reset wave header fields: */ blockHeaders[blockNum]->lpData = blocks[blockNum]; blockHeaders[blockNum]->dwBufferLength = blockLen; blockHeaders[blockNum]->dwFlags = 0; blockHeaders[blockNum]->dwLoops = 0; /* Prepare block header: */ if ( (mmError = waveOutPrepareHeader(waveHandle, blockHeaders[blockNum], sizeof(WAVEHDR))) != 0 ) PASSWINERR(mmError, ID_winwPlay); blockPrepared[blockNum] = 1; if ( (mmError = waveOutWrite(waveHandle, blockHeaders[blockNum], sizeof(WAVEHDR))) != 0 ) PASSWINERR(mmError, ID_winwPlay); //blockNum = (blockNum++) % numBlocks; blockPos = 0; blockNum++; if ( blockNum >= numBlocks ) blockNum = 0; } /* Check if the music player should be called: */ if ( mixLeft == 0 ) { mixLeft = updateMix; *callMP = 1; return OK; } } /* No more data fits to the mixing blocks - just return without update: */ *callMP = 0; return OK; } /* WinWave Sound Device structure: */ SoundDevice WinWave = { 0, /* tempoPoll = 0 */ sdUseMixRate | sdUseOutputMode | sdUseDSM, /* configBits */ 0, /* port */ 0, /* IRQ */ 0, /* DMA */ 1, /* cardType */ 1, /* numCardTypes */ sdMono | sdStereo | sd8bit | sd16bit, /* modes */ "Windows Wave Sound Device " WINWVERSTR, /* name */ &winwCardName, /* cardNames */ 0, /* numPortAddresses */ NULL, /* portAddresses */ &winwDetect, &winwInit, &winwClose, &dsmGetMixRate, &winwGetMode, &winwOpenChannels, &dsmCloseChannels, &dsmClearChannels, &dsmMute, &dsmPause, &dsmSetMasterVolume, &dsmGetMasterVolume, &winwSetAmplification, &winwGetAmplification, &dsmPlaySound, &dsmReleaseSound, &dsmStopSound, &dsmSetRate, &dsmGetRate, &dsmSetVolume, &dsmGetVolume, &dsmSetSample, &dsmGetSample, &dsmSetPosition, &dsmGetPosition, &dsmGetDirection, &dsmSetPanning, &dsmGetPanning, &dsmMuteChannel, &dsmAddSample, &dsmRemoveSample, &winwSetUpdRate, &winwStartPlay, &winwPlay #ifdef SUPPORTSTREAMS , &dsmStartStream, &dsmStopStream, &dsmSetLoopCallback, &dsmSetStreamWritePosition #endif }; /* * $Log: winwave.c,v $ * Revision 1.8 1997/01/16 18:41:59 pekangas * Changed copyright messages to Housemarque * * Revision 1.7 1997/01/16 18:28:40 pekangas * Added pointer to dsmSetStreamWritePosition * * Revision 1.6 1996/07/29 19:33:10 pekangas * Added a proper detection function * * Revision 1.5 1996/07/13 19:56:40 pekangas * Eliminated Visual C warnings * * Revision 1.4 1996/07/08 19:40:32 pekangas * Fixed winwClose() calling convention * * Revision 1.3 1996/05/26 20:57:00 pekangas * Added StartStream and EndStream to WinWave Sound Device structure * * Revision 1.2 1996/05/25 09:32:47 pekangas * Changed to use mBufferLength and mBufferBlocks * * Revision 1.1 1996/05/22 20:49:33 pekangas * Initial revision * */