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






4.80/5 (16 votes)
Apr 15, 2005
9 min read

171114

4766
Multiwave player ('Waveform Audio Interface' PCM wave wrapper).
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 INTERFACE
s 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 INTERFACE
s. 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 Load
ed and UnLoad
ed 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.