Preparing and using audio clips in XNA

One of the last steps in the production of my game for the XNA Contest was the gathering of all the needed sound clips and the implementation of a little game component that would take care of playing the different audio cues, based on what would happen during the game.

Tips: sound clips

For a simple game like the one I ended making, even if you don't need first class audio, you still need some form of acceptable audio: a great source for such clips is SoundSnap.com (among others). It's a searchable collection of free sounds that can be used in commercial and non-commercial applications.

Editing and mixing

After getting the sound clips, make sure you mix them to the right volume and add echoes, delays or whatever is needed to fit the sound to your game. To do this, I used the open source "Audacity" audio editor:

Audacity screenshot

Audacity is not a particularly pretty program, but it works perfectly and has a bunch of built in effects that can be pretty useful. It's not Cubase by any means, but still...  :)

For instance, to get a "powerup-like" sound, I downloaded an orchestra hit sound from SoundSnap.com and modified it, adding a second slightly delayed hit and changing its pitch. I also added a bit of reverb to make the sound more "spacey".

Original sounds

On the other hand, sometimes you simply can't find good free audio clips on the internet: in this case, it is usually much simpler to record something you can find near to your computer and tweak it to suit your needs. For instance, for the "whirring engine" sound of the spaceship, I resorted to a recording of Kimi Räikkönen's F1 car (gently provided by Denis: roommate and Kimi-worshipper  ;)): the sound was first lowered in pitch and then tweaked with some of audacity's effects. The resulting sound is pretty nice, considering the source.  :D

The same thing can be applied successfully to many other sound clips: footsteps, clanks, crashes and so on, are best made with a cheap microphone and 5 minutes time to record it and edit it - instead of spending hours in googling.

By the way: Denis also made all the speech clips in the game. It was a fairly funny session where we simply threw out crappy ideas and random sentences, recording them on the equally crappy microphone of my laptop.

Audio GameComponent

To actually play the audio clips you produced, you need to import all files into the XACT tool and finally use the appropriate classes provided by the XNA framework. The very simple GameComponent I ended up with exposes this interface:

namespace XnaContestGame.Components {
    public interface IAudioService {

        void PlayCollision();
        void PlayFatalCollision();

        void PlayCollection();
        void PlayCollectionLast();
        void PlayCollectionHalfway();

        void PlayGameStart();

        void PlayVictory();

        void PlayShipEngine(PlayerSpeed speed);

        void PlayMusic(Music music);

    }

    public enum Music {
        Game,
        Win,
        Lose
    }

    public enum PlayerSpeed {
        Stop,
        InstantStop,
        Moving,
        Boosting
    }
}

As you can see, there is no game logic in the audio service: the service simply responds to each method with a sound suited to the particular game state each method represents. The only logic included in the audio component has to do with handling Cue instances, fading the music and gradually adapting the "engine sound" pitch to the current PlayerSpeed that was set by the game logic.

For instance, the implementation of a single sound would be:

public void PlayCollision() {
	if(Enabled)
		soundBank.PlayCue("Hit");
}

The engine sound, on the other hand, adapts gradually when the ship accelerates or stops:

Cue engineLow;
float currEngineSpeed = 0.0f, targetEngineSpeed = 0.0f;
float currEngineVolume = 0.0f, targetEngineVolume = 0.0f;

public void PlayShipEngine(PlayerSpeed speed) {
	switch (speed) {
		case PlayerSpeed.Moving:
			targetEngineSpeed = 0.0f;
			targetEngineVolume = 90.0f;
			break;

		case PlayerSpeed.Boosting:
			targetEngineSpeed = 100.0f;
			targetEngineVolume = 100.0f;
			break;

		case PlayerSpeed.InstantStop:
			currEngineSpeed = 0.0f;
			currEngineVolume = 0.0f;
			goto case PlayerSpeed.Stop;

		case PlayerSpeed.Stop:
			targetEngineSpeed = 0.0f;
			targetEngineVolume = 0.0f;
			break;
	}
}

const string cEngineSpeed = "EngineSpeed";
const string cEngineVolume = "EngineVolume";

public override void Update(GameTime gameTime) {
	if (Enabled) {
		//Adapt engine values
		float diffVolume = targetEngineVolume - currEngineVolume;
		float diffSpeed = targetEngineSpeed - currEngineSpeed;

		currEngineVolume += diffVolume * (float)gameTime.ElapsedGameTime.TotalSeconds;
		currEngineSpeed += diffSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;

		engineLow.SetVariable(cEngineVolume, currEngineVolume);
		engineLow.SetVariable(cEngineSpeed, currEngineSpeed);

		engine.Update();
	}

	base.Update(gameTime);
}

The two values targetEngineSpeed and targetEngineVolume are set accordingly to the current spaceship status (moving, stopped, etc.). At each Update() call we gradually adapt the current values to the target values and then update the variables on the engineLow cue: the two variables "EngineSpeed" and "EngineVolume" have been defined inside the XACT audio editor and are linked to the "whirring engine" sound's pitch and volume.

Using the variable system provided by the XNA framework is a great way to decouple "sound design" from your actual code: just ensure that you declare the right variables in XACT and link them to the correct sound properties. At each update call, you set the new values of each variable and the XNA audio subsystem will take care of the rest (variables can be declared locally for each Cue or globally, like for instance the Doppler factor or the player's position).