In some applications, particularly touch-screen kiosk apps and
casual games, it is desirable to be able to play a sound
immediately upon a user action. For example, you may want to play a
"click" sound when they press a key on an on-screen keyboard, or
play a series of laser sounds when their on-screen spaceship fires
its weapons.
Please note that this article and the attached sample code was
written using the Silverlight 5 Beta, available at MIX11 in April
2011 and updated for the Silverlight 5 RC.
In previous versions of Silverlight, developers used a variety
of tricks, including pre-loading sounds into a round-robin buffer
of MediaElements in order to provide as low latency as possible.
Windows Phone 7 introduced the XNA SoundEffect class to Silverlight
developers. Silverlight 5 now includes that class as well.
Project Setup
Create a regular Silverlight 5 application. Most of the Xna
libraries are in the core Silverlight runtime (some additional ones
will be in the samples for the beta, and eventually in the SDK for
release), so you don't need to add any additional references.
Add some wav files to the project (see notes below about
format). I have a couple included in the source code with this
post.
In MainPage.xaml, add a button and a click event handler. I
called the button "ClickMe"
Loading and Playing a Sound Effect
Once you have a wav file in your project (as content), you can
load it using the GetResourceStream method of the Application
object. You then pass the stream to the FromStream method of the
SoundEffect class and you're all set to play. Assuming you have a
wav file in the root of your project, with its build action set to
content and its name set to "laser_shot.wav", this is all you need
to play sound. Put this code in the click event handler for the
button.
var laserStream =
Application.GetResourceStream(
new Uri("laser_shot.wav", UriKind.RelativeOrAbsolute));
var effect = SoundEffect.FromStream(laserStream.Stream);
effect.Play();
While that was really easy to do, with SoundEffect, the devil's
in the details. SoundEffect is notoriously picky about what
kind of wav files you provide it. Make sure you are using PCM
encoded files, 8 or 16 bit mono or stereo (no 24 bit floating point
samples) and either 22.5, 44.1 or 48khz sample rates. The
SoundEffect class will fail to load other formats. This is
consistent with the implementation on XNA desktop and
XNA/Silverlight on the phone.
Controlling Volume, Pitch and Pan
The Play method shown above has an overload which accepts three
float values for volume, pitch, and pan.
Parameter |
Value |
volume |
0 to 1 with 1 being loudest |
pitch |
-1 to 1 (low to high) with 0 being
normal pitch |
pan |
-1 to 1 with -1 being far left and 1
being far right |
These parameters are also available as properties on the
SoundEffectInstance class as we'll see in the next examples.
Creating Multiple Instances of Sound Effects and Looping
What if you want to use the same sound effect at multiple places
in your application? One thing you can do is load it once using the
methods shown above, and then obtain a SoundEffectInstance using
the CreateInstance method of the SoundEffect class.
Each sound effect instance has its own volume, pitch, and pan
settings, as well as the ability to loop the sample. For example,
here's how to create a sound effect instance for a looping low
backgound engine sound played at 3/4 volume.
var engineStream =
Application.GetResourceStream(
new Uri("engine_rumble4.wav", UriKind.RelativeOrAbsolute));
_engineEffect = SoundEffect.FromStream(engineStream.Stream);
SoundEffectInstance engineInstance = _engineEffect.CreateInstance();
engineInstance.IsLooped = true;
engineInstance.Pitch = -1.0f; // low sound
engineInstance.Volume = 0.75f;
engineInstance.Play();
In the next example, I use the same source engine sound to
create two overlapping sounds with different pitch and volume
settings. To delay the start of the second one by one second, I use
a one-shot timer.
var engineStream =
Application.GetResourceStream(
new Uri("engine_rumble4.wav", UriKind.RelativeOrAbsolute));
_engineEffect = SoundEffect.FromStream(engineStream.Stream);
SoundEffectInstance engineInstance = _engineEffect.CreateInstance();
engineInstance.IsLooped = true;
engineInstance.Pitch = -1.0f; // low sound
engineInstance.Volume = 0.75f;
engineInstance.Play();
// offset a second instance by 1 second, helps mask my bad loop point
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += (s, ea) =>
{
timer.Stop();
SoundEffectInstance engineInstance2 = _engineEffect.CreateInstance();
engineInstance2.IsLooped = true;
engineInstance2.Pitch = -.75f; // slightly higher sound
engineInstance2.Volume = 0.5f;
engineInstance2.Play();
};
timer.Start();
Now you can have great looping drones in the background with
very low latency sound effects responding to user or application
triggers. Sweet!
Source code for the Silverlight 5 Beta version of this
application may be downloaded via the link below.
A video version of this tutorial will be on Silverlight.net
shortly.