Welcome to Pete Brown's 10rem.net

First time here? If you are a developer or are interested in Microsoft tools and technology, please consider subscribing to the latest posts.

You may also be interested in my blog archives, the articles section, or some of my lab projects such as the C64 emulator written in Silverlight.

(hide this)

Silverlight 5: Using the SoundEffect Class for Low-Latency Sound (and play WAV files in Silverlight)

Pete Brown - 13 April 2011

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.

       

Source Code and Related Media

Download /media/76764/soundeffectdemo.zip
posted by Pete Brown on Wednesday, April 13, 2011
filed under:        

13 comments for “Silverlight 5: Using the SoundEffect Class for Low-Latency Sound (and play WAV files in Silverlight)”

  1. Petesays:
    @fool

    I haven't done anything with that, but I don't believe that will be in SL5. The closest we have is the custom MediaStreamSource stuff, but latency there is higher.

    I'd love to see DSEI so I could create another synthesizer app :)

    Pete
  2. foolsays:
    I'm already using a custom MediaStreamSource in SL 4, but it leaves a lot to be desired - especially on WP7 devices (where it doesn't work at all on my HTC device). I'm also already using DynamicSoundEffectInstance in XNA 4, and it works much much better, both on the desktop and WP7 devices. The conclusion is obvious - DSEI really needs to be in the SL 5 API for the same reasons that SEI is being added.
  3. Justin Lsays:
    about using SoundEffect with an overloaded stream, the runtime seems to call the stream's Read() method once for each 4-byte wave header entry and then it reads the entire file payload in a single block, failing to play anything if it can't immediately read the entire file, so it's currently not possible to play a file that can't fit in memory, to overload the stream to give dynamic content while the sound is playing, or to play from a network source without prebuffering the entire file
  4. Petesays:
    @Dan

    I wouldn't compare it to ASIO or similar, as those are highly optimized for the use. I haven't measured latency here, but since creating dynamic sounds isn't a primary use-case, I'd rule it out for creating soft synths.

    Pete
  5. olivier dahansays:
    Hi Pete,

    I'm testing the new sound API with SL 5 final and I have a problem with little sound files : they are not playing...

    I used your test project to be sure, replacing the laserbeam sound with one of my file, and it does not play.
    Format is the same (44.1hz, 16, stereo, pcm). The only difference is that your files are bigger than mines.
    THe laser shot is about 220 KB, since my files are very short sound (drums), about just 5 or 6 KB.

    Did you "hear" something about this problem with small wav files ?
    Thanks !
  6. olivier dahansays:
    Hi, it's me again :-)

    Some other test results : Any sound having a duration under 300 ms is not played correctly.
    Adding some silence at the end of short sounds to make the wav at least 300 ms is solving the problem...

    There is a bug...
  7. Petesays:
    @Olivier

    Have you confirmed that it's the length of the sample? 300ms is definitely a short sample (1/3 a second). Can you email me pete DOT brown at Microsoft DOT com with your sample project with the WAV file included?

    THanks.

    Pete
  8. Phillip Trelfordsays:
    @Olivier, @Pete,

    I've been experiencing a similar issue with short sound effects (in my case beeps) not being audible. Longer sound effects do seem to fire (over 1/4 second), shorter sound effects sometimes don't.

    I haven't been able to pinpoint the issue, last night a project I created to reproduce the issue actually produced audible sounds at all lengths. This morning running the same program untouched I can't hear the 100ms sample anymore..@Pete I've sent you this project by e-mail.

    For now I've reverted to using the MediaElement class instead.

    Phil

Comment on this Post

Remember me