RIA Development Center
Features Tips Events Videos
In Adam Flater's Flex blog, he suggests for teams getting started with RIAs that they:
  • Use Source Control
  • Create an easy designer/developer workflow
  • Select a micro-architecture, patterns and frameworks that work for your group
    Read more
    See more tips
  • Get regular email alerts when we publish new features!
    DevX RIA Development Update

    More Newsletters
    Guitar Tuner Vista Gadget Using Silverlight (cont'd)
    More Resources
  • MSDN Evaluation Center: Microsoft Visual
        Studio 2005
  • Microsoft Silverlight Software Development Kit
  • Guitar Tuner in Action

    Now, that you have finished the presentation, you need to start adding some punch to it. There are three goals you want to accomplish in this section:

    1. Make the strings clickable and, when clicked, you would like to shown an indication that it is being played.
    2. When a string is being played, and you hit the red stop button, the indication that you implemented the above should disappear.
    3. Finally, whenever the indication is on, you want to play the sound of the respective string and when the indication stops, the sound should stop playing too.

    On to goal 1. The first step is to give a visual indication that the strings and the stop button are clickable when you hover the mouse over them. The standard indicator for such things is a hand cursor, so open the XAML file again and add a Cursor attribute to all the strings and the stop button like below:

    <Rectangle x:Name="LowEString" Width="315" Height="5"
               Canvas.Left="15" Canvas.Top="37" Cursor="Hand">
    

    We now need to add a mouse button handler to perform an action when the strings are clicked. The way to do it is to the event name and set the handler function like below. For now, you use the same handler function for all six strings, so please repeat this step for all the string elements.

    <Rectangle x:Name="LowEString" Width="315" Height="5"
               Canvas.Left="15" Canvas.Top="37" Cursor="Hand"
               MouseLeftButtonDown="stringPlucked">
    

    Open the guitartuner.js file in notepad and add the stringPlucked function like below:

    function stringPlucked(sender, args)
    {
    }
    

    You need to do something here. For the sake of this article, do this: When a string is plucked, make a green indicator blink at the location where the bridge intersects the plucked string. For this, you add a new element called [stringname]indicator like below:

    <Rectangle x:Name="LowEString" Width="315" Height="5"
               Cursor="Hand" Canvas.Left="15" Canvas.Top="37"
               MouseLeftButtonDown="stringPlucked">
       <Rectangle.Fill>
          <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
             <GradientStop Color="Brown" Offset="0.0"/>
             <GradientStop Color="White" Offset="0.5"/>
             <GradientStop Color="Brown" Offset="1.0"/>
          </LinearGradientBrush>
       </Rectangle.Fill>
    </Rectangle>
    <Rectangle x:Name="LowEStringIndicator" Height="15" Width="10"
               Canvas.Left="10" Canvas.Top="30" Fill="#00FF00"
               Opacity="0.0" >
    </Rectangle>
    

    Points to be noted here are: The Opacity attribute. Setting this to 0 means that the whole element is transparent and as such the background shows through. The default opacity is 1.0 which means the whole element is visible. You want the indicator to be transparent always except when the corresponding string has been plucked. So, if you now refresh your browser, you wouldn't see this element simply because its opacity has been set to 0.0.

    What you want to do now is to kick off a timed operation where, when the stringPlucked function is called, you gradually change the opacity of this element from 0 to 1 giving it a cool blinking look, enough to give the user some feedback as to what string is being played, so s/he can play the same string on his/her guitar. One possible way could be to use a timer. However, see whether you can leverage XAML for the same. It appears there is in fact a way; it is called storyboard. A storyboard is conceptually a way of describing a story. A story has a timeline and certain things happen in certain times. In your case, you have a story to tell. You want a story that will last a few seconds and what happens in the story is that the opacity of the indicator will be altered in a linear fashion and moreover, when the story has ended, it will start back again. So, when the string is plucked, all you need to do is fetch the correspoding storyboard and begin it. All this is represented by the following XAML.

    <Rectangle x:Name="LowEStringIndicator" Height="15" Width="10"
               Canvas.Left="10" Canvas.Top="30" Fill="#00FF00"
               Opacity="0.0" >
    <Rectangle.Resources>
       <Storyboard x:Name="LowEStringIndicatoranimation">
          <DoubleAnimation
             Storyboard.TargetName="LowEStringIndicator"
             Storyboard.TargetProperty="Opacity"
             From="0.0" To="1.0" Duration="0:0:0.3" AutoReverse="True"
             RepeatBehavior="Forever"
          />
       </Storyboard>
    </Rectangle.Resources>
    </Rectangle>
    

    Things to note. Note the usage of Resources section. This is the XAML way of adding storyboards to elements. Note that it also has a name LowEStringIndicatorAnimation. You use this naming convention for a specific reason to help you locate the appropriate storyboard. The important items are the TargetName and TargetProperty. The TargetName indicates the element on which the TargetProperty will apply. As such, you want to animate the LowEStringIndicator and for this story, you want the Opacity property to be targeted. The next important things are From and To. Note the DoubleAnimation type used. All it means is that such a animation can be used to change/animate a property that is of double type. The Opacity is of that kind, and you indicate that the value should animate from a value of 0.0 (in other words, fully transparent) to 1.0 (fully visible) over a duration of 0.3 seconds. Autoreverse = True indicates that when the animation has reached from 0.0 opacity to 1.0 , it starts animating back from 1.0 to 0.0. If this is not specified, it will start from 0.0 again if RepeatBehavior is Forever.

    All that is left now is to kick off this storyboard animation. Stick in this into the stringPlucked function in the guitartuner.js file.

    function stringPlucked(sender, args)
    {
       var storyboardObj =
          sender.findName(sender.Name + "Indicatoranimation");
       storyboardObj.begin();
    }
    

    Save the XAML and JS files and refresh the HTML file in the browser. Now, click on the topmost string. You should see a green blinking animation.

    Things to be noted in that small JavaScript code. Note the use of a very useful findName function. Also, note the sender argument. The sender argument represents the object that generated the event. In your case, the stringPlucked function could be generated by the LowEString, AString, GString, DString, BString, or HighEString (because you added this handler for those elements). The sender.Name will help you get the x:Name attribute of the element that generated the event. Now, per your naming convention, the animation name is simply the stringname with the Indicatoranimation appended to it. So, that is what you do; when you append the sender.Name to "Indicatoranimation", you have the name of the storyboard you would like to run. To get this storyboard object, you use the findName function. findName is a pretty interesting function. The way it is called (in other words, [Obj].findName), it appears that it will find the passed-in named element from the object down its tree hierarchy. But, it is not; it will look for the name over the whole tree. So, that is what you do, even if it is the string that generates the event, and the string does not have the storyboard under it, you can still get to it by using the findName function. Once you have the storyboard object, all you need to do is kick off the animation by using begin().

    That takes care of goal 1.

    On to goal 2: When a string is being played, and you hit the red stop button, the indication that you implemented above should disappear. The approach will be similar. Add a MouseLeftButtonDown handler and call it something like StopClicked.

    <Ellipse x:Name="StopButtonb" Height="15" Width="15"
             Canvas.Left="8" Canvas.Top="125" Stroke="Black"
             StrokeThickness="1" Cursor="Hand"
             MouseLeftButtonDown="StopClicked">
    

    Add the function to the JavaScript. Now, because at the time the stop button is clicked, you need to know what storyboard is being played, you somehow have to store this information when the storyboard is begun. To accomplish this, you make the storyBoardObj a global variable like below, and in the stopClicked handler, call the end() function on it.

    var storyboardObj = null;
    function stringPlucked(sender, args)
    {
       storyboardObj = sender.findName(sender.Name + "Indicatoranimation");
       storyboardObj.begin();
    }
    
    function StopClicked(sender, args)
    {
       if(storyboardObj != null)
       {
          //stop any animation first
          storyboardObj.stop();
       }
    }
    

    Save the XAML and JS files and refresh the HTML file in the browser. Now, click on the topmost string. You should see a green blinking animation. Click on the red stop button and the animation should stop. Repeat the same process for all other strings, making sure the indicator and storyboard elements are named properly and you set the Canvas.Top for the indicator elements properly (incremented by 15 pixels from the previous one).

    That takes care of goal 2.

    On to goal 3: Whenever the indication is on, you want to play the sound of the respective string and when the indication stops, the sound should stop playing too.

    For this, you need sound files for the individual string notes. Please unzip the media zip file from the downloads section and drop the WMA files in the same location as the rest of the files. Note the names of each file; they have been named exactly as the name of the string elements in the XAML file with a WMA extension.

    XAML also can hold media elements. So, open up the XAML file and add the following element:

    <Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/
                          presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Height="400" Width="290" >
    <MediaElement x:Name="sound" Width="0" Height="0"/>
    <Ellipse Height="100" Width="100" Canvas.Left="30"
             Canvas.Top="25" Fill="Black"/>
    

    You give it a name to identify it from JavaScript and specify the height and width as 0 because you don't want it to be visible as such (because it is only audio). We do not specify the actual media file yet because you do not know beforehand which one to play unless a string has been plucked. Now, all you need to do is to play the media in parallel with the animation. The same with stopping the playing audio too. So, you modify the JavaScript code as below:

    function stringPlucked(sender, args)
    {
       storyboardObj = sender.findName(sender.Name + "Indicatoranimation");
       storyboardObj.begin();
       sender.findName("sound").Source = sender.Name + ".wma";
       sender.findName("sound").Play();
    }
    
    function StopClicked(sender, args)
    {
       if(storyboardObj != null)
       {
          //stop any animation first
          storyboardObj.stop();
          sender.findName("sound").Stop();
       }
    }
    

    All you do here is to find the sound element and execute the Play and Stop methods on it. Also, you follow the same convention to arrive at the sound file to play (which we set using the Source property). The only thing left now is to stop all old animation (indicator + media) each time a string is plucked before starting the new one. So, the JavaScript code changes to this:

    function stringPlucked(sender, args)
    {
       StopClicked(sender,args);
       storyboardObj = sender.findName(sender.Name + "Indicatoranimation");
       storyboardObj.begin();
       sender.findName("sound").Source = sender.Name + ".wma";
       sender.findName("sound").Play();
    }
    

    Save the XAML and JS files and refresh the HTML file in the browser. Now, click on the topmost string. You should see a green blinking animation and, if you have turned on your speakers/headphones, you should hear a sound. Click on the red stop button and the animation and sound should stop. Click on any other string and a new sound should play.

    All is good. But, in practise, this is silghtly insufficient. Most guitarists will need to have the sound constantly played till they have adjusted their own guitar strings to match the sound from the tuner. What this means is that the single burst sound is not good enough. One way to tackle this is to have a lengthier audio file that keeps playing the same note time and again. Although this is possible, it is a needless waste of media when the same can be achieved by playing the media in a loop. You could do the Storyboard animation trick here, but try something else. MediaElement generates an event called MediaEnded. You could use this for your purpose here. The idea is to handle this event, and in the handler, to seek to the beginning of the media and play it again. The changes are below:

    XAML file:

    <MediaElement x:Name="sound" Width="0" Height="0"
                  MediaEnded="endofmedia"/>
    

    JavaScript file:

    function endofmedia(sender,args)
    {
       //reached end, seek to beginning and play again
       var mediaElement = sender.findName("sound");
       var position = mediaElement.position;
       position.seconds = 0;
       mediaElement.position = position;
       mediaElement.Play();
    }
    

    Save the XAML and JS files and refresh the HTML file in the browser. Now, click on any string. You should see a green blinking animation and, if you have turned on your speakers/headphones, you should hear a sound. Let it remain so and you should hear the note ringing repeatedly. Click on the stop button and it should stop.

    You should now have a basic guitar tuner all up and running in your browser.

    Downloads

  • GuitarTuner.zip - Finished gadget files ( rename .zip to .gadget extension to install as Vista sidebar gadget )
  • media.zip - Media files ( media.zip )

  • Previous Page: Guitar Tuner Presentation Layout Next Page: Converting to a Vista Gadget
    Page 1: IntroductionPage 4: Guitar Tuner in Action
    Page 2: PresentationPage 5: Converting to a Vista Gadget
    Page 3: Guitar Tuner Presentation Layout 
    We have a winner in the RIA Run contest! Check out the Contest Winners Gallery and see which entries took the top prizes. You can play the games, too! Also, be sure to check out our interview with the grand prize winner to see how he crafted his winning entry. (Silverlight 2 Beta 2 required)