450 lines
12 KiB
C
450 lines
12 KiB
C
|
#include <windows.h>
|
||
|
#define MMNOTIMER
|
||
|
#define MMNOSEQ
|
||
|
#define MMNOJOY
|
||
|
#define MMNOMIDI
|
||
|
#define MMNOMCI
|
||
|
#include "mmsystem.h"
|
||
|
#include "mmsysi.h" // to get DOUT() and _hread()
|
||
|
#include "playwav.h"
|
||
|
|
||
|
//
|
||
|
// These globals are used to keep track of the currently playing sound, and
|
||
|
// the handle to the wave device. only 1 sound can be playing at a time.
|
||
|
//
|
||
|
|
||
|
static HWAVEOUT hWaveOut; // handle to open wave device
|
||
|
LPWAVEHDR lpWavHdr; // current wave file playing
|
||
|
|
||
|
/* flags for _lseek */
|
||
|
#define SEEK_CUR 1
|
||
|
#define SEEK_END 2
|
||
|
#define SEEK_SET 0
|
||
|
|
||
|
#define FMEM (GMEM_MOVEABLE|GMEM_SHARE)
|
||
|
|
||
|
BOOL NEAR PASCAL soundInitWavHdr(LPWAVEHDR lpwh, LPCSTR lpMem, DWORD dwLen);
|
||
|
BOOL NEAR PASCAL soundOpen(HGLOBAL hSound, UINT wFlags);
|
||
|
BOOL NEAR PASCAL soundClose(void);
|
||
|
void NEAR PASCAL soundWait(void);
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* @doc INTERNAL
|
||
|
*
|
||
|
* @api void | WaveOutNotify | called by mmWndProc when it recives a
|
||
|
* MM_WOM_DONE message
|
||
|
* @rdesc None.
|
||
|
*
|
||
|
****************************************************************************/
|
||
|
|
||
|
void FAR PASCAL WaveOutNotify(WPARAM wParam, LPARAM lParam)
|
||
|
{
|
||
|
if (hWaveOut && !(lpWavHdr->dwFlags & WHDR_DONE))
|
||
|
return; // wave is not done! get out
|
||
|
|
||
|
//
|
||
|
// wave file is done! release the device
|
||
|
//
|
||
|
|
||
|
DOUT("MMSYSTEM: ASYNC sound done, closing wave device\r\n");
|
||
|
|
||
|
soundClose();
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* @doc INTERNAL
|
||
|
*
|
||
|
* @api BOOL | soundPlay | Pretty much speaks for itself!
|
||
|
*
|
||
|
* @parm HGLOBAL | hSound | The sound resource to play.
|
||
|
*
|
||
|
* @parm wFlags | UINT | flags controlling sync/async etc.
|
||
|
*
|
||
|
* @flag SND_SYNC | play synchronously (default)
|
||
|
* @flag SND_ASYNC | play asynchronously
|
||
|
*
|
||
|
* @rdesc Returns TRUE if successful and FALSE on failure.
|
||
|
****************************************************************************/
|
||
|
BOOL NEAR PASCAL soundPlay(HGLOBAL hSound, UINT wFlags)
|
||
|
{
|
||
|
//
|
||
|
// Before playing a sound release it
|
||
|
//
|
||
|
soundClose();
|
||
|
|
||
|
//
|
||
|
// open the sound device and write the sound to it.
|
||
|
//
|
||
|
if (!soundOpen(hSound, wFlags))
|
||
|
return FALSE;
|
||
|
|
||
|
if (!(wFlags & SND_ASYNC))
|
||
|
{
|
||
|
soundWait();
|
||
|
soundClose();
|
||
|
}
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* @doc INTERNAL
|
||
|
*
|
||
|
* @api BOOL | soundOpen | Open the wave device and write a sound to it.
|
||
|
*
|
||
|
* @parm HGLOBAL | hSound | The sound resource to play.
|
||
|
*
|
||
|
* @rdesc Returns TRUE if successful and FALSE on failure.
|
||
|
****************************************************************************/
|
||
|
BOOL NEAR PASCAL soundOpen(HGLOBAL hSound, UINT wFlags)
|
||
|
{
|
||
|
UINT wErr;
|
||
|
|
||
|
if (!hSound || !hwndNotify)
|
||
|
return FALSE;
|
||
|
|
||
|
if (hWaveOut)
|
||
|
{
|
||
|
DOUT("MMSYSTEM: soundOpen() wave device is currently open.\r\n");
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
lpWavHdr = (LPWAVEHDR)GlobalLock(hSound);
|
||
|
|
||
|
if (!lpWavHdr)
|
||
|
{
|
||
|
#ifdef DEBUG
|
||
|
if ((GlobalFlags(hSound) & GMEM_DISCARDED))
|
||
|
DOUT("MMSYSTEM: sound was discarded before play could begin.\r\n");
|
||
|
#endif
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// open the wave device, open any wave device that supports the
|
||
|
// format
|
||
|
//
|
||
|
wErr = waveOutOpen(&hWaveOut, // returns handle to device
|
||
|
(UINT)WAVE_MAPPER, // device id (any device)
|
||
|
(LPWAVEFORMAT)lpWavHdr->dwUser, // wave format
|
||
|
(DWORD)(UINT)hwndNotify, // callback function
|
||
|
0L, // callback instance data
|
||
|
WAVE_ALLOWSYNC | CALLBACK_WINDOW); // flags
|
||
|
|
||
|
if (wErr != 0)
|
||
|
{
|
||
|
DOUT("MMSYSTEM: soundOpen() unable to open wave device\r\n");
|
||
|
GlobalUnlock(hSound);
|
||
|
lpWavHdr = NULL;
|
||
|
hWaveOut = NULL;
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
wErr = waveOutPrepareHeader(hWaveOut, lpWavHdr, sizeof(WAVEHDR));
|
||
|
|
||
|
if (wErr != 0)
|
||
|
{
|
||
|
DOUT("MMSYSTEM: soundOpen() waveOutPrepare failed\r\n");
|
||
|
soundClose();
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Only allow sound looping if playing ASYNC sounds
|
||
|
//
|
||
|
if ((wFlags & SND_ASYNC) && (wFlags & SND_LOOP))
|
||
|
{
|
||
|
lpWavHdr->dwLoops = 0xFFFFFFFF; // infinite loop
|
||
|
lpWavHdr->dwFlags |= WHDR_BEGINLOOP|WHDR_ENDLOOP;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
lpWavHdr->dwLoops = 0;
|
||
|
lpWavHdr->dwFlags &=~(WHDR_BEGINLOOP|WHDR_ENDLOOP);
|
||
|
}
|
||
|
|
||
|
lpWavHdr->dwFlags &= ~WHDR_DONE; // mark as not done!
|
||
|
wErr = waveOutWrite(hWaveOut, lpWavHdr, sizeof(WAVEHDR));
|
||
|
|
||
|
if (wErr != 0)
|
||
|
{
|
||
|
DOUT("MMSYSTEM: soundOpen() waveOutWrite failed\r\n");
|
||
|
soundClose();
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* @doc INTERNAL
|
||
|
*
|
||
|
* @func BOOL | soundClose | This function closes the sound device
|
||
|
*
|
||
|
* @rdesc Returns TRUE if successful and FALSE on failure.
|
||
|
****************************************************************************/
|
||
|
BOOL NEAR PASCAL soundClose(void)
|
||
|
{
|
||
|
UINT wErr;
|
||
|
|
||
|
//
|
||
|
// Do we have the sound device open?
|
||
|
//
|
||
|
if (!lpWavHdr || !hWaveOut)
|
||
|
return TRUE;
|
||
|
|
||
|
//
|
||
|
// if the block is still playing, stop it!
|
||
|
//
|
||
|
if (!(lpWavHdr->dwFlags & WHDR_DONE))
|
||
|
waveOutReset(hWaveOut);
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
if (!(lpWavHdr->dwFlags & WHDR_DONE))
|
||
|
{
|
||
|
DOUT("MMSYSTEM: soundClose() data is not DONE!???\r\n");
|
||
|
lpWavHdr->dwFlags |= WHDR_DONE;
|
||
|
}
|
||
|
|
||
|
if (!(lpWavHdr->dwFlags & WHDR_PREPARED))
|
||
|
{
|
||
|
DOUT("MMSYSTEM: soundClose() data not prepared???\r\n");
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
//
|
||
|
// unprepare the data anyway!
|
||
|
//
|
||
|
wErr = waveOutUnprepareHeader(hWaveOut, lpWavHdr, sizeof(WAVEHDR));
|
||
|
|
||
|
if (wErr != 0)
|
||
|
{
|
||
|
DOUT("MMSYSTEM: soundClose() waveOutUnprepare failed?\r\n");
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// finaly actually close the device, and unlock the data
|
||
|
//
|
||
|
waveOutClose(hWaveOut);
|
||
|
GlobalUnlock((HGLOBAL)HIWORD(lpWavHdr));
|
||
|
|
||
|
//
|
||
|
// update globals, claiming the device is closed.
|
||
|
//
|
||
|
hWaveOut = NULL;
|
||
|
lpWavHdr = NULL;
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* @doc INTERNAL
|
||
|
*
|
||
|
* @api void | soundWait | wait for the sound device to complete
|
||
|
*
|
||
|
* @rdesc none
|
||
|
****************************************************************************/
|
||
|
void NEAR PASCAL soundWait(void)
|
||
|
{
|
||
|
if (lpWavHdr)
|
||
|
while (!(lpWavHdr->dwFlags & WHDR_DONE))
|
||
|
;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* @doc INTERNAL
|
||
|
*
|
||
|
* @api void | soundFree | This function frees a sound resource created
|
||
|
* with soundLoadFile or soundLoadMemory
|
||
|
*
|
||
|
* @rdesc Returns TRUE if successful and FALSE on failure.
|
||
|
****************************************************************************/
|
||
|
void NEAR PASCAL soundFree(HGLOBAL hSound)
|
||
|
{
|
||
|
if (!hSound)
|
||
|
return;
|
||
|
|
||
|
// !!! we should only close the sound device iff this hSound is playing!
|
||
|
//
|
||
|
soundClose();
|
||
|
GlobalFree(hSound);
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* @doc INTERNAL
|
||
|
*
|
||
|
* @api HGLOBAL | soundLoadFile | Loads a specified sound resource from a
|
||
|
* file into a global, discardable object.
|
||
|
*
|
||
|
* @parm LPCSTR | lpszFile | The file from which to load the sound resource.
|
||
|
*
|
||
|
* @rdesc Returns NULL on failure, GLOBAL HANDLE to a WAVEHDR iff success
|
||
|
****************************************************************************/
|
||
|
HGLOBAL NEAR PASCAL soundLoadFile(LPCSTR szFileName)
|
||
|
{
|
||
|
HFILE fh;
|
||
|
OFSTRUCT of;
|
||
|
DWORD dwSize;
|
||
|
LPSTR lpData;
|
||
|
HGLOBAL h;
|
||
|
UINT wNameLen;
|
||
|
|
||
|
// open the file
|
||
|
fh = OpenFile(szFileName, &of, OF_READ | OF_SHARE_DENY_NONE);
|
||
|
if (fh == HFILE_ERROR)
|
||
|
return NULL;
|
||
|
|
||
|
wNameLen = lstrlen(szFileName) + 1;
|
||
|
dwSize = _llseek(fh, 0l, SEEK_END); // get the size of file
|
||
|
_llseek(fh, 0l, SEEK_SET); // seek back to the start
|
||
|
|
||
|
// allocate some discardable memory for a wave hdr, name and the file data.
|
||
|
h = GlobalAlloc(FMEM+GMEM_DISCARDABLE, sizeof(WAVEHDR) + wNameLen + dwSize);
|
||
|
if (!h)
|
||
|
goto error1;
|
||
|
|
||
|
// lock it down
|
||
|
lpData = GlobalLock(h);
|
||
|
|
||
|
// read the file into the memory block
|
||
|
|
||
|
if (_hread(fh,lpData+sizeof(WAVEHDR)+wNameLen,(LONG)dwSize) != (LONG)dwSize)
|
||
|
goto error3;
|
||
|
|
||
|
// do the rest of it from the memory image
|
||
|
if (!soundInitWavHdr((LPWAVEHDR)lpData, lpData+sizeof(WAVEHDR)+wNameLen, dwSize))
|
||
|
goto error3;
|
||
|
|
||
|
_lclose(fh);
|
||
|
|
||
|
lstrcpy(lpData+sizeof(WAVEHDR), szFileName);
|
||
|
GlobalUnlock(h);
|
||
|
return h;
|
||
|
|
||
|
error3:
|
||
|
GlobalUnlock(h);
|
||
|
GlobalFree(h);
|
||
|
error1:
|
||
|
_lclose(fh);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* @doc INTERNAL
|
||
|
*
|
||
|
* @api HGLOBAL | soundLoadMemory | Loads a user specified sound resource from a
|
||
|
* a memory block supplied by the caller.
|
||
|
*
|
||
|
* @parm LPCSTR | lpMem | Pointer to a memory image of the file
|
||
|
*
|
||
|
* @rdesc Returns NULL on failure, GLOBAL HANDLE to a WAVEHDR iff success
|
||
|
****************************************************************************/
|
||
|
HGLOBAL NEAR PASCAL soundLoadMemory(LPCSTR lpMem)
|
||
|
{
|
||
|
HGLOBAL h;
|
||
|
LPSTR lp;
|
||
|
|
||
|
// allocate some memory, for a wave hdr
|
||
|
h = GlobalAlloc(FMEM, (LONG)sizeof(WAVEHDR)+1);
|
||
|
if (!h)
|
||
|
goto error1;
|
||
|
|
||
|
// lock it down
|
||
|
lp = GlobalLock(h);
|
||
|
|
||
|
//
|
||
|
// we must assume the memory pointer is correct! (hence the -1l)
|
||
|
//
|
||
|
if (!soundInitWavHdr((LPWAVEHDR)lp, lpMem, (DWORD)-1l))
|
||
|
goto error3;
|
||
|
|
||
|
lp[sizeof(WAVEHDR)] = (char)0; // No file name for memory file
|
||
|
GlobalUnlock(h);
|
||
|
return h;
|
||
|
|
||
|
error3:
|
||
|
GlobalUnlock(h);
|
||
|
GlobalFree(h);
|
||
|
error1:
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/*****************************************************************************
|
||
|
* @doc INTERNAL
|
||
|
*
|
||
|
* @api BOOL | soundInitWavHdr | Initializes a WAVEHDR data structure from a
|
||
|
* pointer to a memory image of a RIFF WAV file.
|
||
|
*
|
||
|
* @parm LPWAVHDR | lpwh | Pointer to a WAVEHDR
|
||
|
*
|
||
|
* @parm LPCSTR | lpMem | Pointer to a memory image of a RIFF WAV file
|
||
|
*
|
||
|
* @rdesc Returns FALSE on failure, TRUE on success.
|
||
|
*
|
||
|
* @comm the dwUser field of the WAVEHDR structure is initialized to point
|
||
|
* to the WAVEFORMAT structure that is inside the RIFF data
|
||
|
*
|
||
|
****************************************************************************/
|
||
|
BOOL NEAR PASCAL soundInitWavHdr(LPWAVEHDR lpwh, LPCSTR lpMem, DWORD dwLen)
|
||
|
{
|
||
|
FPFileHeader fpHead;
|
||
|
LPWAVEFORMAT lpFmt;
|
||
|
LPCSTR lpData;
|
||
|
DWORD dwFileSize,dwCurPos;
|
||
|
DWORD dwSize;
|
||
|
|
||
|
if (dwLen < sizeof(FileHeader))
|
||
|
return FALSE;
|
||
|
|
||
|
// assume the first few bytes are the file header
|
||
|
fpHead = (FPFileHeader) lpMem;
|
||
|
|
||
|
// check that it's a valid RIFF file and a valid WAVE form.
|
||
|
if (fpHead->dwRiff != RIFF_FILE || fpHead->dwWave != RIFF_WAVE ) {
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
dwFileSize = fpHead->dwSize;
|
||
|
dwCurPos = sizeof(FileHeader);
|
||
|
lpData = lpMem + sizeof(FileHeader);
|
||
|
|
||
|
if (dwLen < dwFileSize) // RIFF header
|
||
|
return FALSE;
|
||
|
|
||
|
// scan until we find the 'fmt' chunk
|
||
|
while( 1 ) {
|
||
|
if( ((FPChunkHeader)lpData)->dwCKID == RIFF_FORMAT )
|
||
|
break; // from the while loop that's looking for it
|
||
|
dwCurPos += ((FPChunkHeader)lpData)->dwSize + sizeof(ChunkHeader);
|
||
|
if( dwCurPos >= dwFileSize )
|
||
|
return FALSE;
|
||
|
lpData += ((FPChunkHeader)lpData)->dwSize + sizeof(ChunkHeader);
|
||
|
}
|
||
|
|
||
|
// now we're at the beginning of the 'fmt' chunk data
|
||
|
lpFmt = (LPWAVEFORMAT) (lpData + sizeof(ChunkHeader));
|
||
|
|
||
|
// scan until we find the 'data' chunk
|
||
|
lpData = lpData + ((FPChunkHeader)lpData)->dwSize + sizeof(ChunkHeader);
|
||
|
while( 1 ) {
|
||
|
if( ((FPChunkHeader)lpData)->dwCKID == RIFF_CHANNEL)
|
||
|
break; // from the while loop that's looking for it
|
||
|
dwCurPos += ((FPChunkHeader)lpData)->dwSize + sizeof(ChunkHeader);
|
||
|
if( dwCurPos >= dwFileSize )
|
||
|
return NULL;
|
||
|
lpData += ((FPChunkHeader)lpData)->dwSize + sizeof(ChunkHeader);
|
||
|
}
|
||
|
|
||
|
// now we're at the beginning of the 'data' chunk data
|
||
|
dwSize = ((FPChunkHeader)lpData)->dwSize;
|
||
|
lpData = lpData + sizeof(ChunkHeader);
|
||
|
|
||
|
// initialize the WAVEHDR
|
||
|
|
||
|
lpwh->lpData = (LPSTR)lpData; // pointer to locked data buffer
|
||
|
lpwh->dwBufferLength = dwSize; // length of data buffer
|
||
|
lpwh->dwUser = (DWORD)lpFmt; // for client's use
|
||
|
lpwh->dwFlags = WHDR_DONE; // assorted flags (see defines)
|
||
|
lpwh->dwLoops = 0;
|
||
|
|
||
|
return TRUE;
|
||
|
}
|