Click here to Skip to main content
Click here to Skip to main content
Go to top

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

, 14 Apr 2005
Rate this:
Please Sign up or sign in to vote.
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 Wink | ;-) . 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' Wink | ;-) . 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. Wink | ;-) .

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. Wink | ;-)

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. Wink | ;-)

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! Wink | ;-)

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

Share

About the Author

zenith__
Web Developer
Austria Austria
No Biography provided

Comments and Discussions

 
QuestionHow can I use this class in C# application? Pinmemberricardoborges17-Apr-07 20:06 
AnswerRe: How can I use this class in C# application? Pinmemberzenith__17-Apr-07 20:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140916.1 | Last Updated 15 Apr 2005
Article Copyright 2005 by zenith__
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid