Guitar Tuner Vista Gadget Using Silverlight

Introduction

.. and then Redmondians said, "let there be Silverlight, and there was Silverlight". So, just what is Silverlight? In a nutshell, Silverlight is Microsoft's solution for creating next generation interactive content for the web. Silverlight uses XAML and JavaScript for creating killer applications that are rich with multimedia and interactivity. It currently uses a subset of WPF. The cool thing about Silverlight is that it is a cross-browser development framework and, as such, all you need is to have the Silverlight plug-in installed on the host system. This might sound like Macromedia (now Adobe) Flash, but, where Silverlight differs is that the content that drives the application (XAML) is all text and thus is search engine friendly.

Formerly code named WPF/e, Silverlight (version 1.0) is now available to download from Microsoft's web site and Microsoft has already released 1.1 beta for developers.

Note: For the puposes of this article, you'll concern yourslf with version 1.0 RC.

About the Article

This article is not really a exhaustive in-depth drill down into Silverlight, nor into Sidebar gadget details. Here, all you will attempt to do is to use Silverlight to create a nifty little Vista gadget. In the process, you will tread through some of the commonly used elements and usage scenarios. What is interesting is that you don't need much of anything to get started. The only pre-requisites are the following:

In fact, for the most part—in other words, until you really hit creating a gadget—you won't even need Vista. XP is just fine because most of your work would be editing some files and refreshing the browser.

Guitar Tuner

I don't think there are very many guitar fans out here in the CodeGuru community. But I am one and as such, I decided to do a guitar tuner, just because it is a pretty simple tool that lends itself well to being used as a Vista gadget just because of its simplicity.

For those who don't know what a guitar tuner is, let me explain. The rest can skip this section. A guitar is a stringed instrument. Typically, a guitar has six strings and each string is tuned to a particular frequency to make it easier to play. So, each string, when plucked, will produce a unique sound. The sound of these six strings is typically standardised and called a tuning. Tuning is essentially the sound of each string in relation to the others. There are different tunings such as standard, Drop D, and such. One of the first things guitarists do is to tune their guitars before they start playing. This gives them a known platform to play from. When tuned, all notes sound pleasant; otherwise, all you hear would be cacophony. The guitar tuner assists in this. It just plays the sounds of the six strings, so players can pluck their strings and try to match the sounds. When all strings match the sounds from the tuner, the guitar is said to be tuned. These days, most players use their laptops to check tabs for popular songs online, listen to their favorite songs, and record their own. So, as such, it is useful to have a guitar tuner handy. Hence, the idea to create a gadget out of it.

Goals

The basic goals will be these. Please take a look at the image below:

In the image, you see you are mimicing the look of a guitar. You see that there are six strings and a hole in the center (just to make it look like a guitar).

  • Tiy want to depict the body of a guitar and the strings. This will familiarize you with the Shape elements, the drawing operations, and other features in XAML.
  • When you click on any of the strings, you want to play the sound associated with the string. Also, you want to display a blinking element to indicate the string that is being played. This will familiarize you with the handling events, animating elements, and using media (audio) and such in XAML.
  • When you have clicked the string once, the plucking sound of the string that is played should be repeated continously. This is needed to allow the user sufficient time to turn the knobs on their guitars to bring it in unison with the tuner's sound. This will familairize you with media element and some of its properties.
  • When you click on the red button, the sound should stop playing. (You want some way to shut it up when done, right?)

This way, you will get your hands wet with XAML and a little JavaScript and their intermingling.

The finished Vista gadget should look like this when docked...

...and like this when undocked

Guitar Tuner Vista Gadget Using Silverlight

Presentation

The basic framework

It's time to get started. Before proceeding, I assume you have installed the Silverlight plugin. If not, please download and install it from here. Also, download and install the SDK from here.

  • Create a folder and call it GuitarTuner.gadget
  • Copy over the SilverLight.js file onto this folder. This file can be found here: [Drive]:\Program Files\Microsoft Silverlight 1.0 SDK\Resources.
  • Create a new text file, change its extension to HTML, and name it GuitarTuner.HTML. Copy over the following contents to it.
  • <!DOCTYPE html PUBLIC  "-//W3C//DTD XHTML 1.0 Transitional//EN"
       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
    <head>
       <title>Guitar Tuner</title>
       <script type="text/javascript" src="Silverlight.js"></script>
    </head>
    <body>
    </body>
    </html>
    
  • This is the launching HTML. You now will use this to host the Silverlight control. To do this, you add a div tag and add JavaScript code to create your Silverlight control. After inserting the div tag for the control, the body element looks like that below:
  • <body>
    <div id="SilverLightControlHost">
       <script type="text/javascript">
       var parentElement =
          document.getElementById("SilverLightControlHost");
       createGuitarTunerControl();
       </script>
    </div>
    </body>
    
  • You now need to point to the createGuitarTunerControl function. So, create a new text document, change the extension to .js, and call it GuitarTuner.js.
  • Add the following code to it.
  • function createGuitarTunerControl()
    {
       Silverlight.createObject(
          "guitarTuner.xaml",      // Source property value.
          parentElement,           // DOM reference to hosting DIV tag.
          "guitarTunerControl",    // Unique plugin ID value.
          {
             // Per-instance properties.
             width:'400',      // Width of rectangular region of
                               // plugin area in pixels.
             height:'290',     // Height of rectangular region of
                               // plugin area in pixels.
             // Determines whether to display in-place
             // install prompt if invalid version detected.
             inplaceInstallPrompt:false,
    
             background:'Orange',    // Background color of plugin.
             // Determines whether to display plugin in Windowless
             // mode.
             isWindowless:'true',
             framerate:'24',    // MaxFrameRate property value.
             version:'1.0'      // Silverlight version to use.
          },
          {
             onError:null,    // OnError property value --
             // event handler function name.
             onLoad:null      // OnLoad property value --
             // event handler function name.
          },
          null);    // Context value -- event handler function name.
    }
    

    What you are doing here is actually instantiating a Silverlight control object and passing it the path to the XAML file that implements the control. In your case, it is guitarTuner.xaml. The other important items here are the plugin ID (guitarTunerControl) that can be used to access the tuner control through DOM methods such as getElementbyId. You also set the dimensions of the control at 400 X 290 pixels.

  • Add a reference to this script file in the head section of the HTML file like below:
  • <head>
    <title>Guitar Tuner</title>
    <script type="text/javascript" src="Silverlight.js"></script>
    <script type="text/javascript" src="guitartuner.js"></script>
    </head>
    
  • What is missing now is the XAML file. Create another text document, change its extension to XAML, and name it guitarTuner.xaml.
  • Add the following content to it.
  • <Canvas
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/
                     presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Height="400" Width="290" >
    </Canvas>
    
  • Cool. Now, just double-click the guitarTuner.HTML file to launch it in the web browser. If all is well, you should see an Orange rectangle on the screen like that below.

[step1.png]

Guitar Tuner Vista Gadget Using Silverlight

Guitar Tuner Presentation Layout

With the basic structure up and running, you now are ready to start laying out the guitar tuner elements that you saw in the goals section. Break them up into different objects as you see here.

[goals_marked.png]

  • You need a circular object representing the sound hole in the middle (marked 1).
  • You need six strings (marked 2).
  • You need a rectangle representing the bridge. This is the location where all strings end beyond the sound hole (marked 3).
  • You need a circular button to stop the sound (marked 4). This is actually not part of a real guitar, but you need it for the gadget.

Each of these can be represented in XAML as an element. The circular object can be represented as a Ellipse element, each string can be represented as a Rectangle element, the bridge as a Rectangle element, and the stop button again as a Ellipse element. These are all Shape elements and thus have properties such as fill color (to fill their interior) , size, and so forth.

All elements in XAML are laid out on what is called a Canvas (à la DC in traditional Win32 programming). The canvas is simply a container for its child elements. The child elements can be control elements (button and so on), Shape elements (Rectangle, ellipse, and the like), media element, and such. The child element can be another canvas too.

All these elements form a kind of hierarchical tree structure as below:

[hierarchy.png]

So, in your case, that is what you do. You write the XAML in such a way that you lay out this tree and specify the location for each element. What is to be noted here is the way the XAML is rendered. It always goes from the topmost item in the tree downwards. This means the ones lower in the tree render on top of those higher up. This is the default (unless the Z ordering is changed explicitly).

What this means in this case is that the circle object (marked 1) should be one of the first elements that you describe in the XAML file. The rest of the elements do not overlap with each other, so they can take any ordering.

The layout and the positions are as shown in the following diagram:

[layout.png]

  • Open your XAML file and add an Ellipse element (shown in black color in the layout) like that below. What you are doing is adding an Ellipse Shape element at (30, 25) relative to the canvas' (0,0) position (which is the top-left corner). Also, you specify the width and height to be 100, which makes it a circle of 50 pixels' radius. You also describe the element to be filled with a black color. Also, you give the element a name, Hollow. The name is important if you need to identify the element from code or in XAML as you will see later. Save the XAML file and refresh the HTML file in the browser.
  • <Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/
                          presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Height="400" Width="290" >
            <Ellipse x:Name="Hollow" Height="100" Width="100"
                     Canvas.Left="30" Canvas.Top="25" Fill="Black"/>
    </Canvas>
    
  • Next, in the Z-order hierarchy comes the bridge (shown in red color in the layout). You add a Rectangle Shape element this time. Specify the location and size relative to the parent canvas and give it a name, Bridge.
  • <Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/
                          presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Height="400" Width="290" >
    <Ellipse Height="100" Width="100" Canvas.Left="30"
             Canvas.Top="25" Fill="Black"/>
    <Rectangle x:Name="Bridge" Width="10" Height="90"
               Canvas.Left="10" Canvas.Top="30" >
    </Rectangle>
    </Canvas>
    

    In a guitar, the bridge is actually an embossed piece. It would be interesting if you could convey this info. Well, it seems you can do this easily with the Fill attribute. For the hollow, you specified a soild flat fill—a fill that was a uniform color all the way. This was the right way for the hollow. However, for this embossed look, you will use a gradient fill. You want the color to grade from black to gray from the left side to the center and grade back from gray to back from the center to the left. You specify this like below:

    <Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/
                          presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Height="400" Width="290" >
    <Ellipse Height="100" Width="100" Canvas.Left="30"
             Canvas.Top="25" Fill="Black"/>
       <Rectangle x:Name="Bridge" Width="10" Height="90"
                  Canvas.Left="10" Canvas.Top="30" >
          <Rectangle.Fill>
             <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
             <GradientStop Color="Black" Offset="0.0"/>
             <GradientStop Color="Gray" Offset="0.5"/>
             <GradientStop Color="Black" Offset="1.0"/>
             </LinearGradientBrush>
          </Rectangle.Fill>
       </Rectangle>
    </Canvas>
    

    Pay attention to the details. The StartPoint and EndPoint specify the direction in which the gradation has to work. In your case, you want only horizontal gradation, so you want it to start at the 0th position to the full length of the element (indicated by 1). You specify 0 for the Y direction. The GradientStops specify the behavior of the gradient. You say that at 0th interval of the full gradation interval, you want to start with the Black color and gradually grade to Gray at half intervala and from there on over the next half you want to grade back to Black. Save the XAML file and refresh the HTML file in the browser.

  • Next in the Z-order hierarchy come the strings (shown in blue color in the layout). For the purpose of this article, just add one string (because the procedure is the same for all the other strings, except that their top location changes). The strings in a guitar are called the Low E, A, D, G, B, and High E strings in that order, from top to bottom. On a real guitar, the top strings (which produce the lower frequency notes) are thicker and they become gradually thinner as you reach the lowest one. You will use all this information to depict it as such in your XAML. All you have to do is the change the height/width.

    A string can be thought of as a cylinder. You want to depict a cylinder lying horizontal. Again, you resort to using a rectangle. And again, you will use the same gradation technique you used earlier, except that you use different colors to match a string look and also, you apply veritical gradation (note the StartPoint and EndPoint changes compared to that for Bridge) like below:

    <Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/
                          presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Height="400" Width="290" >
    <Ellipse Height="100" Width="100" Canvas.Left="30"
             Canvas.Top="25" Fill="Black"/>
    <Rectangle x:Name="Bridge" Width="10" Height="90"
               Canvas.Left="10" Canvas.Top="30" >
       <Rectangle.Fill>
          <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
             <GradientStop Color="Black" Offset="0.0"/>
             <GradientStop Color="Gray" Offset="0.5"/>
             <GradientStop Color="Black" Offset="1.0"/>
          </LinearGradientBrush>
       </Rectangle.Fill>
    </Rectangle>
    
    <Rectangle x:Name="LowEString" Width="315" Height="5"
               Canvas.Left="15" Canvas.Top="37">
    <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>
    </Canvas>
    

    Save the XAML file and refresh the HTML file in the browser.

  • Okay. The last element is the Stop button (shown in a green color in the layout). This will again be a Ellipse Shape element and, to give it a 3D look, you apply another type of gradation called Radial gradation. The element declaration is like below:
  • <Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/
                          presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Height="400" Width="290" >
    <Ellipse Height="100" Width="100" Canvas.Left="30"
             Canvas.Top="25" Fill="Black"/>
    <Rectangle x:Name="Bridge" Width="10" Height="90"
               Canvas.Left="10" Canvas.Top="30" >
       <Rectangle.Fill>
          <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
             <GradientStop Color="Black" Offset="0.0"/>
             <GradientStop Color="Gray" Offset="0.5"/>
             <GradientStop Color="Black" Offset="1.0"/>
          </LinearGradientBrush>
       </Rectangle.Fill>
    </Rectangle>
    <Rectangle x:Name="LowEString" Width="315" Height="5"
               Canvas.Left="15" Canvas.Top="37">
       <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>
    <Ellipse x:Name="StopButtonb" Height="15" Width="15"
             Canvas.Left="8" Canvas.Top="125" Stroke="Black"
             StrokeThickness="1" >
       <Ellipse.Fill>
          <RadialGradientBrush>
             <GradientStop Color="Red"   Offset="1.0"/>
             <GradientStop Color="White" Offset="0.0"/>
          </RadialGradientBrush>
       </Ellipse.Fill>
    </Ellipse>
    </Canvas>
    

    Save the XAML file and refresh the HTML file in the browser.

  • For the sake of completion, go ahead and add all the strings like below:
  • <Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/
                          presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Height="400" Width="290" ><Ellipse Height="100"
       Width="100" Canvas.Left="30" Canvas.Top="25" Fill="Black"/>
    <Rectangle x:Name="Bridge" Width="10" Height="90"
               Canvas.Left="10" Canvas.Top="30" >
       <Rectangle.Fill>
          <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
             <GradientStop Color="Black" Offset="0.0"/>
             <GradientStop Color="Gray" Offset="0.5"/>
             <GradientStop Color="Black" Offset="1.0"/>
          </LinearGradientBrush>
       </Rectangle.Fill>
    </Rectangle>
    <Rectangle x:Name="LowEString" Width="315" Height="5"
               Canvas.Left="15" Canvas.Top="37">
       <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="AString" Width="315" Height="5"
               Canvas.Left="15" Canvas.Top="52">
       <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="DString" Width="315" Height="4"
               Canvas.Left="15" Canvas.Top="67">
       <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="GString" Width="315" Height="4"
               Canvas.Left="15" Canvas.Top="82">
       <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="BString" Width="315" Height="3"
               Canvas.Left="15" Canvas.Top="97">
       <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="HighEString" Width="315" Height="3"
               Canvas.Left="15" Canvas.Top="112">
       <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>
    <Ellipse x:Name="StopButtonb" Height="15" Width="15"
             Canvas.Left="8" Canvas.Top="125" Stroke="Black"
             StrokeThickness="1" >
       <Ellipse.Fill>
          <RadialGradientBrush>
             <GradientStop Color="Red" Offset="1.0"/>
             <GradientStop Color="White" Offset="0.0"/>
          </RadialGradientBrush>
       </Ellipse.Fill>
    </Ellipse>
    </Canvas>
    

    Save the XAML file and refresh the HTML file in the browser. It should look like this:

    [presentation.png]

Guitar Tuner Vista Gadget Using Silverlight

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.

Guitar Tuner Vista Gadget Using Silverlight

Converting to a Vista Gadget

The next step is to convert it to a Vista gadget. This step is actually the easiest of all. In the same folder, create a new file called Gadget.xml, open it in Notepad, and paste the following into it:

<?xml version="1.0" encoding="utf-8" ?>
<gadget>
   <name>Guitar Tuner</name>
   <namespace>GuitarTuner</namespace>
   <version>1.0</version>
   <author name="[Your Name here]">
   </author>
   <copyright>2007</copyright>
   <description>Guitar Tuner for tuning by ear</description>
   <icons>
   </icons>
   <hosts>
      <host name="sidebar">
         <base type="HTML" apiVersion="1.0.0"
               src="GuitarTuner.html" />
         <permissions>full</permissions>
         <platform minPlatformVersion="0.3" />
      </host>
   </hosts>
</gadget>

Make sure that when you save it, do a "save as" and ensure you select the encoding as UTF-8. This is very critical; otherwise, the gadget will not be recognized as a valid gadget by Vista. Actually , that is all there is to convert it to a gadget because the gadget framework also is an application hosting a browser for each gadget and thus, if you have a HTML page working, you pretty much have got it working in the gadget.

The only thing you need to work on now is to make the gadget compliant with the gadget development guidelines and make it look pretty and behave normally. Have a look at the Vista gadget UI guidelines.

I think you have addressed the following well:

  • Guideline says, A good gadget provides functionality that is useful all the time. I think your tuner gadget does it well. It is a useful gadget for those who install it.
  • Guideline says, An effective gadget works well in the periphery. You will address this soon. But overall, you are in the right direction in that you don't distract users if not in use (provision for stopping media and animation when done with tuning)
  • Guideline says, A great gadget does a single, well-defined task very well. Again, you are on the right track. It helps in tuning and that's it. No more frills.
  • Guideline says, The best gadgets use a visual theme that suitably indicates their singular task. Again, I think you have done it right. You have the representation of a guitar body and the strings.

The one thing you need to do is to restrict the size of the gadget in docked state. You do this for now by specifying the size of the body element in the GuitarTuner.HTML like below:

<head>
   <title>Guitar Tuner</title>
      <style>
         body
         {
            width: 130px;
            height: 130px;
            margin: 0px;
            font-size:11px;
            font:10px segoe ui,tahoma;
         }
      </style>
      <script type="text/javascript" src="Silverlight.js"></script>
      <script type="text/javascript" src="guitartuner.js"></script>
</head>

This puts you in compliance with gadget guidelines.

If you have been using gadgets on Vista, you would know that a gadget could either be docked to the sidebar, or it could undocked. Microsoft guidelines recommends a 130px X 130px size for docked gadgets, but for undocked ones, the restrictions are little more lenient. Microsoft recommends a 400px X 400px size in the undocked state.

A 130px by 130px size is pretty restrictive; all you can fit in there is 6 strings. However, with a 400px X 400px square area, you could do much more. One of the pushes for gadget developers is to make the gadgets look cool. So, I just thought it would be cool if, in the undocked state, you could show the curved shape of a guitar body.

So, how do you know when this docking and undocking happens? Gadgets can listen to these events by registering handlers. This is part of the Vista gadget API. One just has to register to these notifications and do the necessary in those handlers. You register these handlers when the body is loaded and, in the handler, you change the body size from 130 to 400 depending on the state. I hope the code below is self explanatory.

In GuitarTuner.HTML, do the changes like below:

<script type="text/javascript" src="guitartuner.js"></script>
</head>
<script type="text/javascript">
function loadMain()
{
   //register for the events and pass in our handler
   System.Gadget.onDock = dockStateChanged;
   System.Gadget.onUndock = dockStateChanged;
}

function dockStateChanged()
{
   //change size depending on state
   if(System.Gadget.docked)
   {
      document.body.style.width  = "130px";
      document.body.style.height = "130px";
   }
   else
   {
      document.body.style.width  = "400px";
      document.body.style.height = "290px";
   }
}
</script>

<body onload="loadMain()">
<div id="SilverLightControlHost">

This will ensure that the sizes stretch accordingly. You now need a guitar body shape for the gadget. I do this by setting a proper background image for the body. The background image is BKGND.png in the media zip file in the downloads section. Change the style in the HTML like below:

<style>
body
{
   width: 130px;
   height: 130px;
   margin: 0px;
   font-size:11px;
   font:10px segoe ui,tahoma;
   background-image:url(bkgnd.png);background-repeat:no-repeat;
}
</style>

Open the guitarTuner.js file and change the background color to white as shown below:

inplaceInstallPrompt:false,    // Determines whether to display
                               // in-place install prompt if
                               // invalid version detected.
background:'#00000000',        // Background color of plugin.
isWindowless:'true',           // Determines whether to display plugin

If you were to launch the HTML now, you would see something like below:

[bkimage.png]

Not good. The location is not right. The background image needs to be pushed a little to the left and top. So, you add this to the body style.

background-image:url(bkgnd.png);
background-position: -80px -70px;
background-repeat:none;

There is another challenge with these positions that will come up. In the docked state, the background image needs to be moved as you did. If you did that, the strings and so forth are in the right location with respect to the guitar body shape.

In the undocked state though, you need to do the opposite. You need to bring the background image's left-top to align with the body's left-top. So, you need to set the position of the background image to 0 , 0. Also, now that image has shifted, so should the strings and the rest. You have to do this for all elements; this is tedious. Is there a simpler way? Yes, of course. If you remember, when I started off explaining the canvas and so on, I mentioned that a canvas itself can hold another canvas. This is a boon. What you could do is simply put all the elements into a canvas which in turn is an element of the root canvas. You then have to just move the child canvas and automatically, all the elements within it are moved.

These are the changes for what I discussed.

<Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/
                      presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   Height="400" Width="290" >
<Canvas x:Name="MainDrawing" Canvas.Left="0" Canvas.Top="0"
        Height="130" Width="130" >
<MediaElement x:Name="sound" Width="0" Height="0"
              MediaEnded="endofmedia"/>

.....

</Canvas>
</Canvas>

You now handle the dockstatechanged to move these elements like below:

In GuitarTuner.HTML

function dockStateChanged()
{
   if(System.Gadget.docked)
   {
      document.body.style.width  = "130px";
      document.body.style.height = "130px";
      document.body.style.backgroundPosition = "-80px -70px";
      document.getElementById("guitarTunerControl").content.
         findName("MainDrawing")["Canvas.Left"]="0";
      document.getElementById("guitarTunerControl").content.
         findName("MainDrawing")["Canvas.Top"]="-10";
   }
   else
   {
      document.body.style.width  = "400px";
      document.body.style.height = "290px";
      document.body.style.backgroundPosition = "0px 0px";
      document.getElementById("guitarTunerControl").content.
         findName("MainDrawing")["Canvas.Left"]="80";
      document.getElementById("guitarTunerControl").content.
         findName("MainDrawing")["Canvas.Top"]="70";
   }
}
</script>

That's it. You are finally done. All that is pending is to install this gadget. The easiest way is to select all files (exclude media.zip file if there is one) in the folder (Ctrl-A), , right-click and select Send to-> Compressed (zipped) folder to zip up the contents of the GuitarTuner folder, and rename the zip extension to, say, GuitarTuner.gadget. (Make sure to zip up the contents only and not the folder itself.) Now, on a Vista machine, double-click on the GuitarTuner.gadget file. After confirmation, you should see the gadget and should be able to play around with the gadget. Check out undocking it and see how it looks.

References



Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

  • In support of their business continuity and disaster recovery plans, many midsized companies endeavor to avoid putting all their eggs in one basket. Understanding the critical role of last-mile connectivity and always available Internet access for their enterprises, savvy firms utilize redundant connections from multiple service providers. Despite the good intentions, their Internet connectivity risk may still be in a single basket. That is because internet service providers (ISPs) and competitive local …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds