After successfully opening a waveform output device driver, you can begin waveform playback. The Multimedia extensions provide the following function for sending data blocks to waveform output devices:
waveOutWrite
Writes a data block to a waveform output device.
Use the WAVEHDR data structure to specify the waveform data block you are sending using waveOutWrite. This structure contains a pointer to a locked data block, the length of the data block, and some assorted flags. The MMSYSTEM.H file defines the WAVEHDR data structure as follows:
typedef struct wavehdr_tag {
LPSTR lpData; /* pointer to data block */
DWORD dwBufferLength; /* length of data block */
DWORD dwBytesRecorded; /* number of bytes recorded */
DWORD dwUser; /* user instance data */
DWORD dwFlags; /* assorted flags */
DWORD dwLoops; /* loop control counter */
struct wavehdr_tag far *lpNext; /* private to driver */
DWORD reserved; /* private to driver */
} WAVEHDR;
After you send a data block to an output device using waveOutWrite, you must wait until the device driver is finished with the data block before freeing it. If you are sending multiple data blocks, you must monitor the completion of data blocks to know when to send additional blocks. For details on different techniques for monitoring data block completion, see “Managing Audio Data Blocks,” earlier in this chapter.
The following code fragment illustrates the steps required to allocate and set up a WAVEHDR data structure, and write a block of data to a waveform output device.
/* Global variables--Must be visible to window-procedure function so it
* can unlock and free the data block after it has been played.
*/
HANDLE hData = NULL; // handle to waveform data memory
HPSTR lpData = NULL; // pointer to waveform data memory
void ReversePlay()
{
HWAVEOUT hWaveOut;
HWAVEHDR hWaveHdr;
LPWAVEHDR lpWaveHdr;
HMMIO hmmio;
MMCKINFO mmckinfoParent;
MMCKINFO mmckinfoSubchunk;
WORD wResult;
HANDLE hFormat;
WAVEFORMAT *pFormat;
DWORD dwDataSize;
WORD wBlockSize;
.
.
.
/* Open a waveform device for output using window callback.
*/
if (waveOutOpen((LPHWAVEOUT)&hWaveOut, WAVE_MAPPER,
(LPWAVEFORMAT)pFormat,
(LONG)hwndApp, 0L, CALLBACK_WINDOW))
{
MessageBox(hwndApp, "Failed to open waveform output device.",
NULL, MB_OK | MB_ICONEXCLAMATION);
LocalUnlock(hFormat);
LocalFree(hFormat);
mmioClose(hmmio, 0);
return;
}
/* Allocate and lock memory for the waveform data. The memory for
* waveform data must be globally allocated with GMEM_MOVEABLE and
* GMEM_SHARE flags.
*/
hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, dwDataSize );
if (!hData)
{
MessageBox(hwndApp, "Out of memory.",
NULL, MB_OK | MB_ICONEXCLAMATION);
mmioClose(hmmio, 0);
return;
}
lpData = GlobalLock(hData);
if (!lpData)
{
MessageBox(hwndApp, "Failed to lock memory for data chunk.",
NULL, MB_OK | MB_ICONEXCLAMATION);
GlobalFree(hData);
mmioClose(hmmio, 0);
return;
}
/* Read the waveform data subchunk.
*/
if(mmioRead(hmmio, (HPSTR) lpData, dwDataSize) != dwDataSize)
{
MessageBox(hwndApp, "Failed to read data chunk.",
NULL, MB_OK | MB_ICONEXCLAMATION);
GlobalUnlock(hData);
GlobalFree(hData);
mmioClose(hmmio, 0);
return;
}
/* Allocate and lock memory for the header. This memory must also be
* globally allocated with GMEM_MOVEABLE and GMEM_SHARE flags.
*/
hWaveHdr = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE,
(DWORD) sizeof(WAVEHDR));
if (!hWaveHdr)
{
GlobalUnlock(hData);
GlobalFree(hData);
MessageBox(hwndApp, "Not enough memory for header.",
NULL, MB_OK | MB_ICONEXCLAMATION);
return;
}
lpWaveHdr = (LPWAVEHDR) GlobalLock(hWaveHdr);
if (!lpWaveHdr)
{
GlobalUnlock(hData);
GlobalFree(hData);
MessageBox(hwndApp, "Failed to lock memory for header.",
NULL, MB_OK | MB_ICONEXCLAMATION);
return;
}
/* After allocation, the header must be set up and prepared for use.
*/
lpWaveHdr->lpData = lpData;
lpWaveHdr->dwBufferLength = dwDataSize;
lpWaveHdr->dwFlags = 0L;
lpWaveHdr->dwLoops = 0L;
waveOutPrepareHeader(hWaveOut, lpWaveHdr, sizeof(WAVEHDR));
/* Then the data block can be sent to the output device. The
* waveOutWrite function returns immediately and waveform data
* is sent to the output device in the background.
*/
wResult = waveOutWrite(hWaveOut, lpWaveHdr, sizeof(WAVEHDR));
if (wResult != 0)
{
waveOutUnprepareHeader(hWaveOut, lpWaveHdr, sizeof(WAVEHDR));
GlobalUnlock( hData);
GlobalFree(hData);
MessageBox(hwndApp, "Failed to write block to device",
NULL, MB_OK | MB_ICONEXCLAMATION);
return;
}
.
.
.
}
The lpData field in the WAVEHDR structure points to the waveform data samples. For 8-bit PCM data, each sample is represented by a single unsigned data byte. For 16-bit PCM data, each sample is represented by a 16-bit signed value. The following table summarizes the maximum, minimum, and midpoint values for PCM waveform data.
Data Format | Maximum Value | Minimum Value | Midpoint Value |
8-bit PCM | 255 (0xFF) | 0 | 128 (0x80) |
16-bit PCM | 32767 (0x7FFF) | –32768 (0x8000) | 0 |
Summary: PCM Data Packing
The order of the data bytes varies between 8-bit and 16-bit, and mono and stereo formats. The following illustrations show the data packing for different PCM waveform data formats:
The following messages can be sent to a window-procedure function for managing waveform playback:
Message | Description |
MM_WOM_CLOSE | Sent when the device is closed using waveOutClose. |
MM_WOM_DONE | Sent when the device driver is finished with a data block sent using waveOutWrite. |
MM_WOM_OPEN | Sent when the device is opened using waveOutOpen. |
A wParam and lParam parameter is associated with each of these messages. The wParam parameter always specifies a handle to the open waveform device. For the MM_WOM_DONE message, lParam specifies a far pointer to a WAVEHDR structure identifying the completed data block. The lParam parameter is unused for the MM_WOM_CLOSE and MM_WOM_OPEN messages.
The most useful message is MM_WOM_DONE. When this message signals that playback of a data block is complete, you can clean up and free the data block. Unless you need to allocate memory or initialize variables, you probably don't need to process the MM_WOM_OPEN and MM_WOM_CLOSE messages.
Summary: Example of Processing MM_WOM_DONE
The following code fragment shows how to process the MM_WOM_DONE message. This fragment assumes the application does not play multiple data blocks, so it can close the output device after playing a single data block.
/* WndProc--Main window procedure function.
*/
LONG FAR PASCAL WndProc(HWND hWnd, unsigned msg, WORD wParam, LONG lParam)
{
switch (msg)
{
.
.
.
case MM_WOM_DONE:
/* A waveform data block has been played and can now be freed.
*/
waveOutUnprepareHeader((HWAVEOUT) wParam,
(LPWAVEHDR) lParam, sizeof(WAVEHDR) );
GlobalUnlock(hData);
GlobalFree(hData);
waveOutClose((HWAVEOUT) wParam);
break;
}
return DefWindowProc(hWnd,msg,wParam,lParam);
}
The syntax of the low-level callback function for waveform output devices is as follows:
void FAR PASCAL waveOutCallback(hWaveOut, wMsg, dwInstance, dwParam1, dwParam2)
The following messages can be sent to the wMsg parameter of waveform output callback functions:
Message | Description |
WOM_CLOSE | Sent when the device is closed using waveOutClose. |
WOM_OPEN | Sent when the device is opened using waveOutOpen. |
WOM_DONE | Sent when the device driver is finished with a data block sent using waveOutWrite. |
These messages are similar to the messages sent to window-procedure functions, however, the parameters are different. A handle to the open waveform device is passed as a parameter to the callback, along with the DWORD of instance data that was passed using waveOutOpen.
Summary: Message-Dependent Parameters
The callback has two message-dependent parameters: dwParam1 and
dwParam2. For the WOM_DONE message, dwParam1 specifies a far pointer
to a WAVEHDR structure identifying the completed data block and dwParam2 is not used. For the WOM_OPEN and WOM_CLOSE messages, neither of the parameters are used.
After the driver is finished with a data block, you can clean up and free the data block, as described in “Allocating and Preparing Audio Data Blocks,” earlier in this chapter. Because of the restrictions of low-level audio callbacks, you can't do this within the callback—you must do this work outside of the callback.