Lessons.XNA.08

XNA Programming Lesson 8:
Music and Sound Effects

Sound Effects

Sound is an essential aspect of any modern videogame. Try playing a game on mute and you will understand just how important sound is to videogames. There are a number of different file types that can be used for sounds, but we will focus on WAV files and MP3 files. WAV, or Waveform Audio File Format, is an audio file format typically used for uncompressed audio. This file type is normally used for sound effects in XNA games since they tend to be short so the larger file size of WAV files is not much of an issue.

  • 1. Create a new project called "DrumPad".
  • 2. Download the following four WAV files and add them to your content pipeline:

Cymbal Kick Snare Top

The procedure for adding sound effects for your game is the same as adding most content. We will have to create SoundEffect variables and load them in LoadContent.

  • 3. Add the following code in red to the top of the Game1 class:

namespace DrumPad
{
   /// <summary>
   /// This is the main type for your game
   /// </summary>
   public class Game1 : Microsoft.Xna.Framework.Game
   {
      GraphicsDeviceManager graphics;
      SpriteBatch spriteBatch;

      SoundEffect cymbal;
      SoundEffect kick;
      SoundEffect snare;
      SoundEffect top;

Now we need to load the sound effects in the LoadContent method. The SoundEffect class can only be used with WAV files. We will learn of another class that can be used with other audio types later.

  • 4. Add the following code in red to the LoadContent method:

protected override void LoadContent()
{
   // Create a new SpriteBatch, which can be used to draw textures.
   spriteBatch = new SpriteBatch(GraphicsDevice);

   // TODO: use this.Content to load your game content here
   cymbal = Content.Load<SoundEffect>("cymbal");
   kick = Content.Load<SoundEffect>("kick");
   snare = Content.Load<SoundEffect>("snare");
   top = Content.Load<SoundEffect>("top");


   base.Initialize();
}

Now we need to use the keyboard to control when our sounds are played. We will do this in the Update method.

  • 5. Add the following code in red to the Update method:

public override void Update(GameTime gameTime)
{
   // Allows the game to exit
   if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
      this.Exit();

   KeyboardState keyboard = Keyboard.GetState();

   if (keyboard.IsKeyDown(Keys.C))
      cymbal.Play();


   base.Update(gameTime);
}

You may notice that the sound effect continues to play as you hold down the key. We want the sound effect to play exactly once when the key is pressed. To do this we will learn a common videogame concept known as edge detection.

Edge Detection

What we want to do is only play the cymbal when the button is first pressed down not when it is held down. So we want to determine when the button is being pressed down, but was previously released during the last update. Edge detection is used when you want to determine if a button has just been pressed down.

  • 6. Add the following code in red to the top of the Game1 class:

namespace DrumPad
{
   /// <summary>
   /// This is the main type for your game
   /// </summary>
   public class Game1 : Microsoft.Xna.Framework.Game
   {
      GraphicsDeviceManager graphics;
      SpriteBatch spriteBatch;

      SoundEffect cymbal;
      SoundEffect kick;
      SoundEffect snare;
      SoundEffect top;

      KeyboardState prevKeyboard;

Creating another KeyBoardState variable allows us to keep track of two different keyboard states. Since it is declared at the top it will hold its value from one Update call to the next so we can use it to store the previous state of the keyboard.

  • 7. Add the following code in red to the Update method:

public override void Update(GameTime gameTime)
{
   // Allows the game to exit
   if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
      this.Exit();

   KeyboardState keyboard = Keyboard.GetState();

   if (keyboard.IsKeyDown(Keys.C) && prevKeyboard.IsKeyUp(Keys.C))
      cymbal.Play();

   prevKeyboard = keyboard;

   base.Update(gameTime);
}

We check for to see if the "C" key was previously up as well as if it is currently down. We then set the prevKeyboard equal to the current keyboard state at the end of the method since the current state will become the previous state for the next Update method call.

  • 8. Add code to play the remaining three sound effects.

Playing Music

Playing music is a bit different than playing sound effects. While you could use the SoundEffect class for music it can only play WAV files, which are uncompressed. If you have a song that is a few minutes long it can lead to a large file that can use a lot of memory. Because of this the Song class can be used for MP3 or WMA files, which are compressed audio files that will take up less memory than their WAV counterpart.

  • 9. Add this file to your content pipeline.
  • 10. Add the following code in red to the top of the Game1 class:

namespace DrumPad
{
   /// <summary>
   /// This is the main type for your game
   /// </summary>
   public class Game1 : Microsoft.Xna.Framework.Game
   {
      GraphicsDeviceManager graphics;
      SpriteBatch spriteBatch;

      SoundEffect cymbal;
      SoundEffect kick;
      SoundEffect snare;
      SoundEffect top;

      Song mainSong;

      KeyboardState prevKeyboard;

  • 11. Add the following code in red to the LoadContent method:

protected override void LoadContent()
{
   // Create a new SpriteBatch, which can be used to draw textures.
   spriteBatch = new SpriteBatch(GraphicsDevice);

   // TODO: use this.Content to load your game content here
   cymbal = Content.Load<SoundEffect>("cymbal");
   kick = Content.Load<SoundEffect>("kick");
   snare = Content.Load<SoundEffect>("snare");
   top = Content.Load<SoundEffect>("top");
   mainSong = Content.Load<Song>("song");

   base.Initialize();
}

Let's use the spacebar to play and pause the song. The MediaPlayer class will let use play and pause the song as well as loop it to make sure to continuously plays.

  • 12. Add the following code in red to the Update method:

public override void Update(GameTime gameTime)
{
   // Allows the game to exit
   if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
      this.Exit();

   KeyboardState keyboard = Keyboard.GetState();

   if (keyboard.IsKeyDown(Keys.C) && prevKeyboard.IsKeyUp(Keys.C))
      cymbal.Play();

   if (keyboard.IsKeyDown(Keys.Space) && prevKeyboard.IsKeyUp(Keys.Space))
   {
      if (MediaPlayer.State == MediaState.Paused)
         MediaPlayer.Resume();
      else if (MediaPlayer.State == MediaState.Playing)
         MediaPlayer.Pause();
      else
      {
         MediaPlayer.IsRepeating = true;
         MediaPlayer.Play(mainSong);
      }
   }


   prevKeyboard = keyboard;

   base.Update(gameTime);
}

The code above uses edge detection to determine if the spacebar has been pressed. If it has been pressed it checks the state of the media player. If the player is currently paused it resumes playing the song and if it is currently playing then it pauses the song. If it isn't playing or paused that means we haven't loaded a song yet so we need to call the Play method with the song as the parameter. Checking the state of the media player is essential before doing anything with it since you can run into problems otherwise.