Now that your game looks stunning with all of the new
fancy graphics techniques you learned, it is time to round out your
game with some sound effects and music. Notice that although graphics
make your game look nice, something is missing and as a player of the
game you are not quite as engaged as you know you can be. Sounds play an
integral part in our lives and as such can play a huge role in how
gamers perceive a game.
Adding sound effects and music to your game is simple using XNA
Game Studio. Determining when and where to use the right sound effects
and music is not as easy, and time and care should be put into the sound
effect and music selection in your game. Often, those who are new to
creating games overlook the sound design of a game. What you find is
that all of the effort you put into making the game sound great is paid
back in immersion and emotion the game provides.
XNA Game Studio provides
the capability to play back sounds for a number of different sources:
sound effects and music saved to files, sounds recorded through a
microphone, and sounds generated dynamically through programming.
Using SoundEffect for Audio Playback
The simplest way to play audio is to use the SoundEffect class. A SoundEffect contains the shared resources needed to play audio such as the wave data that makes up the sound. Load the SoundEffect by using the content pipeline similar to how you load a texture or other resources you have seen so far.
SoundEffect provides
two mechanisms when playing audio. The first is fire and forget, which
provides an easy-to-use API to play an audio file. The second is
instanced-based playback where you request a SoundEffectInstance that enables you as the developer to have more control over the playback of the audio file.
Loading from a File
The first step to play an audio
file is to add the file to your content project for your game.
Right-click your content project and select Add Existing Item. Then,
navigate to an existing audio file. XNA Game Studio supports audio files
in WAV, WMA, and MP3 formats.
The first bit of code you need is a member variable that holds the SoundEffect that you load. Add the following lines of code as a member variable to your game:
// Store our SoundEffect resource
SoundEffect soundEffect;
Now you need to load the SoundEffect using the content pipeline. You can do this by calling the Load method on the ContentManager of the Game class using SoundEffect as the generic parameter and the asset name as the only parameter:
// Load the SoundEffect resource
soundEffect = Content.Load<SoundEffect>("beep");
Fire and Forget Audio Playback
Now that you have loaded
the resources for the audio file, you can use the first mechanism for
sound playback called fire and forget. It is called fire and forget
because after you call play, you don’t need to manage the playback of
the audio stream. The audio file plays from the start of the file until
the end of the file. Although this limits the control you have as a
developer on how the playback occurs, it is simple to use.
To play the SoundEffect using fire and forget, call the Play method:
The Play method returns a boolean value that specifies whether the sound is able to play or not. If Play returns true, then the sound plays. If false returns, there are too many sounds currently playing, so it cannot play.
The Play method also contains an overload with three parameters to control the volume, pitch, and panning of the playback:
soundEffect.Play(1.0f, 0.0f, 0.0f);
The
first parameter is a float value that determines the volume of the
sound. The value can range from 0, which indicates no sound, to 1.0,
which means the sound should be full volume with respect to the master
volume setting. Use the MasterVolume static property of SoundEffect to set the master volume setting for all SoundEffects. The MasterVolume values range from 0 to 1 with 1 as the default value.
The second parameter is a
float value that determines the pitch that the sound plays in. The
pitch value ranges from –1, which lowers the pitch by one octave, to 1,
which raises the pitch by one octave. A value of 0 plays the sound as it
is stored in the file.
The third parameter controls the
pan of the playback. Panning determines how much of the playback occurs
from the left and right speakers. The values rage from –1, which comes
from only the left speaker, to 1, which comes from only from the right
speaker. A value of 0 plays the sound as it was loaded from the file.
Note
You can save files with the
audio playing from only one of the sides. If the file is saved with the
audio source already panned to one side or the other, a value of 0 still
contains the original panning.
SoundEffect also exposes two properties that are useful. The first is the Duration property that returns the length as a TimeSpan of the SoundEffect. The other is Name that returns the name of the SoundEffect.
Playback Using SoundEffectInstance
The other method to play back sounds requires an instance of the sound effect. The SoundEffectInstance provides the capability to control the playback of a single audio stream from a SoundEffect. Create a SoundEffectInstance by calling the CreateInstance method on the SoundEffect. A SoundEffect can create many instances. Each instance provides a number of controls that enable more advanced playback scenarios.
SoundEffectInstance provides methods to Play, Pause, Stop, and Resume playback. The Play method takes no parameters and works similar to the SoundEffect.Play method. It starts the playback of the SoundEffectInstance at the current play location. If the sound is paused, calling Play resumes playback from where the sound was paused. The Resume method is similar and resumes playback from the current sound location.
The Pause method
does what its name suggests and pauses playback at the current playback
location where it can be started again later by calling the Play or Resume methods.
The Stop method stops the sound playback immediately and sets the playback location to the start of the sound. Stop
provides an overload that takes a boolean value to specify if the
playback should stop immediately. If this value is set to false, the
playback stops only after it reaches the end of the sound. This is
useful when you want to allow the sound to complete but stop any looping
that occurs.
Use the State property to determine the current state of a SoundEffectInstance. It returns a SoundState enumeration that contains the values Playing, Paused, or Stopped.
Looping Audio Playback
When a SoundEffectInstance
is set to loop, the sound continues to play until it is paused or
stopped. After the current sound location reaches the end of the
duration, the current position sets back to the start where playback
continues causing the sound playback to loop until it is paused or
stopped.
To set a SoundEffectInstance so it loops, set the IsLooped property to true. After the property is set to true, the sound loops when it is played. You can set the IsLooped property only before Play or Resume methods are called on the instance. After Play or Resume are called on an instance, the IsLooped property can’t change. Otherwise, an InvalidOperationException is raised. To go from playing a specific sound with looping behavior back to not looping another, SoundEffectInstance must be created.
Adjusting the Pitch, Pan, and Volume of Playback
The Pitch, Pan, and Volume properties can be used to adjust their values. These work in the same way as the SoundEffect.Play overload method, which takes volume, pitch, and pan as arguments. . Pitch and Pan are float values that must be between –1 and 1, whereas Volume must be between 0 and 1.
3D Audio Positioning
In most 3D games and
applications, the sounds should come from a source in the game. Jet
engine noise should sound like it comes from the direction of the
engines, the gun fire noises should come from the direction of an enemy,
and car horns should sound like they come from a nearby passing car. We
perceive that these sounds come from different positions in the world
even though they come from a stationary set of speakers. This simulation
occurs because different speakers output different amounts of the
sound, giving the impression that the audio source is a specific
direction from the player. The volume of the sound can also create the
illusion that a sound is close or farther away.
SoundEffectInstance allows for an AudioEmitter and AudioListener by using the Apply3D method. The AudioEmitter
type describes the location, direction, and velocity of the object that
is the source of the sound that is played back using the SoundEffectInstance. To create a new instance of an AudioEmitter, use the default parameterless constructor:
AudioEmitter audioEmitter;
To set the current position of the AudioEmitter using the Position property, set the location to the position of the object emitting the sounds. If you use a Matrix to store the position and orientation of an object in your game, use the Translation property of the matrix to get the position of the object. The AudioEmitter also contains properties for the Forward and Up vectors. Use these vectors to determine the direction that the emitter moves along with the Velocity property. These properties allow for the sound coming from the AudioEmitter to simulate the Doppler effect.
The Doppler effect, in
regards to audio, has to do with the change in an audio emitter’s
frequency when the position and velocity of the object change in
relation to the listener of
the audio waves. As an object moves toward the listener, the sound
waves compress, causing the frequency to increase from the source sound.
After the object passes the listener, the audio waves stretch, causing
the frequency to lower.
AudioEmitter provides a DopplerScale
property. Use this property to scale the Doppler calculation. The
default value is 1.0. Lowering this value decreases the influence of the
Doppler on the final sound output. Increasing the value increases the
Doppler effect on the final sound output.
Note
SoundEffect also provides two static properties that affect the Doppler calculation. Use DopplerScale and DistanceScale to change the influence of the Doppler calculation.
The AudioListener represents the location of the emitter sound. To create an instance of the AudioListener, use the default parameterless constructor:
AudioListener audioListener;
Similar to the AudioEmitter, the AudioListener provides properties for the current Position, Forward, and Up vectors, and the Velocity the listener moves. These properties are used in conjunction with the emitter to determine the final audio output.
After you create the AudioEmitter and AudioListener, you can call the Apply3D method to update the 3D positioning information used by the SoundEffectInstance:
soundEffectInstance.Apply3D(audioListener, audioEmitter);
Adding SoundEffectInstance to Your Game
Now that you learned how the SoundEffectInstance works, you can add more advanced audio playback to your game.
The first thing to add to the game is some local variables to store the SoundEffect, SoundEffectInstance, AudioEmitter, and AudioListener:
// Store our SoundEffect resource
SoundEffect soundEffect;
// Instance of our SoundEffect to control playback
SoundEffectInstance soundEffectInstance;
// Location of the audio source
AudioEmitter audioEmitter;
// Location of the listener
AudioListener audioListener;
Next, load the audio file that is used for the source of audio. In the LoadContent method, add the following lines of code:
// Load the SoundEffect resource
soundEffect = Content.Load<SoundEffect>("beep");
soundEffectInstance = soundEffect.CreateInstance();
audioEmitter = new AudioEmitter();
audioListener = new AudioListener();
You just loaded the sound effect from the file. Then, create a new SoundEffectInstance that is used to control the playback. An AudioEmitter and AudioListener are also created with their default constructors.
In the Update method, you control the playback of the SoundEffectInstance. The first controls you add are for basic sound playback of playing, pausing, stopping, and resuming:
// Play the sound
if (currentKeyboardState.IsKeyDown(Keys.Space) &&
lastKeyboardState.IsKeyUp(Keys.Space))
soundEffectInstance.Play();
// Pause the sound
if (currentKeyboardState.IsKeyDown(Keys.P) &&
lastKeyboardState.IsKeyUp(Keys.P))
soundEffectInstance.Pause();
// Stop
if (currentKeyboardState.IsKeyDown(Keys.S) &&
lastKeyboardState.IsKeyUp(Keys.S))
soundEffectInstance.Stop();
// Resume
if (currentKeyboardState.IsKeyDown(Keys.R) &&
lastKeyboardState.IsKeyUp(Keys.R))
soundEffectInstance.Resume();
Use the Space, P, S, and R keys to control the Play, Pause, Stop, and Resume
methods, respectively. Check the last keyboard state to ensure you
don’t get repeat keyboard presses. Otherwise, when users press the key,
the Play or other methods are called multiple times until users release the key.
Next, use the L key to control whether the instance should loop or not. If the L key is pressed before the first time, the SoundEffectInstance plays, and then the sound loops until it is stopped or paused. As mentioned before, if you set the IsLooped property after the instance has already been played, the InvalidOperationException
is raised. As a developer, you need to determine whether a sound needs
to loop before playing the sound. Add the additional lines of code to
the Update method to add the looping behavior:
// Start or stop looping
if (currentKeyboardState.IsKeyDown(Keys.L) &&
lastKeyboardState.IsKeyUp(Keys.L))
soundEffectInstance.IsLooped = !soundEffectInstance.IsLooped;
Next, add controls to set the Pitch, Pan, and Volume of the SoundEffectInstance:
// Change Pitch
if (currentKeyboardState.IsKeyDown(Keys.Q))
soundEffectInstance.Pitch = -1.0f;
else if (currentKeyboardState.IsKeyDown(Keys.W))
soundEffectInstance.Pitch = 0.0f;
else if (currentKeyboardState.IsKeyDown(Keys.E))
soundEffectInstance.Pitch = 1.0f;
You can use the Q key to lower the pitch to the lowest value of –1.0f and use the E key to set the pitch to the highest value of 1.0f. Use the W key to reset the pitch to the recorded value from the loaded SoundEffect file by setting the pitch to a value of 0:
// Change Pan
if (currentKeyboardState.IsKeyDown(Keys.Z))
soundEffectInstance.Pan = -1.0f;
else if (currentKeyboardState.IsKeyDown(Keys.X))
soundEffectInstance.Pan = 0.0f;
else if (currentKeyboardState.IsKeyDown(Keys.C))
soundEffectInstance.Pan = 1.0f;
You can use the Z key to pan the sound all the way to the left by using a value of –1.0f. Use the C key to pan the sound all the way to the right by using a value of 1.0f. Use the X key to center the playback to the recorded values from the loaded file:
// Change Volume
if (currentKeyboardState.IsKeyDown(Keys.B) &&
soundEffectInstance.Volume <= 0.99f)
soundEffectInstance.Volume += 0.01f;
else if (currentKeyboardState.IsKeyDown(Keys.V) &&
soundEffectInstance.Volume >= 0.01f)
soundEffectInstance.Volume -= 0.01f;
Use the B and V keys to raise and lower the volume of playback, respectively. Check the value of the Volume property to ensure the values stay within the required bounds.
Finally, change the position of the audio source by updating the AudioEmitter.Position property and setting its value on the SoundEffectInstance. Add the following lines of code inside the Update method:
// Move audio emitter
Vector3 emitterPos = audioEmitter.Position;
if (currentKeyboardState.IsKeyDown(Keys.Right))
emitterPos.X += (float)gameTime.ElapsedGameTime.TotalSeconds;
else if (currentKeyboardState.IsKeyDown(Keys.Left))
emitterPos.X -= (float)gameTime.ElapsedGameTime.TotalSeconds;
if (currentKeyboardState.IsKeyDown(Keys.Up))
emitterPos.Z -= (float)gameTime.ElapsedGameTime.TotalSeconds;
else if (currentKeyboardState.IsKeyDown(Keys.Down))
emitterPos.Z += (float)gameTime.ElapsedGameTime.TotalSeconds;
// Store new position
audioEmitter.Position = emitterPos;
// Set the audio emitter and listener values
soundEffectInstance.Apply3D(audioListener, audioEmitter);
You can use the Right and Left arrow keys to move the audio emitter’s position in the X coordinate. Use the Up and Down arrow keys to move the emitter’s position along the Z coordinate. Update the SoundEffectInstance by calling the Apply3D method passing in the AudioListener and AudioEmitter.
Note
Calling Apply3D is required anytime you adjust the AudioListener and AudioEmitter, if you want those changes to affect the audio playback. Just changing the values of AudioListener and AudioEmitter without calling Apply3D does not change the SoundEffectInstance playback even if Apply3D has already been called.