We have already discussed the fact that the SoundEffect
class is not to be used for playing music, but that doesn't mean that
we can't provide backing tracks for our games. XNA provides separate
functionality for playing music, but in the context of Windows Phone 7
there are some things that we need to be aware of in order for our game
to meet Microsoft's certification requirements.
Let's look at how (and when) you can play music within your games.
1. To Play or Not To Play
The certification
requirement complication for Windows Phone 7 games revolves around the
fact that one of the other primary uses for the device is as a media
player. The operating system has a flexible media library that allows
music and other audio content to be played on the device, even when the
media library has been moved to the background.
As a result, it is
entirely possible that, when the user launches your game, music is
already playing in the background. Microsoft has decided that this
existing music should take priority over your game music and that you
must not stop it from playing without either directly asking the user
for permission (by displaying a dialog box, for example, asking if the
user wants to play the game music instead of the current audio track) or
by providing a configuration option that allows it to be configured on a
more permanent basis (in which case the option must default to not
interrupting the existing media playback). Without observing this
requirement, your game will be rejected when you submit it to the
Windows Phone Marketplace.
We will look at how to perform
this check (and how to pause the background music if appropriate) in a
moment, but for now please bear in mind the need to do this.
Note that this check applies only to playing music with the MediaPlayer
class, which is the class that provides access to the music player
functionality. Sound effect playback is permitted even if the device is
already playing music, so no special checking needs to be performed for
sound effects. The certification requirements state that sound effect
objects should not be used for playing background music, however, so
this isn't a way to bypass the requirement.
2. Adding Music to your Project
Music is also added to the
Content project. Unlike sound effects, music is expected to be in either
MP3 or WMA format. This is a good thing because such formats produce
much smaller files than WAV files due to the way they are compressed.
Because music is likely to be much longer in duration than a sound
effect, having compression applied is essential for keeping the size of
your finished game under control.
MP3 files have taken over the
world during the last decade and must surely form one of the most widely
known file formats in existence. Sound files encoded using MP3 are
compressed using a lossy compression algorithm. Although this means that
there is some degradation of the audio when it is played back (just as
there is a loss of image quality with JPG images), in most cases the
quality loss is virtually or completely unnoticeable.
MP3 can compress audio
data to different degrees, and the higher the compression the greater
the quality loss on playback. The compression level is set when the MP3
is created by specifying a bit rate, which controls how many kilobits of
data can be used to store each second of compressed audio. Compressing
files at a bit rate of 128 kilobits per second will typically reduce CD
quality audio to about 9 percent of its original file size—a massive
saving.
Windows Media Audio (WMA)
files are similar in approach to MP3s, also using a proprietary
Microsoft compress to provide lossy compression (although a lossless
variation is available). Microsoft claims that WMA files can be created
that have the same quality level as an MP3 while using only half the
storage space, though there are those who dispute this claim.
Nevertheless, it is still a capable format and certainly worth
considering as a format for your game music.
NOTE
Audacity is capable of exporting sounds in both MP3 and WMA format.
When you add a music file to the content, the properties show the Content Processor as Song, as shown in Figure 1.
"Song" is in fact the term
that XNA uses to refer to a piece of music, and its class names reflect
this. To load a song, we use the Song class along with the usual Content.Load call. In the game framework, we store a collection of songs within the GameHost class inside a Dictionary named Songs. Listing 1 shows the instruction within a project's LoadContent function that loads a song into the framework.
Example 1. Loading a song into the game framework from the Content project
// Load our song
Songs.Add("2020", Content.Load<Song>("Breadcrumbs_2020"));
|
3. Playing the Music
Playing a song is very easy: just call the static MediaPlayer.Play function, passing in the Song
object that you have loaded. Only one song can play at a time; trying
to play a second song will stop the first song from playing.
However, we have the
tricky issue of whether we are allowed to play music or not because, if
the device is already playing background music, we must leave it alone.
This is determined using the MediaPlayer.GameHasControl property. If it returns true, we have full access to playing music; if it returns false,
there is music already playing; and unless the user has explicitly
confirmed that they want for our game to take control, we must allow it
to continue. Listing 2 shows some code from the LoadContent function of the BackgroundMusic
example project . If it detects that media
are already playing, it doesn't even attempt to load the song;
otherwise, the song is loaded and played.
Example 2. Checking whether the game is allowed to play music and then loading and starting playback
// Load songs
if (MediaPlayer.GameHasControl)
{
// Load our song
Songs.Add("2020", Content.Load<Song>("Breadcrumbs_2020"));
// Play the song, repeating
MediaPlayer.IsRepeating = true;
MediaPlayer.Play(Songs["2020"]);
}
|
Assuming that we can play our music, there is a number of other methods and properties that we can access from the MediaPlayer class to affect the way in which the music plays:
Pause and Stop can be used to halt playback. Just as with sound effects, Pause will remember the position at which the song was paused, allowing it to be later resumed; whereas Stop will discard the position. Either way, you can call the Resume method to start the song playing again.
IsMuted and Volume provide control over the playback volume. IsMuted is a boolean property that will completely silence the song without pausing it, while Volume allows the playback volume to be faded between silence (0.0) and the current device volume (1.0).
IsRepeating
allows you to set the song to loop endlessly. This looping is often
very useful for games because they tend to have background music that
plays repeatedly the whole time the game is running. There is a slight
issue with repeating music to be aware of, however, as we will discuss
in a moment.
PlayPosition returns a TimeSpan
object detailing the current playback time through the song. This can
be used to create a playback time display, as will be demonstrated
shortly.
Each loaded Song
object also has a series of interesting-looking properties that might be
interrogated. Unfortunately, it turns out that they aren't too useful
after all. Properties are available to provide information on the song's
Album, Artist, and Genre,
among other things, but when songs are read from a game's Content
project, none of them is populated even if the information is present
inside the MP3 file. These properties are instead used to access data on
songs contained within the device's media library.
One useful property that we can read from the Song is its Duration, which also returns a TimeSpan, this time containing the length of the song. We can use this alongside the MediaLibrary.PlayPosition for our playback time display. The BackgroundMusic example project displays such a timer and generates its text as shown in Listing 3. An example of the display it produces is shown in Figure 2.
Example 3. Displaying the current position and duration of a song
// Are we playing a song?
if (MediaPlayer.GameHasControl)
{
// Yes, so read the position and duraction
currentPosition = MediaPlayer.PlayPosition;
duration = Songs["2020"].Duration;
// Display the details in our text object
TextObject positionText = (TextObject)GameObjects[0];
positionText.Text = "Song position: "
+ new DateTime(currentPosition.Ticks).ToString("mm:ss") + "/"
+ new DateTime(duration.Ticks).ToString("mm:ss");
}
|
This is nearly all you need to
know about playing music, but there is one other thing that you should
be aware of, and it is a rather annoying feature of XNA's media playback
functionality. When you set a song to loop, there is a very slight
pause between the song finishing and restarting. If your song fades to
silence at the end, it will be unnoticeable, but if you try to loop the
song so that it restarts seamlessly, this pause can be quite noticeable.
Although there is nothing you
can do to eliminate the pause, you can make a minor adjustment to the
song to slightly reduce its impact. The pause can be irritating on two
levels: it enforces a brief moment of silence and it results in the
music playback being thrown slightly off-beat. This second problem can
be very distracting indeed.
To eliminate the timing
problem, edit your music file and trim about one-tenth of a second from
the end. Then save it and use this modified version in your game. The
pause will then be offset by a corresponding gap in the music track,
allowing the beat to continue uninterrupted.