OpenLightGroup Blog

rss

Blogs from OpenLightGroup.net


Silverlight Metronome

I have recently been practicing playing the bass again and during one of the practicing session my friend had suggested some features for a digital metronome. We had used a few JavaScript based ones; however none of them had a few helpful features. These features included: Adjustable tempo, support for different beats per measures, a visual counter displaying the current count of the measure, the ability to allow for multiple measures, the ability to have two different volumes for the tick sound that plays for each beat, the ability to mute the tick for specified beats of a measure and allow the metronome to be run from the computer without being connected to the internet.So since we could not find a single metronome that supported these features, I decided to build one using Silverlight, figuring that it would be a pretty simple exercise. It turns out I was right, well for the most part.

Obviously the most important feature of a metronome is to keep time, this turned out to be so easy it was almost ridiculous. All that needed to be done was to create an empty storyboard with a duration of one second.

    <UserControl.Resources>
        <Storyboard x:Name="Tick"
                    Duration="00:01:00" />
    </UserControl.Resources>

The an event handler was wired up to restart the story board each time it was completed, like this:

this.Tick.Completed += new EventHandler(Tick_Completed);
//Inside the event handler:
this.Tick.Begin();

Now to allow for tempos other than 60 beats per minute, I created a slider control and wired the value to the SpeedRatio of the storyboard. Unfortunately, element to element binding would not work in this case because the storyboard is a resource. So I had to wire up an event handler to the slider’s ValueChanged event that would update the SpeedRatio of the storyboard. I also needed to ensure that the value was a whole number.  A custom behavior I found here: http://blogs.veracitysolutions.com/tag/behaviors/ was all it took to snap the slider to whole number.

<Slider x:Name="BeatsPerMinute"
        Minimum="20"
        Maximum="200"
        Value="60"
        Margin="0"
        Orientation="Vertical"
        SmallChange="1"
        HorizontalAlignment="Center"
        Height="180">
    <i:Interaction.Behaviors>
        <OpenLightGroup_AgMetronome:SnappingSliderBehavior />
    </i:Interaction.Behaviors>
</Slider>

With that I had the foundation laid for a metronome with an adjustable tempo. I then wired up a toggle button to control starting and stopping the metronome to allow for a different look while the metronome is running. So now we need to be able to set the number of beats per measure so we know what to count to. For this, I simply used a combobox to allow you to select from 3/4, 4/4 and 5/4 times and wired up an event handler to set a field that holds the count for each measure. Then I added some code to the tick completed event handler to track the current beat and reset it to one once the maximum count is reached based on the beats per measure that is currently selected. I also set the content of the toggle button to show what the current count is.

this._count++;
if (this._count > this._maxCount)
    this._count = 1;
this.PlayButton.Content = this._count.ToString();

At this point, if you click the play button the metronome will count the number of beats selected pre measure and display them in the button. You can also adjust the tempo to count faster or slower. Next, I added the ability to have multiple measure displayed so the metronome would count to the max count of the measure and below show which count it was on in all of the measures. To do this I added a wrap panel with a number of beats added to it based on the beats per measure and the number of measures selected. To allow for the feature of stressing certain beats I decided to create a beat user control that would have three states: Normal, Medium and High. These states are shown by changing the color of the text that shows the number of the beat. The state of the beat is selected by click on the beat to cycle through the options. All beats start in the normal state, but change to medium upon the first click and high on the second click. Clicking the beat a third time cycles it back to the normal state.

this.StressLevel++;
if (this.StressLevel > 3)
    this.StressLevel = 1;
if (this.StressLevel == 1)
    this.MeasureNumberText.Foreground = this.NormalStressCountBrush;
else if (this.StressLevel == 2)
    this.MeasureNumberText.Foreground = this.MediumStressCountBrush;
else if (this.StressLevel == 3)
    this.MeasureNumberText.Foreground = this.HighStressCountBrush;

When the metronome loads, it defaults to four beats per measure and only one measure which then adds four beats set to the normal state to the beats wrap panel. By clicking the up arrow on the number of measures control the proper number of beats will be added to the wrap panel. Clicking the down arrow will remove the appropriate number of beats from the wrap panel. To prevent issues with the count, the metronome is paused and then restarted once the correct number of beats are added.

void NumberOfMeasures_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
    if (this.PlayButton.IsChecked.HasValue && this.PlayButton.IsChecked.Value)
    {
        this.Tick.Stop();
    }
    double difference;
    if (e.NewValue > e.OldValue)
    {
        difference = (e.NewValue - e.OldValue) * this._maxCount;
        for (int i = 0; i < difference; i++)
        {
            this.AddBeat();
        }
    }
    else
    {
        difference = (e.OldValue - e.NewValue) * this._maxCount;
        for (int i = 0; i < difference; i++)
        {
            if (this.Beats.Children.Count > 0)
            {
                UIElement child = this.Beats.Children.Last<UIElement>();
                this.Beats.Children.Remove(child);
            }
            else
            {
                break;
            }
        }
    }
    if (this.PlayButton.IsChecked.HasValue && this.PlayButton.IsChecked.Value)
    {
        this.Tick.Begin();
    }
}

To show which beat the metronome is currently on within the measures I increase the border thickness and scale the beat to 1.1. This means I also need to reset the style of the previous beat each time the count is increased. I also set the color of the text of the play button equal to the color of the font of the current beat. This is done to show the current stress level inside of both the beat in the measures and the current count within the beats per measure. All of this is done inside of the tick completed event handler.

this.ResetPreviousBeat();
this.StyleCurrentBeat();
Beat currentBeat = (Beat)this.Beats.Children[this._currentBeat];
this.PlayButton.Foreground = currentBeat.MeasureNumberText.Foreground;

Up until this point everything has been pretty standard coding and went fairly smoothly. Well with the exception of some silly math/logic issues with looping and adding/removing the proper number of beats when changing the number of measures. This leaves us with two features left to implement, playing the tick sound at the correct volume based on the current beats stress level and the ability to run the application “out of browser.”

I will started by creating the sound file that will be used to play the “tick” sound for selected beats. To do this I used the “Generate Click Track” feature of the Audacity sound editor program.

image

After exporting the track as an mp3, I imported the mp3 into Expression Encoder to clip it to a single tick and encode it as a wma file. Once I had encoded a suitable sound file, I added a resource file to the project and added the sound file as a resource. This allows the sound file to be played when the application cannot access the hosting site. With the sound file in place, I added a media element control to the metronome control to allow the sound to be played programmatically. However, since the sound file is now compiled into the application the source has to be set during the loaded event instead of in the xaml.

_soundStream = new MemoryStream(SourdResource.TickSound);
this.TickPlayer.SetSource(_soundStream);

 

We are finally ready to use the stress level of the current beat to determine if the sound should be played and if so at what volume level. This too is done inside of the tick completed event handler. To make the code in the tick completed event handler easier to read I made the PlayTick method to handle setting the volume and playing the sound. Notice that the position of the media element must be set to zero each time it is played.

private void PlayTick(double volume)
{
    this.TickPlayer.Volume = volume;
    this.TickPlayer.Position = TimeSpan.FromSeconds(0.0);
    this.TickPlayer.Play();
}

 

The finished tick completed event handler now looks like this:

void Tick_Completed(object sender, EventArgs e)
{
    this._count++;
    if (this._count > this._maxCount)
        this._count = 1;
    this.PlayButton.Content = this._count.ToString();
    this.ResetPreviousBeat();
    this.StyleCurrentBeat();
    Beat currentBeat = (Beat)this.Beats.Children[this._currentBeat];
    this.PlayButton.Foreground = currentBeat.MeasureNumberText.Foreground;
    switch (currentBeat.StressLevel)
    {
        case 2:
            this.PlayTick(0.75);
            break;
        case 3:
            this.PlayTick(1.0);
            break;
        default:
            break;
    }
    this._currentBeat++;
    if (this._currentBeat > this.Beats.Children.Count - 1)
        this._currentBeat = 0;
    this.Tick.Begin();
}

 

The final feature to implement is by far the easiest one. To enable the application to be run from a disconnected computer all that need to be done is to open the properties of the Silverlight application and check the “Enable running application out of the browser” checkbox.

You can view the finished product at the following link: AgMetronome

The source code for the Silverlight project can be downloaded here: AgMetronome Source

I hope the tutorial or the application itself proves to be useful for you. As always I appreciate any feedback, questions or comments regarding this post.





Comments are closed.
Showing 4 Comments
Avatar  Ian T. Lackey 8 years ago

Thank you Sandy for your kind comments! I am glad to hear you are enjoying the articles.

Avatar  sandy 8 years ago

Been reading your excellent blog for quite a few weeks now, and i am enjoying many of your excellent topics.

Avatar  sandy 8 years ago

The best thing about this post is that, it can convince masses. Its language is easy and conveys the theme of the article in a most appropriate way. The write is not just playing with the words but he is actually providing use full information. The content is unique and depicts the theme very well

Avatar  Art Scott 8 years ago

Take five cool.&lt;br&gt;&lt;br&gt;Thanks, Art