You might recall that in the previous project, all the sound code took up quite a few lines of code. Now consider that with spatialization, it's going to get longer still. To keep our code manageable, we will code a class to manage the playing of all our sound effects. In addition, to help us with spatialization, we will add a function to the Engine class as well, but we will discuss that when we come to it, later in the chapter.
Let's get started by coding and examining the header file.
Right-click Header Files in the Solution Explorer and select Add | New Item.... In the Add New Item window, highlight (by left-clicking) Header File (
.h
) and then in the Name field, type SoundManager.h
. Finally, click the Add button. We are now ready to code the header file for the SoundManager
class.
Add and examine the following code:
#pragma once #include <SFML/Audio.hpp> using namespace sf; class SoundManager { private: // The buffers SoundBuffer m_FireBuffer; SoundBuffer m_FallInFireBuffer; SoundBuffer m_FallInWaterBuffer; SoundBuffer m_JumpBuffer; SoundBuffer m_ReachGoalBuffer; // The Sounds Sound m_Fire1Sound; Sound m_Fire2Sound; Sound m_Fire3Sound; Sound m_FallInFireSound; Sound m_FallInWaterSound; Sound m_JumpSound; Sound m_ReachGoalSound; // Which sound should we use next, fire 1, 2 or 3 int m_NextSound = 1; public: SoundManager(); void playFire(Vector2f emitterLocation, Vector2f listenerLocation); void playFallInFire(); void playFallInWater(); void playJump(); void playReachGoal(); };
There is nothing tricky in the code we just added. There are five SoundBuffer
objects and eight Sound
objects. Three of the Sound
objects will play the same SoundBuffer
. This explains the reason for the different number of Sound
/SoundBuffer
objects. We do this so that we can have multiple roaring sound effects playing, with different spatialized parameters, simultaneously.
Notice there is the m_NextSound
variable that will help us keep track of which of these potentially simultaneous sounds we should use next.
There is a constructor, SoundManager
, where we will set up all our sound effects, and there are five functions that will play the sound effects. Four of these functions simply play normal sound effects and their code will be really simple.
One of the functions, playFire
, will handle the spatialized sound effects and will be a bit more in-depth. Notice the parameters of the playFire
function. It receives a Vector2f
, which is the location of the emitter, and a second Vector2f
, which is the location of the listener.
Now we can code the function definitions. The constructor and the playFire
functions have a fair amount of code, so we will look at them individually. The other functions are short and sweet so we will handle them all at once.
Right-click Source Files in the Solution Explorer and select Add | New Item.... In the Add New Item window, highlight (by left-clicking) C++ File (
.cpp
) and then in the Name field, type SoundManager.cpp
. Finally, click the Add button. We are now ready to code the .cpp
file for the SoundManager
class.
Add the following code for the include directives and the constructor to SoundManager.cpp
:
#include "stdafx.h" #include "SoundManager.h" #include <SFML/Audio.hpp> using namespace sf; SoundManager::SoundManager() { // Load the sound in to the buffers m_FireBuffer.loadFromFile("sound/fire1.wav"); m_FallInFireBuffer.loadFromFile("sound/fallinfire.wav"); m_FallInWaterBuffer.loadFromFile("sound/fallinwater.wav"); m_JumpBuffer.loadFromFile("sound/jump.wav"); m_ReachGoalBuffer.loadFromFile("sound/reachgoal.wav"); // Associate the sounds with the buffers m_Fire1Sound.setBuffer(m_FireBuffer); m_Fire2Sound.setBuffer(m_FireBuffer); m_Fire3Sound.setBuffer(m_FireBuffer); m_FallInFireSound.setBuffer(m_FallInFireBuffer); m_FallInWaterSound.setBuffer(m_FallInWaterBuffer); m_JumpSound.setBuffer(m_JumpBuffer); m_ReachGoalSound.setBuffer(m_ReachGoalBuffer); // When the player is 50 pixels away sound is full volume float minDistance = 150; // The sound reduces steadily as the player moves further away float attenuation = 15; // Set all the attenuation levels m_Fire1Sound.setAttenuation(attenuation); m_Fire2Sound.setAttenuation(attenuation); m_Fire3Sound.setAttenuation(attenuation); // Set all the minimum distance levels m_Fire1Sound.setMinDistance(minDistance); m_Fire2Sound.setMinDistance(minDistance); m_Fire3Sound.setMinDistance(minDistance); // Loop all the fire sounds // when they are played m_Fire1Sound.setLoop(true); m_Fire2Sound.setLoop(true); m_Fire3Sound.setLoop(true); }
In the previous code, we loaded five sound files into the five SoundBuffer
objects. Next, we associated the eight Sound
objects with one of the SoundBuffer
objects. Notice that m_Fire1Sound
, m_Fire2Sound
, and m_Fire3Sound
are all going to be playing from the same SoundBuffer
, m_FireBuffer
.
Next, we set the attenuation and minimum distance for the three fire sounds.
Finally, for the constructor, we used the setLoop
function on each of the fire-related Sound
objects. Now when we call play
, they will play continuously.
Add the playFire
function shown in the following code, and then we can discuss it:
void SoundManager::playFire( Vector2f emitterLocation, Vector2f listenerLocation) { // Where is the listener? Thomas. Listener::setPosition(listenerLocation.x, listenerLocation.y, 0.0f); switch(m_NextSound) { case 1: // Locate/move the source of the sound m_Fire1Sound.setPosition(emitterLocation.x, emitterLocation.y, 0.0f); if (m_Fire1Sound.getStatus() == Sound::Status::Stopped) { // Play the sound, if its not already m_Fire1Sound.play(); } break; case 2: // Do the same as previous for the second sound m_Fire2Sound.setPosition(emitterLocation.x, emitterLocation.y, 0.0f); if (m_Fire2Sound.getStatus() == Sound::Status::Stopped) { m_Fire2Sound.play(); } break; case 3: // Do the same as previous for the third sound m_Fire3Sound.setPosition(emitterLocation.x, emitterLocation.y, 0.0f); if (m_Fire3Sound.getStatus() == Sound::Status::Stopped) { m_Fire3Sound.play(); } break; } // Increment to the next fire sound m_NextSound++; // Go back to 1 when the third sound has been started if (m_NextSound > 3) { m_NextSound = 1; } }
The first thing we do is call Listener::setPosition
and set the location of the listener based on the Vector2f
that is passed in as a parameter.
Next, the code enters a switch
block based on the value of m_NextSound
. Each of the case
statements does the exact same thing, but to either m_Fire1Sound
, m_Fire2Sound
, or m_Fire3Sound
.
In each of the case
blocks, we set the position of the emitter using the passed-in parameter with the setPosition
function. The next part of the code in each case
block checks whether the sound is currently stopped, and if it is, plays the sound. We will see quite soon how we arrive at the positions for the emitter and listener that are passed into this function.
The final part of the playFire
function increments m_NextSound
and ensures that it can only be equal to 1, 2, or 3, as required by the switch
block.
Add these four simple functions:
void SoundManager::playFallInFire() { m_FallInFireSound.setRelativeToListener(true); m_FallInFireSound.play(); } void SoundManager::playFallInWater() { m_FallInWaterSound.setRelativeToListener(true); m_FallInWaterSound.play(); } void SoundManager::playJump() { m_JumpSound.setRelativeToListener(true); m_JumpSound.play(); } void SoundManager::playReachGoal() { m_ReachGoalSound.setRelativeToListener(true); m_ReachGoalSound.play(); }
The playFallInFire
, playFallInWater
, and playReachGoal
functions do just two things. First, they each call setRelativeToListener
, so the sound effect is not spatialized, making the sound effect normal, not directional, and then they call play
on the appropriate Sound
object.
That concludes the SoundManager
class. Now we can use it in the Engine
class.
3.138.134.229