Sounds on Windows Phone 7 – KnockOnWood

Icon99x99

Last week my KnockOnWood Windows Phone 7 app was released in the marketplace. The basic idea of the application is that it gives you something to knock on if no real wood is available. In other words, it should play a sound effect when the screen is touched.

 

From Wikipedia:
Knocking on wood, and the spoken expression “knock on wood,” are used to express a desire to avoid “tempting fate” after making some boast or speaking of one’s own death.

In this application I wanted to try out to work with sounds for another application I’m working on. I had only a few requirements for this application:

    1. Multitouch. The sound should be triggered even if the screen is touched already.
    2. Fast. You  should not have to wait for a sound to finish playing when you tap the screen. Just play it immediately.

Multitouch

Because of requirement 1, Multitouch, I decided to go for the static Touch class instead of using normal mouse events. To handle touches to the screen you’ll have to subscribe to the FrameReported event. I’ve added the following line to the constructor.

Touch.FrameReported += this.TouchFrameReported;

I figured a sound should only be played if a touch point is added to the screen. So I’ve created a private field _current to keep track of the number of touch points. If the first, or primary, touch point is taken off the screen (TouchAction.Up) this number should be reset to 0. If the number of touchpoints at a certain moment exceeds the stored number the sound is played.

private void TouchFrameReported(object sender, TouchFrameEventArgs e)
{   
    int newcurrent = e.GetTouchPoints(this.LayoutRoot).Count();
    if (e.GetPrimaryTouchPoint(this.LayoutRoot)
	 	.Action == TouchAction.Up)
    {
        newcurrent = 0;
    }
    if (newcurrent > this._current)
    {
	// play sound
    }
    this._current = newcurrent;
}

Sound

At first I thought about using a single media element to play the knocking sound. But, with the first few tests it became clear a media element isn’t fast enough. Starting, stopping, rewinding and playing again didn’t work as easy as I hoped. I had to turn over to XNA for playing sounds.

Playing a SoundEffect when the screen is touched was still not fast enough…  So, I tried SoundEffectInstance, which holds an instance of SoundEffect ready to be played. This worked a lot better, but stopping, rewinding and starting an instance was not completely right. Tapping on the screen wasn’t quite right.

Because I wanted to play the sounds as fast as possible I’ve came up with the following solution. I’ve created an array of SoundEffectInstance and cycled through that on every tap. Because the knocking sound is pretty short, I’ve set the buffer size to 10 instances. This didn’t cause anything notable what tapping the screen.

Throughout the code I use a constant and two fields:

private const int BufferSize = 10;
private readonly SoundEffectInstance[] _soundEffectInstances;
private int _currentInstance;

In the constructor the _soundEffectInstances array is constructed and filled with instances of a SoundEffect.

this._soundEffectInstances = new SoundEffectInstance[BufferSize];

var effect = this.LoadSound("woodknock.wav");

for (int i = 0; i < BufferSize; i++)
{
    this._soundEffectInstances[i] = effect.CreateInstance();
    this._soundEffectInstances[i].IsLooped = false;
}

The LoadSound method takes a path and filename of a content file in the solution, reads it and returns a SoundEffect.

private SoundEffect LoadSound(string path)
{
    using (Stream stream = TitleContainer.OpenStream(path))
    {
        return stream != null ? SoundEffect.FromStream(stream) : null;
    }
}

When the screen is tapped, the TouchFrameReported event handler is called. In this method I did all the playing from the array of sound instances and keeping track of which instance to play.

private void TouchFrameReported(object sender, TouchFrameEventArgs e)
{
    int newcurrent = e.GetTouchPoints(this.LayoutRoot).Count();
    if (e.GetPrimaryTouchPoint(this.LayoutRoot)
             .Action == TouchAction.Up)
    {
        newcurrent = 0;
    }
    if (newcurrent > this._current &&
            this._soundEffectInstances != null &&
            this._currentInstance < BufferSize &&
            this._soundEffectInstances[this._currentInstance] != null)
    {
        this._soundEffectInstances[this._currentInstance].Play();
        this._currentInstance++;
        if (this._currentInstance >= BufferSize)
        {
            this._currentInstance = 0;
        }
    }
    this._current = newcurrent;
}

And that’s about it. screenshot1

Wrap-up

Playing one sound at a time without having to deal with speed and responsiveness is not that hard. Playing multiple instances is a bit harder. But, as you’ve just seen, can be done with only a few lines of code.

If you have a Windows  Phone, please give the application a try and show your support by rating it. The KnockOnWood application can be found in the marketplace.

For future tutorials on Windows Phone, Silverlight and Expression Blend please subscribe to the rss feed or follow me on Twitter (@Sorskoot).

1 comment for “Sounds on Windows Phone 7 – KnockOnWood

Leave a Reply

%d bloggers like this: