Writing Waveform Data

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.

Example of Writing Waveform Data

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;
    }
    .
    .
    .
}

PCM Waveform Data Format

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:

Using Window Messages to Manage Waveform Playback

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);
}

Using a Low-Level Callback to Manage Waveform Playback

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.