Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

CWaveBox - WAI wrapper for playing PCM multiwaves, useful in game development

0.00/5 (No votes)
15 Apr 2005 2  
Multiwave player ('Waveform Audio Interface' PCM wave wrapper).

Sample Image

Introduction

Have you ever desired to play Waves in your Windows application or game without being lost with SDK / MFC anomalies which give you only a part of something, which is in fact useless if you're going to do some serious work? Here, I'll collect all needed parts and upgrade it to the higher level so you can think about more serious work with sound.

The CWaveBox class relies on the SDK 'Waveform Audio Interface' API to play / produce (PCM uncompressed) multiwaves at once. Mixing and timing of multiple waves is required in games for adding interactive special/dimensional sound effects into. This class enables you possibility for that through the Windows WAI driver, by wrapping it with only one worker thread for multiwave support. I've seen a lots of examples about this topic concentrated about queuing / streaming WAI with two or more Wave data blocks continuously to avoid gap when switching, but few or just one of them only has properly freed / closed WAI interface which prevents unstoppable waste of memory if not anything else. Memory waste / leak of that (or any other) type is unknown with CWaveBox. Playing performance is enhanced by caching Wave and playing from RAM, so, this class isn't for 'classic players' from file, which costs additional time for 'on the play' caching when CPU time is not critical. If you want to, feel free and recode it for such a thing, I won't be mad ;-). This class if fully portable to Windows CE, so you can play within at Pocket PC also.

Background

Whole idea for coding this class came from 'need for a sound' better than that from 'PlaySound', 'sndPlaySound' or any other partial solution. This class is already implemented in one board game, with great AI, for Pocket PC (which my friend Vrx and I have coded). Special thanks to people who have been translating the game tutorial into eight world languages, and some of them have 'stuck into translation' ;-). Here is a great tutorial at this topic: Using the Windows waveOut Interface.

Special thanks to the guy who wrote and coded the tutorial and WinAmp waveOut plug-in. I hope he wouldn't criticize me, because I have borrowed a few functions (allocateBlocks, freeBlocks and waveOutProc) from his tutorial / code and some of the variables notation. I'm a lazy ass. ;-).

All Waves mixed in the demo are downloaded from here.

Code, brief overview:

The CWaveBox class is structured with the WAVE model which is used for storing wave data, size, wfx (format info) loaded by the Load method and Wave message WMSG set by Play method, to provide INTERFACE model with all required information and serves PlayThread as sub interface to the WAI driver in order to successfully establish the WAI instance and play for every Wave.

WaveBox.h

// WaveBox.h: interface for the CWave class.

//

//////////////////////////////////////////////////////////////////////


#if !defined(AFX_WAVEBOX_H__DE24CFE1_7501_4DA3_AF18_667A845AAE49__INCLUDED_)
#define AFX_WAVEBOX_H__DE24CFE1_7501_4DA3_AF18_667A845AAE49__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000



/////////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////////

////

////                                                               by Zen '05

////

////                                    WaveBox class v0.95

////                                  ~~~~~~~~~~~~~~~~~~~~~~

////                                  ( PCM multiwave player )

////                                        play & joy

////

////

////    

////


/// precompiler

#include <windows.h>

#include <mmsystem.h>


/// wave & PCM marks

#define WAVE_FILE_MARK          "RIFF"
#define WAVE_HEAD_MARK          "WAVEfmt "
#define WAVE_DATA_MARK          "data"
#define WAVE_PCM_16             16
#define WAVE_PCM_1               1

/// wfx header offsets

#define OFFSET_FILE_LEFT         4
#define OFFSET_HEAD_MARK         8
#define OFFSET_WAVE_PCM1        16
#define OFFSET_WAVE_PCM2        20
#define OFFSET_CHANNELS         22
#define OFFSET_SAMPLESPERSEC    24
#define OFFSET_AVGBYTESPERSEC   28
#define OFFSET_BLOCKALIGN       32
#define OFFSET_BITSPERSAMPLE    34
#define OFFSET_DATA_MARK        36
#define OFFSET_DATA_SIZE        40
#define OFFSET_WAVEDATA         44
#define HEADER_SIZE             OFFSET_WAVEDATA
#define EOF_EXTRA_INFO          60


/// messages

typedef unsigned int            WMsg;   // wave messages

typedef unsigned int            TMsg;   // thread messages


#define WMSG_WAIT               0       // wave wait

#define WMSG_START              1       // wave play

#define TMSG_ALIVE              1       // thread alive

#define TMSG_CLOSE              0       // thread close

#define INT_FREE                0       // interface free

#define INT_USED                1       // interface used

#define THREAD_EXIT     0xABECEDA       // thread exit code


/// performance predefines

#define SUPPORT_WAVES          10       // predefined wave count

#define SUPPORT_INTERFACES     10       // predefined interface count


/// buffering

#define READ_BLOCK           8192       // read wave ( file ) block size

#define BLOCK_SIZE           8192       // queue block size

#define BLOCK_COUNT            20       // queue block count

#define BP_TURN                 1       // blocks per turn



static  CRITICAL_SECTION        cs;     // critical section


static unsigned int __stdcall 
             PlayThread( LPVOID lp );   // main play thread

static void CALLBACK waveOutProc(  HWAVEOUT  hWaveOut, // waveOut prototype

                                   UINT      uMsg, 
                                   DWORD     dwInstance,  
                                   DWORD     dwParam1,    
                                   DWORD     dwParam2);
class CWaveBox 
{
    struct WAVE
    {
        char            *data;      /// wave    

        unsigned long    size;      /// size

        WAVEFORMATEX     wfx;       /// wfx

        WMsg             WMSG;      /// { 0,1 } wait / play

    };
    
    struct INTERFACE
    {
        /// interface

        HWAVEOUT        dev;        /// device handle

        unsigned int    state;      /// { 0,1 } free / used


        /// wave

        WAVE           *wave;       /// current wave


        /// wave interface

        unsigned long   wpos;       /// current play position

        WAVEHDR*        wblock;     /// wave block

        volatile int    wfreeblock; /// free blocks left

        int             wcurrblock; /// current block

    };

public:
    
    /// members

    INTERFACE       I[SUPPORT_INTERFACES]; // interface(s)

    WAVE            W[SUPPORT_WAVES];      // wave(s)

    unsigned int    wload;  // current wave(s) loaded

    TMsg            TMSG;   // thread msg { 1,0 } alive / close


    /// prototypes 

    int Load( TCHAR       *file );   // load wave into WaveBox

    int Play( unsigned int wave );
    // play n wave from WaveBox ( starts play thread )


             CWaveBox();  // play thread created suspended

    virtual ~CWaveBox();  // play thread terminated


    /// waveOut open & close

    int AddInterface( HWAVEOUT      *dev, /// push wave wfx to interface

                      WAVEFORMATEX  *wfx,
                      volatile int  *wfreeblock );      
    int RemoveInterface( HWAVEOUT dev );
    /// pull wave wfx from interface


protected:
    
    /// thread

    HANDLE          thread;   /// play thread handle

    unsigned int    run;      /// suspended / resumed


    /// prototypes


    /// alloc heap for wave header blocks

    WAVEHDR*    allocateBlocks( int size, int count );
    /// free heap

    void        freeBlocks( WAVEHDR* blockArray );
};

#endif // !defined(AFX_WAVEBOX_H__DE24CFE1_7501_4DA3_AF18_667A845AAE49__INCLUDED_)

CWaveBox()

In the constructor, PlayThread is created in suspended mode, so will be resumed at first play. All supported interfaces are set to initial state and Wave block headers are created at heap (queuing blocks). A LPVOID argument class instance is passed so PlayThread can easily access any of the public methods/members inside the class.

CWaveBox::CWaveBox()
{ 
    // init wave(s) counter

    wload   = 0;

    // thread suspended < used for resuming thread at first play >

    run     = 0;

    // create suspended player thread

    thread  = CreateThread(  NULL,
                             0,
                             (LPTHREAD_START_ROUTINE)PlayThread,
                             (LPVOID)this,
                             CREATE_SUSPENDED,
                             NULL  );
    

    // alloc mem for interface(s)

    for( unsigned int i = 0; i < SUPPORT_INTERFACES; i++ )
    {
        I[i].wblock     = allocateBlocks( BLOCK_SIZE, BLOCK_COUNT );
        I[i].wfreeblock = BLOCK_COUNT;
        I[i].wcurrblock = 0;
        I[i].state      = INT_FREE;
        I[i].wpos       = 0;
    }
    
    // init msg

    for( i = 0; i < SUPPORT_WAVES; i++ )    W[i].WMSG = WMSG_WAIT;

    // init cs

    InitializeCriticalSection( &cs );
}

Load

First of all, Wave header is read from file and must pass several verifications to satisfy the 'PCM wave' model. Then, Wave wfx structure is filled and the whole data block is loaded into memory.

Note: Load all waves (you want to play) before starting the Play method, because there is no additional Load supported if PlayThread is resumed by starting at first Play. (See wload, it's also used in PlayThread and is not protected by critical section.)

int CWaveBox::Load( TCHAR *file )
{
    if( wload == SUPPORT_WAVES )
        return -1;

    HANDLE hFile;

    // open file

    if((hFile = CreateFile( file,
                            GENERIC_READ,
                            FILE_SHARE_READ,
                            NULL,
                            OPEN_EXISTING,
                            0,
                            NULL )) == INVALID_HANDLE_VALUE)    return -1;

    
    // read wave header

    char            header[HEADER_SIZE];
    unsigned long   rbytes  = 0;
    
    if( !ReadFile(hFile, header, sizeof(header), &rbytes, NULL) )
    { CloseHandle(hFile);   return -1; }

    if( !rbytes || rbytes < sizeof(header) )
    { CloseHandle(hFile);   return -1; }

    /// check if this is a wave file

    if( strncmp( header, WAVE_FILE_MARK, strlen( WAVE_FILE_MARK )) )
    { CloseHandle(hFile);   return -1; }

    if( strncmp( header + OFFSET_HEAD_MARK, 
        WAVE_HEAD_MARK, strlen( WAVE_HEAD_MARK )) )
    { CloseHandle(hFile);   return -1; }

    /// check if wave is uncompressed PCM format

    if (    ((*(DWORD*)(header + OFFSET_WAVE_PCM1)) != WAVE_PCM_16 )
         || ((*(WORD *)(header + OFFSET_WAVE_PCM2)) != WAVE_PCM_1  ))
    {CloseHandle(hFile);     return -1; }

    /// check for 'data' mark

    if( !strncmp( header + OFFSET_DATA_MARK, 
                  WAVE_DATA_MARK, strlen( WAVE_DATA_MARK )) )
        W[wload].size = *((DWORD*)(header + OFFSET_DATA_SIZE ));
        /* size of data */  
    else
    {   /// if data block size cant be read

        /// try to predict data block without extra info

        /// this is unusualy case

        W[wload].size  = *((DWORD*)(header + OFFSET_FILE_LEFT ));  
        W[wload].size -=  ( HEADER_SIZE - EOF_EXTRA_INFO );
        /* size of data */ 
    }

    // fill WAVEFORMATEX from wave header

    W[wload].wfx.nSamplesPerSec  = 
       *((DWORD*)(header + OFFSET_SAMPLESPERSEC )); /* sample rate */
    W[wload].wfx.wBitsPerSample  = 
       *((WORD *)(header + OFFSET_BITSPERSAMPLE )); /* sample size */
    W[wload].wfx.nChannels       = 
       *((WORD *)(header + OFFSET_CHANNELS      )); /* channels    */
    W[wload].wfx.cbSize          = 0;  /* size of _extra_ info */
    W[wload].wfx.wFormatTag      = WAVE_FORMAT_PCM;
    W[wload].wfx.nBlockAlign     = 
       *((WORD *)(header + OFFSET_BLOCKALIGN    ));
    W[wload].wfx.nAvgBytesPerSec = 
       *((DWORD*)(header + OFFSET_AVGBYTESPERSEC));

    // get mem for wave data block

    if((W[wload].data = ( char *) calloc( W[wload].size, 
                                  sizeof( char ))) == NULL)
    { CloseHandle(hFile); return -1; }
 
    char            buffer[READ_BLOCK];
    
    unsigned long   size         = W[wload].size; 
    unsigned long   read_block   = 0;
                    rbytes       = 0;
                      
    do  /// copy uncompressed PCM wave data block

    {
        if( ( size -= rbytes ) >= READ_BLOCK )  read_block = READ_BLOCK;
        else
        if( size && size < READ_BLOCK )         read_block = size;
        else                                    break;

        if( !ReadFile(hFile, buffer, read_block, &rbytes, NULL) )
            break;
        if( rbytes == 0 )
            break;
        if( rbytes < sizeof(buffer) ) 
            memset(buffer + rbytes, 0, sizeof(buffer) - rbytes);
        
        memcpy( &W[wload].data[W[wload].size - size], buffer, rbytes );         

    }while( 1 );    

    // close file handle

    CloseHandle(hFile);
    
    // return current wave count

    return ++wload;
}

Play

Play method is pretty simple, all what it's doing is to set WMSG_START for every wave index which is passed as an argument so PlayThread can start work. Only when first Play is started, PlayThread will be resumed and thread message TMSG_ALIVE set.

int CWaveBox::Play( unsigned int wave )
{
    // check wave id

    if( wave < 0 || wave >= wload )
        return -1;
    
    // set play message

    EnterCriticalSection(&cs);
    W[wave].WMSG = WMSG_START;
    LeaveCriticalSection(&cs);

    // resume thread < at first play >

    if( !run ){ run = 1; TMSG = TMSG_ALIVE; ResumeThread( thread ); }
    
    return 1;
}

Queue Model (streaming multiple waves)

The PlayThread model works in order to serve as a WAI server for preparing and sending as many buffers of BLOCK_SIZE to the current WAI queue, as is limited with BLOCK_COUNT constant. There can be SUPPORT_INTERFACES queues, or WAI driver threads working. For example, if Wave of 1 second is played 10 times with 10ms of delay between each Play, PlayThread will link Wave with 10 different INTERFACEs in order to play the Wave 10 times. Each thread INTERFACE serves each WAI queue/thread required for playing current Wave linked with current INTERFACE. So, eventually effect will be new delayed Wave of approx. 1.1 seconds consisting of 10 Waves with latency of 10ms between each.

To provide continuous and synchronous buffering without noise or gap as side effects, the idea is to control buffers queuing / streaming. When WAVE is linked to the thread INTERFACE it must be linked with the WAI interface or the device for playing (see HWAVEOUT). By calling AddInterface method, pointer of INTERFACE's wfreeblock counter is passed as an argument so it will be returned in callback waveOutProc, when buffer is played from the WAI queue, or WOM_DONE message arrives. By decreasing wfreeblock counter in case of buffer is sent to the WAI queue for playing, and increasing inside of waveOutProc when buffer is finished / played, keeping of limited BLOCK_COUNT queued buffers is simply controlled. If any of the wfreeblock buffers is freed, a new one is sent to the queue, so audio streaming will be continuous and synchronous over all INTERFACEs. You can turn on PlayThread to its limits by playing n SUPPORT_WAVES at m SUPPORT_INTERFACES inside of other CPU time spending processes (for ex., GUI related). I'm sure that will satisfy any of the proper coded tasks and play without gap for a reasonably count of waves / interfaces at once.

/// performance predefines

#define SUPPORT_WAVES          10       // predefined wave count

#define SUPPORT_INTERFACES     10       // predefined interface count


/// buffering

#define READ_BLOCK           8192       // read wave file block

#define BLOCK_SIZE           8192       // queue block size

#define BLOCK_COUNT            20       // queue block count

#define BP_TURN                 1       // blocks per turn



/// AddInterface is wrapper method around WAI API waveOutOpen


int CWaveBox::AddInterface( HWAVEOUT    *dev, 
                         WAVEFORMATEX   *wfx, 
                         volatile int   *wfreeblock )
{
    // check for free device

    if( !waveOutGetNumDevs() )
        return -1;
    
    // try to open the default wave device. WAVE_MAPPER is

    // a constant defined in mmsystem.h, it always points to the

    // default wave device on the system (some people have 2 or

    // more sound cards).

    
    if(waveOutOpen( dev,
                    WAVE_MAPPER, 
                    wfx, 
                    (DWORD)waveOutProc, 
                    (DWORD)wfreeblock, 
                    CALLBACK_FUNCTION   ) != MMSYSERR_NOERROR ) return -1;
    
    return 1;
}

/// waveOutProc is CALLBACK function

/// and serve as a message queue of WAI where all

/// devices HWAVEOUT ( threads ) notifies

/// when any of action / message occurs


static void CALLBACK waveOutProc( HWAVEOUT  hWaveOut, 
                                  UINT      uMsg, 
                                  DWORD     dwInstance,  
                                  DWORD     dwParam1,    
                                  DWORD     dwParam2     )
{
    // pointer to free block counter

    int* freeBlockCounter = (int*)dwInstance;
  
    // ignore calls that occur due to openining and closing the device.

    if(uMsg != WOM_DONE)
        return;

    // increase free block counter

    EnterCriticalSection(&cs);
    (*freeBlockCounter)++;
    LeaveCriticalSection(&cs);
}

PlayThread

You should read the 'Queue Model' paragraph if you have skipped it, because it is an intro for you to completely understand how PlayThread is modelled to interact with the WAI driver and play multiple Waves. The PlayThread responses for two types of messages. First types are Wave WMsg messages set by Play method, so let's first discuss what they trigger.

When WMSG_START arrives for a particularl wave wb->W[i];, the thread continues searching for the first INT_FREE interface to link the Wave within. When free interface is matched and HWAVEOUT handle of device is opened by AddInterface method and assigned to the current interface, WAVE pointer is saved so interface can work with. Then, interface is marked as INT_USED, so you'll see later, the thread will know to play it and current attached Waves. wb->W[i].WMSG; message is set back to WMSG_WAIT state, so if requested, it can be played again at same or another INTERFACE. This linking model gives the CWaveBox possibility for playing one Wave as well as for playing multiple Waves.

Now, it goes to the 'main playing loop' scope, where, first, for every INT_USED interface wb->I[k].wfreeblock; counter is checked to see if there is a free block for queuing on the current interface. If not, loop continues to search first one where it has. Blocks per turn, or BP_TURN constant is nothing but the multiplier for BLOCK_SIZE and is used for queuing more than one block per turn. You can avoid this by enlarging BLOCK_SIZE. I really don't know if it is better queuing one bigger or many smaller blocks per turn, so this can be further tested. ;-)

Inside the 'block per turn' loop, first, we check for Wave data header WAVEHDR block to be unprepared in case of previous queuing (ring queuing / buffering) and complete the WAVEHDR block by copying Wave data block at Wave header member pData and set the size of block in dwBufferLength. After preparations, header is sent for playing at current interface wb->I[k].dev; via waveOutWrite WAI method. Remaining job is to decrease interface wfreeblock counter and to round up circular wcurrblock counter so it will point on the next block.

In case the last block is queued and there is no data left for queuing on current INTERFACE, it again goes to check for wfreeblock counter, but this time it must be equal to BLOCK_COUNT which means that all buffers are played so all prepared buffers can be successfully unprepared and RemoveInterface method invoked which closes current WAI driver thread and releases all memory used. Closing of WAI interfaces must be done in these steps or may fail and produce memory leak! Remaining job is to set the interface wb->I[k].state; message to INT_FREE so it can link another WAVE if needed.

Another type of messages are thread TMsg messages. First message TMSG_ALIVE is set by Play method and keeps the thread alive until TMSG_CLOSE is set from the CWaveBox destructor. When TMSG_CLOSE is arrived, thread breaks from the 'main thread' or while loop and continues to the bottom of PlayThread. Destructor can be invoked while there is 'who knows how many waves' still playing at WAI subsystem, so there is a last check to see on which interface is state flag set to INT_USED so it can be reset with waveOutReset and closed by RemoveInterface method. Without releasing of WAI subsystem, there can be problems (primarily Windows CE) on next WAI start-up so this procedure is necessary.

And, last thing to do, is to return EXIT_THREAD code, so it will signalize the destructor to continue with the destruction of the remaining CWaveBox members. ;-)

static unsigned int __stdcall PlayThread( LPVOID lp )
{
    /// get the class instance

    CWaveBox *wb = ( CWaveBox *)lp;
    
    /// pooling variables < most frequently used / checked >

    register    WMsg            wmsg  = WMSG_WAIT;
    register    TMsg            tmsg  = TMSG_ALIVE;         
    register    unsigned int    i     = 0;
    

    /// thread life cycle

    while( tmsg )
    {

        /// check for 'play' msg

        for( i = 0; i < wb->wload; i++ )
        {
            /// read msg

            EnterCriticalSection( &cs );
            wmsg = wb->W[i].WMSG;
            LeaveCriticalSection( &cs );
            
            /// wave to play?

            if( wmsg == WMSG_START )    break;
        }
        
        
        /// playable wave

        if( wmsg == WMSG_START )
        
            /// link with first free interface

            for( unsigned int j = 0; j < SUPPORT_INTERFACES; j++ )
            
                /// check for free interface

                if( wb->I[j].state == INT_FREE )
    
                    /// attach wave to interface

                    if( wb->AddInterface( &wb->I[j].dev, 
                                          &wb->W[i].wfx, 
                                          &wb->I[j].wfreeblock ) )
                    {
                        /// get wave pointer

                        wb->I[j].wave = &wb->W[i];
                
                        /// mark interface as used

                        wb->I[j].state = INT_USED;

                        /// free wave 

                        EnterCriticalSection( &cs );
                        wb->W[i].WMSG = WMSG_WAIT;  
                        LeaveCriticalSection( &cs );                        

                        /// leave loop

                        break;
                    }


        ///////////////////////////////////////////////////////////////////////

        ///////////////////////////////////////////////////////////////////////

        ///

        ///                       < main playing loop >

        ///

        ///    search for the first marked interface and play attached wave

        ///


        for( unsigned int k = 0; k < SUPPORT_INTERFACES; k++ )
        {
            /// nothing to do with free interface

            if( wb->I[k].state == INT_FREE ) continue;

            EnterCriticalSection( &cs );
            int free = wb->I[k].wfreeblock;
            LeaveCriticalSection( &cs );
            
            /// nothing to do with full queued interface

            if( free < BP_TURN )    continue; 

            WAVEHDR     *current = NULL;
            
            /// how much blocks per turn will be queued

            for( unsigned int m = 0; m < BP_TURN; m++ )
            {   
                /// set current block pointer

                current = &wb->I[k].wblock[wb->I[k].wcurrblock]; 
                
                // first make sure the header we're going to use is unprepared

                if( current->dwFlags & WHDR_PREPARED ) 

                    waveOutUnprepareHeader( wb->I[k].dev, 
                                            current, 
                                            sizeof(WAVEHDR)  );     

                /// how much data is left at this interface to play

                unsigned long left  = wb->I[k].wave->size - wb->I[k].wpos;
                unsigned long chunk = 0;

                if( left  >= BLOCK_SIZE )
                    chunk  = BLOCK_SIZE;
                else 
                if( left && left < BLOCK_SIZE )
                    chunk  = left;
                else
                {   
                    ////////////////////

                    /// nothing left ///

                    ////////////////////////////////////////////////////////

                    ///

                    ///           < clean job, close waveOutProc threads >

                    ///

                    ///           all buffers are queued to the interface

                    ///

                    
                    /// get free block count

                    EnterCriticalSection( &cs );
                    int free = wb->I[k].wfreeblock;
                    LeaveCriticalSection( &cs );


                    if( free == BLOCK_COUNT )   /// are all blocks played!?

                    {
                
                        /// unprepare any blocks that are still prepared

                        for( int i = 0; i < wb->I[k].wfreeblock; i++) 
                            
                            if( wb->I[k].wblock[i].dwFlags & WHDR_PREPARED )
                
                                waveOutUnprepareHeader(  wb->I[k].dev, 
                                                         &wb->I[k].wblock[i], 
                                                         sizeof(WAVEHDR));
                    
                        /// close interface

                        if( wb->RemoveInterface( wb->I[k].dev ) )
                        {
                            /// free interface

                            wb->I[k].wcurrblock = 0;
                            wb->I[k].state      = INT_FREE;
                            wb->I[k].wpos       = 0;
                            wb->I[k].wave       = NULL;
                        }
                    }

                    /// step out

                    break;
                }
    
                /// prepare current wave data block header

                memcpy( current->lpData, 
                        &wb->I[k].wave->data[wb->I[k].wpos], chunk );

                current->dwBufferLength  = chunk;   // sizeof block

                wb->I[k].wpos           += chunk;   // update position

                
                /// prepare for playback

                waveOutPrepareHeader( wb->I[k].dev, 
                         current, sizeof(WAVEHDR) );
                
                /// push to the queue

                waveOutWrite(wb->I[k].dev, current, sizeof(WAVEHDR));

                /// decrease free block counter

                EnterCriticalSection( &cs );
                wb->I[k].wfreeblock--;
                LeaveCriticalSection( &cs );
                
                /// point to the next block

                wb->I[k].wcurrblock++;
                wb->I[k].wcurrblock %= BLOCK_COUNT;
            
            }/// block(s)


        }/// interface(s)

    
        /// wait 10 ms < save CPU time >

        Sleep( 10 );
        
        /// check for thread message

        EnterCriticalSection( &cs );
        tmsg = wb->TMSG;
        LeaveCriticalSection( &cs );

    }/// thread




    //////////////////////////////////////////////////////////////////////

    //////////////////////////////////////////////////////////////////////

    ///

    ///            force to close interfaces which are still playing 

    ///


    for( i = 0; i < SUPPORT_INTERFACES; i++ )
            if( wb->I[i].state == INT_USED )    
                if( waveOutReset( wb->I[i].dev ) == MMSYSERR_NOERROR )
                    wb->RemoveInterface( wb->I[i].dev );
    
                
    return  THREAD_EXIT; /// return exit code < destructor >

}

~CWaveBox()

Within the destructor, for resumed thread, TMSG_CLOSE message is set and looped until THREAD_EXIT code will not arrive or 'thread soft close', so all playing interfaces are properly forced to be reset and closed. For suspended thread, 'thread hard close' or TerminateThread is called. After that, all required buffers and critical section are released.

CWaveBox::~CWaveBox()
{
    unsigned long exit = 0;

    if( run ) // thread resumed

    {
        // set thread close message

        EnterCriticalSection( &cs );
        TMSG = TMSG_CLOSE;
        LeaveCriticalSection( &cs );
        
        do // wait for soft close

        { 
            GetExitCodeThread( thread, &exit ); 
            Sleep( 10 );

        }while( exit != THREAD_EXIT );
    
    }else // thread suspended   

    {
        // hard close 

        GetExitCodeThread( thread, &exit ); 
        TerminateThread( thread, exit );
    }
    
    
    // release wave(s)

    for( unsigned int i = 0; i < wload; i++ )
        free( W[i].data );

    // release interface(s)

    for( i = 0; i < SUPPORT_INTERFACES; i++ )
        freeBlocks( I[i].wblock ); 

    // del cs

    DeleteCriticalSection( &cs );
}

The Demo

This demo is a simple and effective example of how you can use this class and play with timed Waves. All you have to do is to Load a Wave file, choose/set timing, and Play it. Turn your volume on and enjoy! ;-)

Note: for compiling on Windows XP, link 'Winmm.lib' library, while on Windows CE it is already linked.

#include "stdafx.h"

#include "wavebox.h"

#include <stdio.h>

#include <conio.h>


int main(int argc, char* argv[])
{
    CWaveBox w;

    /// load waves

    w.Load("twilightzone.wav");
    w.Load("badfeeling.wav");
    w.Load("wolfcall.wav");
    w.Load("heart.wav");
    w.Load("gusting_winds.wav");
    w.Load("newmail.wav");
    w.Load("dumbass.wav");
    w.Load("porky.wav");
    w.Load("system_alert.wav");

    printf("<CWaveBox v0.95 demo>:\n\n");
    printf("You will listen 9 waves in this demo, approx. 30 sec.\n");

    /// and play with them ;-)

    for( int f = 0; f < 5; f++ ){ w.Play(8); Sleep(50); } Sleep(2000);
    for( f = 0; f < 5; f++ ){ w.Play(8); Sleep(75);}

    for( int k = 0; k < 1000; k += 50 )
    {
        w.Play(3); Sleep( 300 + 1000 - k );

        if( !( k % 200 ) )          w.Play(2);
        if( k == 400 || k == 700 )  w.Play(1);

        if( k == 950 )
        { w.Play(4); Sleep( 500 );}
    }

    w.Play(0);Sleep(2500);
    w.Play(5);Sleep(1500);
    w.Play(6);Sleep(4500);
    w.Play(7);
    
    printf("thats all folks! ;-) \n");  
    getch();

    return 1;
}

Points of Interest

This class can be extended by UnLoad method, so any Wave can be Loaded and UnLoaded any time. Also, playing from file 'on the fly' which has lower memory usage, but has bigger CPU usage, wouldn't be a bad extension. Then, events can replace the current pooling model and extend Play for compressed Waves too. I will like to see this class attached to the MPEG3 'licence free' and 'commercially usable without fee' decoder on this zone so we can freely implement it in our games and applications.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here