Beginning Game Audio Programming [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Beginning Game Audio Programming [Electronic resources] - نسخه متنی

Mason McCuskey

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
توضیحات
افزودن یادداشت جدید





SOUND INSTANCES

As you might have guessed, you're not quite finished dealing with concurrent sound Playback just yet. In the last section, you learned how to get DirectMusic to Play back more than one sound. This section will teach you how to change the audio engine to accommodate the (new) fact that more than one sound can be Playing.

The big problem with the audio engine right now is that it only keeps track of one segment state (IDirectMusicSegmentState8). Play a sound once and m_SegmentState is filled in with an interface to the new segment state; Play another sound and that exact same thing happens, and you lose forever the first sound's state. What's worse is that the first sound's segment state is never released, creating a resource leak.

It's time to fix all of that. What the audio engine needs is a new class—CSoundInstance. This new class will represent a currently Playing sound. The CSound::Play method will create (and give back) a new CSoundInstance every time that sound Plays. That way, the code that called Play can keep track of that particular sound instance.

This is useful when sound effects loop (which you'll learn about in the next section). Usually, most code that calls Play for a non-looping sound effect won't care about the sound once it's Playing. When a Player fires a bullet, the bullet firing code will typically adjust the sound's parameters (the volume), Play it, and then let it Play. Like Ron Popeil's rotisserie grill, the code sets it, then forgets it.

For looping sounds, the pattern is a bit different. Usually a looping sound will need to be adjusted in response to game events. Imagine a looping "hum" of a power generator in a real-time strategy game. At first the hum is barely audible, but when the Player builds a super weapons factory, the power usage skyrockets, and that hum becomes louder.

Sound instances will become more important as you get farther in the book. Right now, you can't do much with them, but when you start learning about 3D sound, they will become very important. Each sound instance will have a 3D position and several other properties you can adjust.

Follow along in the coming sections by looking at the Ch5p2_SoundInstances sample program.





Tip

The CSound and CSoundInstance relationship is a pattern common in game programming. In 2D games, you have sprites, which run around the game world, and bitmaps, which represent how a sprite looks. This lets you have multiple sprites that all use the same bitmap, and saves you from needlessly duplicating the bitmap data.

In the audio engine of this book, CSound is like a bitmap, and CSoundInstance is like a sprite.



Creating CSoundInstance


CSoundInstance is a simple class, so I'm not going to include its declaration here. Look at it in your development environment and you'll notice that it has Init and UnInit methods, a protected segment state member, a standard issue audio manager pointer (used by IsPlaying), and a flag indicating whether the object is valid.

This class, like CAudioManager, uses two-phase construction. This allows the game to pre-allocate sound instances that will be used later. For example, inside a CTank class, you could have a statically-allocated member called m_EngineSound:


class CTank
{
public:
CTank() { } // nothing to do
virtual ~CTank() { } // nothing to do
protected:
CSoundInstance m_EngineSound; };

If you designed CSoundInstance like CSound (one-phase construction), this wouldn't be possible because CSoundInstance would need constructor parameters specifying the sound it came from.

So, the Init and UnInit methods, as well as the m_Valid flag, are all things you need for two-phase construction. That leaves just a few other things—the IsPlaying method and an m_SegmentState member and associated accessor function, GetSegmentState.

The IsPlaying method looks very similar to its counterpart in CSound:


bool CSoundInstance::IsPlaying()
{
assert(IsValid()); if (!IsValid()) { return(false); }
return(GetAudioManager()->GetPerformance()->IsPlaying(
NULL, m_SegmentState) == S_OK);
}

The performance's IsPlaying method is a two-headed beast. It can check to see if a segment is Playing, or if a segment state is Playing. To differentiate, you put a segment in the first parameter and a segment state in the second. Because CSoundInstance has a segment state, it passes NULL as the first parameter. Nothing to it!





Tip

In the IsPlaying method, notice the IsValid check. One of the drawbacks of using two-phase construction is that in each method, before you try to do anything, you need to first make sure that you've got an object that's fully initialized. That way, if someone is being stupid and calls IsPlaying before the instance has been initialized, the program doesn't crash.



Adjustments to CSound Methods


Now that CSoundInstance is coded, you need to make a few changes to CSound. CSound no longer requires a m_SegmentState member because this is now handled by CSoundInstance. Also, the CSound::IsPlaying() method needs to change to use the segment instead of the segment state. Here's the new code:


bool CSound::IsPlaying()
{
if (NULL == m_Segment) return(false);
if (NULL == m_Manager) return(false);
return(
m_Manager->GetPerformance()->IsPlaying(m_Segment, NULL) == S_OK
);
}

Here, I've changed the call to IsPlaying, passing in the segment as the first parameter and NULL as the second. This tells DirectX Audio to return S_OK if any instance of the sound is Playing.

By far however, the biggest change is in the Play method. Here's the new code:


bool CSound::Play(CSoundInstance *newinst)
{
if (NULL == m_Segment || NULL == m_Manager) {
if (newinst) newinst->UnInit();
return(false);
}
static CSoundInstance dummy;
if (NULL == newinst) newinst = &dummy;
m_Segment->Download(m_Manager->GetPerformance());
// initialize the sound instance
// this will also set the sound Playing
newinst->Init(this);
return(true);
}

For starters, notice that we have a parameter, newinst, which is a pointer to the instance the client would like to use to keep track of the sound that's about to be Played. The newinst parameter is defaulted to NULL in CSound's class declaration, so code that doesn't care about retaining a sound instance can just call Play() as it has always done.

If newinst is NULL, the code uses a dummy static CSoundInstance to initialize the sound and Play it. Otherwise, it uses the newinst passed in.

Note that the CSoundInstance::Init() method wants a CSound pointer. Right now the Init method doesn't use the CSound pointer for anything, but in later sections it will.

Ch4p4_ConcurrentPlayback illustrates how all this new code works together.

/ 127