Tải bản đầy đủ (.pdf) (71 trang)

Advanced 3D Game Programming with DirectX - phần 3 pot

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (558.47 KB, 71 trang )


143
HRESULT WINAPI DirectSoundCreate8(
LPCGUID lpcGuid,
LPDIRECTSOUND8 * ppDS,
LPUNKNOWN pUnkOuter
);
lpcGuid
A pointer to a GUID that describes the device you wish to create. While you can
enumerate all of the sound devices with DirectSoundEnumerate, generally there is
only one sound card on a machine. To get the default device (which is what you want,
usually), set this to NULL.

ppDS
A pointer to an LPDIRECTSOUND8 interface pointer that will be filled with a valid
interface pointer if the function succeeds.

pUnkOuter
Used for COM aggregation; leave this as NULL.
Sample code to create the sound interface appears in the following:
LPDIRECTSOUND8 m_pDSound = 0;

// Create IDirectSound using the primary sound device
hr = DirectSoundCreate8( NULL, &m_pDSound, NULL );
if( FAILED( hr ) )
{
// Handle critical error
}
Setting the Cooperative Level
After you acquire the interface pointer, the next step is to declare how cooperative you intend on being.
Just like DirectInput, this is done using the SetCooperativeLevel command.


HRESULT IDirectSound8::SetCooperativeLevel(
HWND hwnd,
DWORD dwLevel
);


144
hwnd
Handle to the window to be associated with the DirectSound object. This should be the
primary window.

dwLevel
One of the following flags, describing the desired cooperative level.
 DSSCL_EXCLUSIVE—Grab exclusive control of the sound device. When the
application has focus, it is the only audible application.
 DSSCL_NORMAL—Smoothest, yet most restrictive cooperative level. The
primary format cannot be changed. This is the cooperative level the sound layer
uses.
 DSSCL_PRIORITY—Like DDSCL_NORMAL except the primary format may be
changed.
 DSSCL_WRITEPRIMARY—This is the highest possible priority for an application
to have. It can't play any secondary buffers, and it has the ability to manually mangle
the bits of the primary buffer. Only for the extremely hardcore!
This code will be changing the primary format of the sound buffer, so I'll go ahead and set this to
DSSCL_PRIORITY. Sample code to do this appears in the following:
// pDSound is a valid LPDIRECTSOUND8 object.
HRESULT hr = pDSound->SetCooperativeLevel( hWnd, DSSCL_PRIORITY );
if( FAILED( hr ) )
{
/* handle error */

}
Grabbing the Primary Buffer
Since the sound layer sets the cooperative level's priority, it can do some crazy things like change the
format of the primary buffer. Generally it's best to set the primary buffer to the same format that all of
your secondary buffers will be in; this makes the mixer's job easier, as it doesn't have to resample any
sound effects to be able to mix them into the primary buffer. You can imagine what would happen if you
tried to play a 22 KHz sound effect in a 44 KHz buffer without resampling: You would run out of samples
twice as soon as you would expect, and the sound effect would have sort of a chipmunkish quality to it.
To change the format of the primary buffer, you just need to grab it using CreateSoundBuffer, fill out a
new format description, and set it using the SetFormat() method on the primary buffer. Listing 4.2
has
code that sets the primary format to 22 KHz, 16-bit stereo.
Listing 4.2: Sample code to change the format of the primary buffer

145

// pDSound is a valid LPDIRECTSOUND object.
LPDIRECTSOUNDBUFFER pDSBPrimary = NULL;

sAutoZero<DSBUFFERDESC> dsbd;
dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER;
dsbd.dwBufferBytes = 0;
dsbd.lpwfxFormat = NULL;

HRESULT hr = pDSound->CreateSoundBuffer( &dsbd, &pDSBPrimary, NULL );
if( FAILED( hr ) )
{
/* handle error */
}


// Set primary buffer format to 22 kHz and 16-bit output.
WAVEFORMATEX wfx;
ZeroMemory( &wfx, sizeof(WAVEFORMATEX) );
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nChannels = 2;
wfx.nSamplesPerSec = 22050;
wfx.wBitsPerSample = 16;
wfx.nBlockAlign = wfx.wBitsPerSample/8* wfx.nChannels;
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;

HRESULT hr = hr = pDSBPrimary->SetFormat(&wfx)
if( FAILED( ) )
{
throw cGameError( "SetFormat (DS) failed!" );
}


146
SafeRelease( pDSBPrimary );


With all the code in place, you can actually write the sound layer class. The header appears in Listing
4.3, and the source code is in Listing 4.4.
Listing 4.3: SoundLayer.h

/*******************************************************************
* Advanced 3D Game Programming using DirectX 9.0
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Desc: Sample application for Direct3D
*

* copyright (c) 2002 by Peter A Walsh and Adrian Perez
* See license.txt for modification and distribution information
******************************************************************/

#ifndef _SOUNDLAYER_H
#define _SOUNDLAYER_H

#include <dsound.h>
#include "GameErrors.h" // Added by ClassView

class cSound;

class cSoundLayer
{

LPDIRECTSOUND8 m_pDSound;
LPDIRECTSOUNDBUFFER8 m_pPrimary; // primary mixer

static cSoundLayer* m_pGlobalSLayer;


147
cSoundLayer( HWND hWnd );

public:
virtual ~cSoundLayer();

static cSoundLayer* GetSound()
{
return m_pGlobalSLayer;

}

LPDIRECTSOUND8 GetDSound()
{
return m_pDSound;
}
static void Create( HWND hWnd )
{
new cSoundLayer( hWnd );
}
};

inline cSoundLayer* Sound()
{
return cSoundLayer::GetSound();
}

#endif //_SOUNDLAYER_H


Listing 4.4: SoundLayer.cpp

/*******************************************************************
* Advanced 3D Game Programming using DirectX 9.0

148
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Desc: Sample application for Direct3D
*
* copyright (c) 2002 by Peter A Walsh and Adrian Perez

* See license.txt for modification and distribution information
******************************************************************/

#include "stdafx.h"

#include "SoundLayer.h"
#include "Sound.h"

cSoundLayer* cSoundLayer::m_pGlobalSLayer = NULL;

cSoundLayer::cSoundLayer( HWND hWnd )
{
m_pDSound = NULL;
m_pPrimary = NULL;

if( m_pGlobalSLayer )
{
throw cGameError( "cSoundLayer already initialized!" );
}
m_pGlobalSLayer = this;

HRESULT hr;
LPDIRECTSOUNDBUFFER pDSBPrimary = NULL;

// Create IDirectSound using the primary sound device
hr = DirectSoundCreate8( NULL, &m_pDSound, NULL );
if( FAILED( hr ) )
{

149

throw cGameError( "DirectSoundCreate failed!" );
}

// Set coop level to DSSCL_PRIORITY
hr = m_pDSound->SetCooperativeLevel( hWnd, DSSCL_PRIORITY );
if( FAILED( hr ) )
{
throw cGameError( "SetCooperativeLevel (DS) failed!" );
}

// Get the primary buffer
sAutoZero<DSBUFFERDESC> dsbd;
dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER;
dsbd.dwBufferBytes = 0;
dsbd.lpwfxFormat = NULL;

hr = m_pDSound->CreateSoundBuffer( &dsbd, &pDSBPrimary, NULL );
if( FAILED( hr ) )
{
throw cGameError( "SetFormat (DS) failed!" );
}

// Set primary buffer format to 22 kHz and 16-bit output.
WAVEFORMATEX wfx;
ZeroMemory( &wfx, sizeof(WAVEFORMATEX) );
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nChannels = 2;
wfx.nSamplesPerSec = 22050;
wfx.wBitsPerSample = 16;
wfx.nBlockAlign = wfx.wBitsPerSample/8* wfx.nChannels;

wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign;


150
if( FAILED( hr = pDSBPrimary->SetFormat(&wfx) ) )
{
throw cGameError( "CreateSoundBuffer (DS) failed!" );
}
SafeRelease( pDSBPrimary );
}

cSoundLayer::~cSoundLayer()
{
SafeRelease( m_pPrimary );
SafeRelease( m_pDSound );
m_pGlobalSLayer = NULL;
}


The cSound Class
To help facilitate the creation and playback of secondary buffers, I constructed an encapsulation class
called cSound. A cSound object can be constructed either from a filename or from another cSound
object. The copy constructor uses a ref-counting map so that all cSounds based on the same WAV file
use the same CWaveSoundRead object. The overhead of the map could have been avoided if the
CWaveSoundRead code was changed to accommodate the needed functionality, but I felt it was better
to leave the code unchanged from the DirectX SDK.
Without any further ado, let's just dive into the code. The details of how this code works isn't terribly
interesting but have a look through it anyway to get accustomed to it.
Listing 4.5: Sound.h


/*******************************************************************
* Advanced 3D Game Programming using DirectX 9.0
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* * Desc: Sample application for Direct3D
*
* copyright (c) 2002 by Peter A Walsh and Adrian Perez
* See license.txt for modification and distribution information

151
******************************************************************/

#ifndef _SOUND_H
#define _SOUND_H

#include <map>

#include "SoundLayer.h"
#include "Wavread.h"

class cSound
{
CWaveSoundRead* m_pWaveSoundRead;
LPDIRECTSOUNDBUFFER8 m_pBuffer;
int m_bufferSize;

/**
* Multiple sounds that use the same
* file shouldn't reread it, they should
* share the CWSR object. This map
* implements rudimentary reference counting.

* I would have just changed CWaveSoundRead,
* but I wanted to keep it unchanged from the
* samples.
*/
static std::map< CWaveSoundRead*, int > m_waveMap;

void Init();

public:
cSound( char* filename );
cSound( cSound& in );

152
cSound& operator=( const cSound &in );

virtual ~cSound();

void Restore();
void Fill();
void Play( bool bLoop = false );

bool IsPlaying();

};

#endif //_SOUND_H


Listing 4.6: Sound.cpp


/*******************************************************************
* Advanced 3D Game Programming using DirectX 9.0
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Desc: Sample application for Direct3D
*
* copyright (c) 2002 by Peter A Walsh and Adrian Perez
* See license.txt for modification and distribution information
******************************************************************/

#include "stdafx.h"

#include "WavRead.h"
#include "Sound.h"

using std::map;

153

map< CWaveSoundRead*, int > cSound::m_waveMap;

cSound::cSound( char* filename )
{
m_pWaveSoundRead = NULL;
m_pBuffer = NULL;

// Create a new wave file class
m_pWaveSoundRead = new CWaveSoundRead();
m_waveMap[ m_pWaveSoundRead]=1;

// Load the wave file

if( FAILED( m_pWaveSoundRead->Open( filename)))
{
throw cGameError("couldn't open file!");
}
Init();
Fill();
}

cSound::cSound( cSound& in )
{
m_pWaveSoundRead = in.m_pWaveSoundRead;
m_waveMap[ m_pWaveSoundRead ]++;
Init();
Fill();
}

cSound& cSound::operator=( const cSound &in )
{
/**

154
* Destroy the old object
*/
int count = m_waveMap[ m_pWaveSoundRead ];
if( !count )
{
delete m_pWaveSoundRead;
}
SafeRelease( m_pBuffer );


/**
* Clone the incoming one
*/
m_pWaveSoundRead = in.m_pWaveSoundRead;
m_waveMap[ m_pWaveSoundRead ]++;

Init();
Fill();

return *this;
}

cSound::~cSound()
{
int count = m_waveMap[ m_pWaveSoundRead ];
if( count == 1 )
{
delete m_pWaveSoundRead;
}
else
{
m_waveMap[ m_pWaveSoundRead ] = count - 1;
}

155

SafeRelease( m_pBuffer );
}

void cSound::Init()

{
/**
* Set up the DirectSound surface. The size of the sound file
* and the format of the data can be retrieved from the wave
* sound object. Besides that, we only set the STATIC flag,
* so that the driver isn't restricted in setting up the
* buffer.
*/
sAutoZero<DSBUFFERDESC> dsbd;
dsbd.dwFlags = DSBCAPS_STATIC;
dsbd.dwBufferBytes = m_pWaveSoundRead->m_ckIn.cksize;
dsbd.lpwfxFormat = m_pWaveSoundRead->m_pwfx;

HRESULT hr;

// Temporary pointer to old DirectSound interface
LPDIRECTSOUNDBUFFER pTempBuffer = 0;

// Create the sound buffer
hr = Sound()->GetDSound()->CreateSoundBuffer( &dsbd, &pTempBuffer,
NULL );
if( FAILED( hr ) )
{
throw cGameError( "couldn't get buffer status" );
}

// Upgrade the sound buffer to version 8

156
pTempBuffer->QueryInterface( IID_IDirectSoundBuffer8,

(void**)&m_pBuffer );
if( FAILED( hr ) )
{
throw cGameError("CreateSoundBuffer failed!");
}

// Release the temporary old buffer
pTempBuffer->Release();

/**
* Remember how big the buffer is
*/
m_bufferSize = dsbd.dwBufferBytes;
}

void cSound::Restore()
{
HRESULT hr;
if( NULL == m_pBuffer )
{
return;
}

DWORD dwStatus;
if( FAILED( hr = m_pBuffer->GetStatus( &dwStatus)))
{
throw cGameError("SoundBuffer query to 8 failed!");
}

if( dwStatus & DSBSTATUS_BUFFERLOST )

{

157
/**
* Chances are, we got here because the app /just/
* started, and DirectSound hasn't given us any
* control yet. Just spin until we can restore
* the buffer
*/
do
{
hr = m_pBuffer->Restore();
if( hr == DSERR_BUFFERLOST )
Sleep( 10 );
}
while( hr = m_pBuffer->Restore() );

/**
* The buffer was restored. Fill 'er up.
*/
Fill();
}
}

void cSound::Fill()
{
HRESULT hr;
uchar* pbWavData; // Pointer to actual wav data
uint cbWavSize; // Size of data
void* pbData = NULL;

void* pbData2 = NULL;
ulong dwLength;
ulong dwLength2;

/**

158
* How big the wav file is
*/
uint nWaveFileSize = m_pWaveSoundRead->m_ckIn.cksize;

/**
* Allocate enough data to hold the wav file data
*/
pbWavData = new uchar[ nWaveFileSize ];
if( NULL == pbWavData )
{
delete [] pbWavData;
throw cGameError("Out of memory!");
}

hr = m_pWaveSoundRead->Read(
nWaveFileSize,
pbWavData,
&cbWavSize );
if( FAILED( hr ) )
{
delete [] pbWavData;
throw cGameError("m_pWaveSoundRead->Read failed");
}

/**
* Reset the file to the beginning
*/
m_pWaveSoundRead->Reset();

/**
* Lock the buffer so we can copy the data over
*/
hr = m_pBuffer->Lock(

159
0, m_bufferSize, &pbData, &dwLength,
&pbData2, &dwLength2, 0L );
if( FAILED( hr ) )
{
delete [] pbWavData;
throw cGameError("m_pBuffer->Lock failed");
}

/**
* Copy said data over, unlocking afterwards
*/
memcpy( pbData, pbWavData, m_bufferSize );
m_pBuffer->Unlock( pbData, m_bufferSize, NULL, 0 );

/**
* We're done with the wav data memory.
*/
delete [] pbWavData;
}


bool cSound::IsPlaying()
{
DWORD dwStatus = 0;
m_pBuffer->GetStatus( &dwStatus );
if( dwStatus & DSBSTATUS_PLAYING )
return true;
else
return false;
}

void cSound::Play( bool bLoop )
{

160
HRESULT hr;
if( NULL == m_pBuffer )
return;

// Restore the buffers if they are lost
Restore();

// Play buffer
DWORD dwLooped = bLoop ? DSBPLAY_LOOPING : 0L;
if( FAILED( hr = m_pBuffer->Play( 0, 0, dwLooped)))
{
throw cGameError("m_pBuffer->Play failed");
}
}



Additions to cApplication
The only addition to cApplication is the InitSound call, which initializes the sound layer. After the call
completes you can freely create cSound objects and play them. If this is not the behavior you would like
in your application, the function is overloadable. The code is in the following:
void cApplication::InitSound()
{
cSoundLayer::Create( MainWindow()->GetHWnd() );
}
Application: DirectSound Sample
Adrian, the lead author of the DirectX 7.0 version of this book, has a few interesting hobbies. As part of
an ongoing effort, he does extracurricular activities that actually have nothing to do with programming.
One of them is an a cappella group that he sings bass in. One of his jobs in the a cappella group is to
take care of some of the vocal percussion.
A cappella music can't have any sort of accompaniment, so any percussion needs to be done with the
human voice. This has spawned an entire subculture of vocal percussionists, each trying to make that

161
perfect snare sound or cymbal crash using only their mouths. The DirectSound sample for this chapter
was created using Adrian's unique vocal abilities.
When you load the file DSSAMPLE from the companion files, you're presented with a small window that
lists six different vocal percussion sounds. The keys 1 through 6 play each of the sounds, and you can
press multiple keys simultaneously to play multiple sounds.
You'll note that I didn't show you a DirectInput sample, because I figured it would be better to roll
DirectSound and DirectInput into one sample. DirectInput is used to capture the keystrokes. With some
practice you can get a pretty swank beat going. The code behind the sample appears in Listing 4.7
.
Listing 4.7: The vocal percussion DirectSound sample app

/*******************************************************************

* Advanced 3D Game Programming using DirectX 9.0
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Desc: Sample application for Direct3D
*
* copyright (c) 2002 by Peter A Walsh and Adrian Perez
* See license.txt for modification and distribution information
******************************************************************/

#include "stdafx.h"

#include <vector>
#include <string>
using namespace std;

class cDSSampleApp : public cApplication, public iKeyboardReceiver
{
vector< cSound* > m_sounds[6];
string m_names[6];

int m_states[6]; // states of the keys 1-6


162
public:

void PlaySound( int num );

//========== cApplication

virtual void DoFrame( float timeDelta );

virtual void SceneInit();

cDSSampleApp() :
cApplication()
{
m_title = string( "DirectSound Sample" );
m_width = 320;
m_height = 200;
for( int i=0; i<6; i++ ) m_states[i] = 0;
}

~cDSSampleApp()
{
for( int i=0; i<6; i++ )
{
for( int i2=0; i2< m_sounds[i].size(); i2++ )
{
delete m_sounds[i][i2];
}
}
}

virtual void KeyUp( int key );
virtual void KeyDown( int key );


163
};

cApplication* CreateApplication()

{
return new cDSSampleApp();
}
void DestroyApplication( cApplication* pApp )
{
delete pApp;
}

void cDSSampleApp::SceneInit()
{
m_names[0] = string("media\\keg.wav");
m_names[1] = string("media\\crash1.wav");
m_names[2] = string("media\\crash2.wav");
m_names[3] = string("media\\bass.wav");
m_names[4] = string("media\\snare.wav");
m_names[5] = string("media\\hihat.wav");

Input()->GetKeyboard()->SetReceiver( this );

for( int i=0; i<6; i++ )
{
m_sounds[i].push_back( new cSound( (char*)m_names[i].c_str() ) );
}
}

void cDSSampleApp::PlaySound( int num )
{
/**
* iterate through the vector, looking


164
* for a sound that isn't currently playing.
*/
vector<cSound*>::iterator iter;
for( iter = m_sounds[num].begin(); iter != m_sounds[num].end(); iter++
)
{
if( !(*iter)->IsPlaying() )
{
(*iter)->Play();
return;
}
}

/**
* A sound wasn't found. Create a new one.
*/
DP("spawning a new sound\n");
cSound* pNew = new cSound( *m_sounds[num][0] );
m_sounds[num].push_back( pNew );
m_sounds[num][ m_sounds[num].size() - 1 ]->Play();
}

void cDSSampleApp::DoFrame( float timeDelta )
{
// Clear the previous contents of the back buffer
Graphics()->GetDevice()->Clear( 0, 0, D3DCLEAR_TARGET |
D3DCLEAR_ZBUFFER,
D3DCOLOR_XRGB( 0,0,200), 1.0f, 0 );
// Set up the strings

string help;
help += "DirectSound Sample application\n";
help += "Vocal Percussion with Adrian Perez\n";

165
help += " [1]: Keg drum\n";
help += " [2]: Crash 1\n";
help += " [3]: Crash 2\n";
help += " [4]: Bass drum\n";
help += " [5]: Snare drum\n";
help += " [6]: Hi-Hat\n";

// Tell Direct3D we are about to start rendering
Graphics()->GetDevice()->BeginScene();

// Output the text
Graphics()->DrawTextString( 1, 1, D3DCOLOR_XRGB( 0, 255, 0),
help.c_str() );

// Tell Direct3D we are done rendering
Graphics()->GetDevice()->EndScene();

// Present the back buffer to the primary surface
Graphics()->Flip();
}

void cDSSampleApp::KeyDown( int key )
{
switch( key )
{

case DIK_1:
if( !m_states[0] )
{
m_states[0] = 1;
PlaySound(0);
}
break;

166
case DIK_2:
if( !m_states[1] )
{
m_states[1] = 1;
PlaySound(1);
}
break;
case DIK_3:
if( !m_states[2] )
{
m_states[2] = 1;
PlaySound(2);
}
break;
case DIK_4:
if( !m_states[3] )
{
m_states[3] = 1;
PlaySound(3);
}
break;

case DIK_5:
if( !m_states[4] )
{
m_states[4] = 1;
PlaySound(4);
}
break;
case DIK_6:
if( !m_states[5] )
{
m_states[5] = 1;

167
PlaySound(5);
}
break;
}
}

void cDSSampleApp::KeyUp( int key )
{
switch( key )
{
case DIK_1:
m_states[0] = 0;
break;
case DIK_2:
m_states[1] = 0;
break;
case DIK_3:

m_states[2] = 0;
break;
case DIK_4:
m_states[3] = 0;
break;
case DIK_5:
m_states[4] = 0;
break;
case DIK_6:
m_states[5] = 0;
break;
}
}


×