Tuesday, April 28, 2009

Beat Box Application - Part 1 overview

I was searching the web for Silverlight applications the used the WPF MediaElement, and I came across these applications / demos:

John Papa has a nice chord finder application:
http://johnpapa.net/silverlight/silverlight-chord-finder/

Pete Brown has built a virtual synthesizer keyboard:
http://community.irritatedvowel.com/blogs/pete_browns_blog/archive/2009/03/23/Creating-Sound-using-MediaStreamSource-in-Silverlight-3-Beta.aspx

During the search I found eight mp3 files that play drum sounds "crash; hhclosed; hhopen; kick; snare; tom1; tom2; tom3". This and my recent knowledge of creating custom layout panels inspired me to create a beat box application. This is what I was able to build in about 3 hours.


video

I have always been interested in the relationship between math and music, so I constructed the API so the user could fill the beat box using lambda expressions. But before I go into that I need to explain the math used to control the box.





















In the picture, there are 2 custom panels, the BeatPanel is horizontal. It is composed on many KitPanel's. Each KitPanel is composed of 8 Ellipses, one for each sound.

All 8 sounds in the drum kit can be played at the same time. Each beat is played if the opacity of the drum element is .6 or greater. Here is the code:


public void Play()

{

Background = new SolidColorBrush(Colors.Black);

for (int i = 0; i < 8; i++)

{

Ellipse oEllipse = Children[i] as Ellipse;

if (oEllipse.Opacity < .6)

continue;

StreamResourceInfo oInfo = Sounds[i];

MediaElement oElement = Elements[i];

oElement.SetSource(oInfo.Stream);

}

}



I used the threshold value of .6 on opacity because I wanted to make it possible to write multiple math expressions that, if applied to the same section of the beat, could cancel or enhance the pattern already in place.

To do this I used 2 techniques. The first was a lambda expression:

KitAction(0, xx => (xx % 16) == 1); //Crash every 16 beat

KitAction(4, xx => (xx % 4) == 1); //snare every 4 beats

public void KitAction(int iIndex, Func<int,bool> oOnFunction)

{

for (int i = 0; i < MaxCount; i++)

{

double dValue = oOnFunction(i) ? 1.0 : 0.1;

BlendBeat(iIndex, i, dValue);

}

}



Using the lambda expression lets you describe a beat pattern with a simple rule; it lays down the basic beat for a complete song.

Fill patterns are not as regular, so I created an API to describe them with a string of X's and Spaces:


BlendBeat(6, 0, "XXX XX XXX XXXXXXXX XX"); //Tom2 fill from opening

BlendBeat(7, 0, "XXX X XXX XX XXXXX X"); //Tom3 fill from opening


public int BlendBeat(int iIndex, int i, string sMask)

{

for (int j = 0; j <= sMask.Length-1; j++)

{

double dValue = sMask[j] == ' ' ? 0.0 : 1.0;

try

{

KitPanel oKit = Children[i + j] as KitPanel;

oKit.BlendBeat(iIndex, dValue);

}

catch

{

return -1;

}

}

return i + sMask.Length;

}


In the process of building this example, I documented four "A-Ha" moments about custom panels and media elements. These are things that took longer then they should have until I learned the tricks and tips of Silverlight development. I will explain them in parts 2 and 3.

The complete source code for beat box can be downloaded here:

http://drumpanel.codeplex.com/

No comments: