As I’ve been migrating towards using C# with Silverlight 2, the best way I’ve found to learn it (aside from the tutorials/labs out there), is to actually write applications. Today, I wrote one that emulates an electronic drum kit.
The idea was to create a set of drum “pads”, associate each pad with a key on the keyboard, and then play an associated MediaElement sound when the key is pressed. The end result looks like this:

You can check out the live version of it here.
The application was created by using a single “drum pad” that has a storyboard on it that will flash the pad red when a key is pressed. The pad was instantiated 9 times and positioned as shown in the screenshot.
Interestingly enough, the most challenging part of creating the app was getting the sound to play. The “press a key, play the sound” idea sounds really simple, and works great.
Once.
After that, the sound changes volume randomly, and does not play consistently. Searching the Silverlight.net forums turned up a history on this issue back as far as June of 2007. Unfortunately, this problem was in Silverlight 1, and it’s still present in Silverlight 2.
I developed a work-around that is probably not ideal, but it does the job.
What I discovered is that each time a MediaElement is triggered, it will play fine one time. So my answer was to dynamically create a new MediaElement each time a key is pressed. The problem with this approach is cleanup. Once 125 or so are added, the application crashes.
Attaching a MediaEnded event to remove the MediaElement when the audio is done playing results in choppy or truncated audio. Instead, I elected to place the dynamically created MediaElements into a specific container on the main canvas, and use MediaEnded to check the number of children in that container.
When a preset threshold has been reached (the default is 25), I run a quick loop that deletes all the children on that canvas, effectively clearing out my cache of “used” MediaElements. This allows my sounds to play each time a key is pressed, and cleans itself up.
Codewise, my MediaElement is defined globally in the Page class as follows:
MediaElement _mediaElement;
When a key is pressed, I play the storyboard for the appropriate instance of the drum pad, instantiate the MediaElement that will hold the sound for that keypress, assign the sound to it, then call a function called “do_MediaElement” that handles tasks common to all key presses.
These steps look like this:
case Key.J:
crashPad.padHit.Begin();
_mediaElement = new MediaElement();
_mediaElement.Source = new Uri(crashDrumSound, UriKind.Relative);
do_MediaElement();
break;
The do_MediaElement function attaches a MediaEnded event to the newly created MediaElement, then adds it to the media element container. It then plays the sound.
void do_MediaElement()
{
_mediaElement.MediaEnded += new RoutedEventHandler(_mediaElement_MediaEnded);
mediaElementContainer.Children.Add(_mediaElement);
_mediaElement.Play();
}
Earlier I mentioned that I didn’t have luck using a MediaEnded event to remove the MediaElement when the sound finishes playing. Instead, it’s used to do a quick check on the number of children in the container I’m using. If the number of children is greater than or equal to the defined threshold, then it does a quick removal of the children on that canvas.
Note that the line that removes the children removes them from the bottom up - always removing the child element at index 0. This is because you can’t just directly count through the children as they are being removed without running into problems - the first time through there are 25 children, then 24, then 23, and so on. If I attempted to remove the node at position 25, it would fail since node 25 doesn’t exist after any node is removed. The loop has to compensate for the fact that it is causing the number of children to reduce each time through.
void _mediaElement_MediaEnded(object sender, RoutedEventArgs e)
{
int count = mediaElementContainer.Children.Count;
if (count >= cleaningThreshold)
{
for (int i = 0; i < cleaningThreshold; i++)
{
mediaElementContainer.Children.RemoveAt(0);
}
}
}
I have some ideas for expanding upon the app and may come back to it at some point in the future. For the time being though, it does what I set out to do, and my son seems to enjoy it quite a bit.



Entries (RSS)