Welcome to MSDN Blogs Sign in | Join | Help

A user wrote in to me via my blog the other day and asked:

“Is there a way to control volume using WSR? I can't find any command that will do it. Can you point to a WSR Macro that might help me? Thanks for any help you can give.”

Great question. Vista’s Windows Speech Recognition doesn’t have such a voice command built-in, but with WSR Macros, it can be added. With today’s macro, you can say:

Turn off the speakers
Turn on the speakers
Turn the speakers off
Turn the speakers on
Mute ?the speakers
Mute ?the audio
Un-mute ?the speakers
Un-mute ?the audio

In addition, I thought I’d also add a few other commands. Like:

Increase/Decrease ?the volume
Increase/Decrease ?the volume ?by [1to100] ?times

And:

Set the volume to [1to100]

If that’s what can be done, how does the macro do it? Another good question. For today’s macro, I am simply emulating the sending of application commands, otherwise known as WM_APPCOMMAND messages. To learn more about that, you can read about WM_APPCOMMAND on MSDN here.

Here’s what the macro looks like in total:

<speechMacros>

  <!--

  This macro demonstrates how to use the <sendMessage> element.
  It also demonstrates how to use WM_APPCOMMAND via the
  <sendMessage> element.

  WM_APPCOMMAND is documented on MSDN here: 
  http://msdn.microsoft.com/en-us/library/ms646275(VS.85).aspx

    WM_APPCOMMAND is defined as 0x319 in WINUSER.H, which is  
    793 in decimal

    WM_APPCOMMAND's wParam needs to be set to the window that
    generated the command.

      For this macro, we're pretending that the window that
      generated the command is the desktop window (whose handle
      can be retrieved by calling GetDesktopWindow()).

      On Windows Vista and Windows XP, it turns out that the
      desktop window handle is always set to 0x000010010 (or 793
      in decimal). Thus for each SendMessage in this macro, we'll
      set the wParam to that value (793).

    WM_APPCOMMAND's lParam needs to be set to the a combination
    of the application command to generate, combined with what
    device generated the command, and also combined with any
    keyboard modifiers that are held down at the same time.

      For this macro, we'll always set the device to be
      FAPPCOMMAND_KEY (0).

      For this macro, we'll always set the keyboard modifiers to
      none (0).

      Thus, the lParam ends up just being the application command,
      shifted into the right position in the lParam.

      APPCOMMAND_VOLUME_MUTE is 8,
      APPCOMMAND_VOLUME_MUTE << 16 is 0x00080000, or 524288

      APPCOMMAND_VOLUME_DOWN is 9,
      APPCOMMAND_VOLUME_DOWN << 16 is 0x00090000, or 589824

      APPCOMMAND_VOLUME_UP is 10,
      APPCOMMAND_VOLUME_UP << 16 is 0x000A0000, or 655360

  Now that I've described (briefly) how the WM_APPCOMMAND works,
  we'll also need to pick a specific window to send the
  application command to. It turns out that it's not really
  important where we send it, as long as that window doesn't
  ever process the message. That way, the application command
  will ultimately end up being handled by the system itself.

  One easy to find window that doesn't process these application
  commands itself is the Start button. So ... the className can
  be set to "Button" and the windowName can be set to "Start".

  -->

  <command>

    <listenFor>Turn off the speakers</listenFor>
    <listenFor>Turn on the speakers</listenFor>
    <listenFor>Turn the speakers off</listenFor>
    <listenFor>Turn the speakers on</listenFor>
    <listenFor>Mute ?the speakers</listenFor>
    <listenFor>Mute ?the audio</listenFor>
    <listenFor>Unmute ?the speakers</listenFor>
    <listenFor>Unmute ?the audio</listenFor>
    <setTextFeedback>Toggling audio mute...</setTextFeedback>
    <sendMessage className="Button" windowName="Start" message="793" wParam="6552" lParam="524288"/>
  </command>

  <command>
    <listenFor>[incOrDec] ?the volume</listenFor>
    <listenFor>[incOrDec] ?the volume ?by 1 ?time</listenFor>
    <listenFor>[incOrDec] ?the volume ?by [volumeTimes] ?times</listenFor>
    <setTextFeedback>Changing the volume...</setTextFeedback>
    <script language="vbscript">
      <![CDATA[

        times = "{[times]}"
        If times = "" Then times = "2"

        lParam = {[incOrDec.lParam]}       

        For i = 1 to Int(times) / 2
          Application.SendMessage "Button", "Start", 793, 6552, lParam
        Next

      ]]>
    </script>
  </command>

  <command>
    <listenFor>Set the volume to [volumeTimes]</listenFor>
    <emulateRecognition>Decrease the volume by 100</emulateRecognition>
    <setTextFeedback>Setting the volume to {[volumeTimes]}...</setTextFeedback>
    <waitFor seconds=".50" />
    <emulateRecognition>Increase the volume by {[volumeTimes]}</emulateRecognition>
    <setTextFeedback>Volume set to {[volumeTimes]}</setTextFeedback>
  </command>

  <command>
    <listenFor>Show ?the volume</listenFor>
    <setTextFeedback>Showing the volume</setTextFeedback>
    <sendKeys>{WIN}</sendKeys>
    <waitFor seconds=".750" />
    <emulateRecognition>Move mouse to Volume</emulateRecognition>
    <sendKeys>{WIN}</sendKeys>
  </command>
  <listenForList name="incOrDec" propname="lParam">
    <item propval="655360">Increase</item>
    <item propval="589824">Decrease</item>
  </listenForList>
  <numbers name="volumeTimes" start="2" stop="100" propname="times"/>

</speechMacros>

I get feedback from people, from time to time, that they'd like a more efficient way to add items to their speech dictionary. Although, there is a facility in Windows Speech Recognition already to do this, it's one word at a time, and it only allows you to record the pronunciation, not specify it yourself.

So ... What do I do when I have request like this? I make a new macro of the day. Thus, today's speech macro of the day: Speech Dictionary.wsrMac

First, if we're going to be messing around with the speech dictionary, it might be nice to see what's already in it... To do that, I made a command where I can say "Export the speech dictionary", and it'll export all the words/phrases that have been customized into a text file, and then launch that text file for me to take a look at. Here's the command:

<command>
    <listenFor>Export ?the speech dictionary</listenFor>
    <script language="VBScript">
      <![CDATA[
        fileName = "dictionary.txt"
        Set lexToken = CreateObject("SAPI.SpObjectToken")
        lexToken.SetId("HKEY_CURRENT_USER\SOFTWARE\Microsoft\Speech\CurrentUserLexicon")
        Set lex = lexToken.CreateInstance()
        Set words = lex.GetWords(1)
        Set fso = CreateObject("Scripting.FileSystemObject")
        Set file = fso.CreateTextFile(fileName, 1)

        For Each word in words
          If (word.LangId = 1033) Then

            Set prons = word.Pronunciations
            If prons.Count = 0 Then
              file.Write word.Word & vbCrLf       
            Else
              For Each pron in prons
                file.Write word.Word & "/"
                If pron.PartOfSpeech = 61440 Then
                  file.Write "BLOCKED" & vbCrLf
                Else
                  file.Write pron.Symbolic & vbCrLf
                End If
              Next
            End If

          End If
        Next
        file.Close
        Application.Run(fileName)
      ]]>
    </script>
  </command>

As you can see, it uses a bunch of speech APIs that are already in Vista, that any application can take advantage of. The first 7 lines opens up the speech dictionary (aka User Lexicon), and also opens up a file (dictionary.txt) to stick the words in in a more human readable format.

Then, for each word that it finds, it checks the language, and if it's 1033 (which means US English), it'll output the word into the file. But to do that, it needs to see how many pronunciations are available for each word. If there are zero, it'll just output the word. If there are more than one, it'll output one line per pronunciation.

There's also a special case, where if the part of speech is "61440", it outputs the word "BLOCKED". "61440" is a special kind of part of speech that the underlying speech platform uses to tell the underlying speech engine, this word should be blocked and not recognized at any time. The "BLOCKED" convention is just one I made up for this macro.

After looping thru all the words, it'll close the file, and launch the text file that was created.

Here's a sample of what my speech dictionary looks like when I say "Export the speech dictionary":

Ima/BLOCKED
im/BLOCKED
Visual Studio/v ih zh uw l s t uw d iy ow
antidisestablishmentarianism/ae n t ay d ih s ih s t ae b l ih sh m ax n t eh r iy ax n ih z ax m
Rob Chambers
Itamar/ih d ae m aa r
Itamar/ih t ae m aa r
Zac Chambers
Nic Chambers
Bec Chambers
Jac Chambers

The first few I've pasted here are words that the system has learned thru adaptation that I don't actually want it to use. Sometimes when I say "I am a GPM at Microsoft", it thinks I'm saying Ima or im. Thus... The first time I saw that happen, I selected Ima, and blocked it from my dictionary. More on that in a second...

Then, you can see for Visual Studio, I've actually got a full pronunciation listed. I probably don't have to, but it's here to show you that you can either have pronunciations listed, or not. Having it added as a single unit, ensures that when I say it, it'll always be cased properly.

The next word, "antidisestablishmentarianism", is one of the longest words in the English language, but it's not included in the speech dictionary by default. My son, Zac, loves this word, so of course I have to have it in my speech dictionary.

Next, you can see my name is listed with no pronunciation. Since both my first and last name are also common words, I've added my name here as a single unit, so when I say "Rob Chambers", I again get the proper casing.

Next up, is Itamar, pronounced two different ways. One with a "d" sound and one with a "t" sound. This way, no matter how I end up saying it in a hurry, I can speak Itamar's name properly in email communication with Itamar. BTW ... If you don't know Itamar, you should check out his ms-speech forum on Yahoo! Groups. It's a great place to learn more about Microsoft Speech and speech recognition in general.

Next up are the names of my kids. I have 4 boys, and they have short forms of more traditional names for their first names, so that their initials actually are the same as their first names. I played a bit too many video games as a kid, and RLC wasn't that cool for initials, compared with other kids in my neighborhood. My kids initials are the same as their names. It throws their teachers for a loop at first, but ... Well ... What can I say... I like it. So do they.

OK, now that I've described the lines, let's talk about the format of the pronunciations for a minute. These pronunciations are an attempt at human readable form, but using the exact same form as the underlying speech platform. That brings me to the next command, "Show phonemes":

<command>
  <listenFor>Show phonemes</listenFor>
  <run command="http://msdn.microsoft.com/en-us/library/ms717239(VS.85).aspx"/>
</command>

Say it, and it'll take you to the page on MSDN that describes what the American English Phoneme Representation is for the Speech API.

OK, now we're getting to the fun part. Now, let's say you wanted to add a new word. The command I'm about to show you, will let you say "Add that to the speech dictionary", and it'll copy whatever word is selected in your document (using the Windows clipboard), and add it to the speech dictionary with no specific pronunciation.

When I originally wrote this set of commands, I had 4 different commands. One for adding words, one for removing words, one for blocking them, and one for unblocking them. I quickly saw that they were all identical, so I made one command that can do any one of those 4 operations. Here's what it looks like with it's helper listenForList:

<command>
  <listenFor>[operationPhrase] ?for that ?from ?to the speech dictionary</listenFor>
  <setTextFeedback>Speech Dictionary: {[operationPhrase]}</setTextFeedback>
  <script language="VBScript">
    <![CDATA[
      ' Get the "that" text from the curent application...
      Application.SendKeys("{250 WAIT}{{CTRL}}c{250 WAIT}")
      that = Application.clipboardData.GetData("text")

      ' Determine if we're adding prons, adding phrases, remove phrases, or blocking phrases
      operation = "{[operation]}"
      ' If we're adding a pron, we'll need to use the recognizer, otherwise we'll just need the lexicon        
      If operation = "addpron" Then
        Set recognizer = CreateObject("SAPI.SpSharedRecognizer")
      Else
        Set lexToken = CreateObject("SAPI.SpObjectToken")
        lexToken.SetId("HKEY_CURRENT_USER\SOFTWARE\Microsoft\Speech\CurrentUserLexicon")

        Set lex = lexToken.CreateInstance()
      End If
      ' Keep track of how many words/phrases we added, and loop thru the lines in the "that"...
      cWords = 0
      lineStartPos = 1
      Do

        ' Find the next line break
        lineSeperatorPos = InStr(lineStartPos, that, Chr(10))
        if (lineSeperatorPos = 0) Then lineSeperatorPos = Len(that)

        ' Find the text for that line
        thisLine = Mid(that, lineStartPos, lineSeperatorPos - lineStartPos + 1)
        lineStartPos = lineStartPos + Len(thisLine)

        ' Trim off the CR/LF
        if (Right(thisLine, 1) = Chr(10)) Then thisLine = Left(thisLine, Len(thisLine) - 1)
        if (Right(thisLine, 1) = Chr(13)) Then thisLine = Left(thisLine, Len(thisLine) - 1)

        ' If we have something to operate on
        If (Len(Trim(thisLine)) > 0) Then

          ' Determine if there's a pronuncation included
          pronSeperatorPos = InStr(thisLine, "/")
          If (pronSeperatorPos = 0) Then
            ' Perform the operation with no pronuncation
            If operation = "addpron" Then Call recognizer.DisplayUI(65552, thisLine, "AddRemoveWord", thisLine)
            If operation = "add" Then Call lex.AddPronunciation(thisLine, 1033, 0)
            If operation = "remove" Then Call lex.RemovePronunciation(thisLine, 1033, 0)
            If operation = "block" Then Call lex.AddPronunciation(thisLine, 1033, 61440)
          Else
            ' Find the pronuncation and collapse it
            word = Left(thisLine, pronSeperatorPos - 1)
            pron = Right(thisLine, Len(thisLine) - pronSeperatorPos)
            pron = CollapsePron(pron)
            ' Special case the "BLOCKED" pronuncation
            partOfSpeech = 0
            If pron="BLOCKED" Then
              partOfSpeech = 61440
              pron = ""
            End If
            ' Perform the operation with the pronuncation (and just continue if there's an error)
            On Error Resume Next              
            If operation = "addpron" Then Call recognizer.DisplayUI(65552, word, "AddRemoveWord", word)
            If operation = "add" Then Call lex.AddPronunciation(word, 1033, partOfSpeech, pron)
            If operation = "remove" Then Call lex.RemovePronunciation(word, 1033, partOfSpeech, pron)
            If operation = "block" Then Call lex.AddPronunciation(word, 1033, 61440, pron)
            On Error Goto 0             
          End If

          cWords = cWords + 1

        End If

      Loop while lineStartPos < Len(that)

      ' Tell the user what we did...
      If (cWords = 1) Then
        If operation = "addpron" Then Call Application.Alert("Added pronunciation for " & Chr(34) & thisLine & Chr(34) & "!", "Speech Dictionary", 2)
        If operation = "add" Then Call Application.Alert("Added " & Chr(34) & thisLine & Chr(34) & "!", "Speech Dictionary", 2)
        If operation = "remove" Then Call Application.Alert("Removed " & Chr(34) & thisLine & Chr(34) & "!", "Speech Dictionary", 2)
        If operation = "block" Then Call Application.Alert("Blocked " & Chr(34) & thisLine & Chr(34) & "!", "Speech Dictionary", 2)
      Else
        If operation = "addpron" Then Call Application.Alert("Added pronunciations for " & cWords & " words/phrases!", "Speech Dictionary", 1)
        If operation = "add" Then Call Application.Alert("Added " & cWords & " words/phrases!", "Speech Dictionary", 1)
        If operation = "remove" Then Call Application.Alert("Removed " & cWords & " words/phrases!", "Speech Dictionary", 1)
        If operation = "block" Then Call Application.Alert("Blocked " & cWords & " words/phrases!", "Speech Dictionary", 1)
      End If

      Function CollapsePron(pron)

        ret = ""
        insideBrackets = vbFalse
        For i = 1 to Len(pron)
          If (Not insideBrackets) Then
            If (Mid(pron, i, 1) = "[") Then
              insideBrackets = vbTrue
            ElseIf (Mid(pron, i, 1) <> "/") Then
              ret = ret & Mid(pron, i, 1)
            End If
          ElseIf (Mid(pron, i, 1) = "]") Then
            insideBrackets = vbFalse
          End If
        Next
        CollapsePron = ret
      End Function
    ]]>
  </script>
</command>

and:

<listenForList name="operationPhrase" propname="operation">
  <item propval="addpron">Add ?a pronunciation</item>
  <item propval="addpron">Add ?a pron</item>
  <item propval="add">Add</item>
  <item propval="remove">Remove</item>
  <item propval="block">Block</item>
  <item propval="remove">Unblock</item>
</listenForList>

I'll leave the details on the specifics for an exercise for the readers. As a user of the macro, though, you can now say things like:

"Add a pronunciation for that from the speech dictionary",
"Add that to the speech dictionary",
"Remove that from the speech dictionary",
"Block that from the speech dictionary", and
"Unblock that from the speech dictionary"

Your selection will have to be a single word/phrase, or multiple  words/phrases separated by line breaks. The word/phrases can also have a trailing pronunciation, similar in form to what you see in the output from "Export the speech dictionary".

"OK, but how can I generate those pronunciations myself?" Good question!

Use this command:

<command>
  <listenFor>Sounds like [...]</listenFor>
  <listenFor>Insert sounds like [...]</listenFor>
  <script language="VBScript">
    <![CDATA[

      Application.SetTextFeedback("Sounds like...")
      Set pc = CreateObject("SAPI.SpPhoneConverter")
      pc.LanguageId = 1033

      pron = "/"
      firstElement = Result.PhraseInfo.Properties.Item(0).FirstElement
      numberOfElements = Result.PhraseInfo.Properties.Item(0).NumberOfElements

      For i = 1 To numberOfElements
        Set elem = Result.PhraseInfo.Elements.Item(firstElement + i - 1)
        pron = pron & "[" & elem.LexicalForm & "]" & "/" & pc.IdToPhone(elem.Pronunciation) & " "
      Next
      Application.Wait(0.25)
      Application.SetTextFeedback("Sounds like: " & pron)
      Application.InsertText(pron)

    ]]>
  </script>
</command>

This will use dictation to allow you to say "Sounds like Visual Studio", and it'll output /[visual]/v ih zh uw l /[studio]/s t uw d iy ow. So, if you have a word that you're trying to add, you can use the built in pronunciations of other words that WSR already knows about, to cut and paste together your own pronunciation.

Another way to do it would be to select the word of phrase you wanted to build a pronunciation for, and saying "What's that sound like?", which is the final command we'll put into this macro:

<command>
  <listenFor>What's that sound like</listenFor>
  <listenFor>What does that sound like</listenFor>
  <script language="VBScript">
    <![CDATA[

      Application.SendKeys("{250 WAIT}{{CTRL}}c{250 WAIT}")
      that = Application.clipboardData.GetData("text")

      Application.EmulateRecognition("Go after that")
      Application.EmulateRecognition("Insert sounds like " & that)

    ]]>
  </script>
</command>

That will copy the selection, move right after it, and then pretend you actually said it. For many words/phrases, this will work even if Windows Speech Recognition doesn't really know how to pronounce the word/phrase, because the system will make it's best guess on how to pronounce it just like it would if you were trying to click on that word on a web page with your voice.

OK ... Now here's another command that will make your phrases a little shorter if you're actually using the commands inside Notepad.exe with the dictionary.txt file open:

<command>
  <appIsInForeground processName="notepad.exe" windowTitleContains="dictionary.txt"/>
  <listenFor>[operationPhrase] ?for that</listenFor>
  <emulateRecognition>{[operationPhrase]} that the speech dictionary</emulateRecognition>
</command>

This basically only works when notepad is in focus, and it's editing dictionary.txt (as it would be when you've just said "Export the speech dictionary". This will enable you to say simpler commands like:

"Add a pronunciation for that",
"Add that",
"Remove that",
"Block that", and
"Unblock that"

Here's the macro in complete form:

<speechMacros>

<!--

NOTE #1: The magic number 1033 represent en-us
NOTE #2: The magic number 6552 is a special hack to represent the desktop window handle (Validated on XP, and Vista)
NOTE #3: The magic number 61440 means that this "word/phrase" should be blocked

-->
  <command>
    <listenFor>[operationPhrase] ?for that ?from ?to the speech dictionary</listenFor>
    <setTextFeedback>Speech Dictionary: {[operationPhrase]}</setTextFeedback>
    <script language="VBScript">
      <![CDATA[
        ' Get the "that" text from the curent application...
        Application.SendKeys("{250 WAIT}{{CTRL}}c{250 WAIT}")
        that = Application.clipboardData.GetData("text")

        ' Determine if we're adding prons, adding phrases, remove phrases, or blocking phrases
        operation = "{[operation]}"
        ' If we're adding a pron, we'll need to use the recognizer, otherwise we'll just need the lexicon        
        If operation = "addpron" Then
          Set recognizer = CreateObject("SAPI.SpSharedRecognizer")
        Else
          Set lexToken = CreateObject("SAPI.SpObjectToken")
          lexToken.SetId("HKEY_CURRENT_USER\SOFTWARE\Microsoft\Speech\CurrentUserLexicon")

          Set lex = lexToken.CreateInstance()
        End If
        ' Keep track of how many words/phrases we added, and loop thru the lines in the "that"...
        cWords = 0
        lineStartPos = 1
        Do

          ' Find the next line break
          lineSeperatorPos = InStr(lineStartPos, that, Chr(10))
          if (lineSeperatorPos = 0) Then lineSeperatorPos = Len(that)

          ' Find the text for that line
          thisLine = Mid(that, lineStartPos, lineSeperatorPos - lineStartPos + 1)
          lineStartPos = lineStartPos + Len(thisLine)

          ' Trim off the CR/LF
          if (Right(thisLine, 1) = Chr(10)) Then thisLine = Left(thisLine, Len(thisLine) - 1)
          if (Right(thisLine, 1) = Chr(13)) Then thisLine = Left(thisLine, Len(thisLine) - 1)

          ' If we have something to operate on
          If (Len(Trim(thisLine)) > 0) Then

            ' Determine if there's a pronuncation included
            pronSeperatorPos = InStr(thisLine, "/")
            If (pronSeperatorPos = 0) Then
              ' Perform the operation with no pronuncation
              If operation = "addpron" Then Call recognizer.DisplayUI(65552, thisLine, "AddRemoveWord", thisLine)
              If operation = "add" Then Call lex.AddPronunciation(thisLine, 1033, 0)
              If operation = "remove" Then Call lex.RemovePronunciation(thisLine, 1033, 0)
              If operation = "block" Then Call lex.AddPronunciation(thisLine, 1033, 61440)
            Else
              ' Find the pronuncation and collapse it
              word = Left(thisLine, pronSeperatorPos - 1)
              pron = Right(thisLine, Len(thisLine) - pronSeperatorPos)
              pron = CollapsePron(pron)
              ' Special case the "BLOCKED" pronuncation
              partOfSpeech = 0
              If pron="BLOCKED" Then
                partOfSpeech = 61440
                pron = ""
              End If
              ' Perform the operation with the pronuncation (and just continue if there's an error)
              On Error Resume Next              
              If operation = "addpron" Then Call recognizer.DisplayUI(65552, word, "AddRemoveWord", word)
              If operation = "add" Then Call lex.AddPronunciation(word, 1033, partOfSpeech, pron)
              If operation = "remove" Then Call lex.RemovePronunciation(word, 1033, partOfSpeech, pron)
              If operation = "block" Then Call lex.AddPronunciation(word, 1033, 61440, pron)
              On Error Goto 0             
            End If

            cWords = cWords + 1

          End If

        Loop while lineStartPos < Len(that)

        ' Tell the user what we did...
        If (cWords = 1) Then
          If operation = "addpron" Then Call Application.Alert("Added pronunciation for " & Chr(34) & thisLine & Chr(34) & "!", "Speech Dictionary", 2)
          If operation = "add" Then Call Application.Alert("Added " & Chr(34) & thisLine & Chr(34) & "!", "Speech Dictionary", 2)
          If operation = "remove" Then Call Application.Alert("Removed " & Chr(34) & thisLine & Chr(34) & "!", "Speech Dictionary", 2)
          If operation = "block" Then Call Application.Alert("Blocked " & Chr(34) & thisLine & Chr(34) & "!", "Speech Dictionary", 2)
        Else
          If operation = "addpron" Then Call Application.Alert("Added pronunciations for " & cWords & " words/phrases!", "Speech Dictionary", 1)
          If operation = "add" Then Call Application.Alert("Added " & cWords & " words/phrases!", "Speech Dictionary", 1)
          If operation = "remove" Then Call Application.Alert("Removed " & cWords & " words/phrases!", "Speech Dictionary", 1)
          If operation = "block" Then Call Application.Alert("Blocked " & cWords & " words/phrases!", "Speech Dictionary", 1)
        End If

        Function CollapsePron(pron)

          ret = ""
          insideBrackets = vbFalse
          For i = 1 to Len(pron)
            If (Not insideBrackets) Then
              If (Mid(pron, i, 1) = "[") Then
                insideBrackets = vbTrue
              ElseIf (Mid(pron, i, 1) <> "/") Then
                ret = ret & Mid(pron, i, 1)
              End If
            ElseIf (Mid(pron, i, 1) = "]") Then
              insideBrackets = vbFalse
            End If
          Next
          CollapsePron = ret
        End Function
      ]]>
    </script>
  </command>

  <command>
    <listenFor>Export ?the speech dictionary</listenFor>
    <script language="VBScript">
      <![CDATA[
        fileName = "dictionary.txt"
        Set lexToken = CreateObject("SAPI.SpObjectToken")
        lexToken.SetId("HKEY_CURRENT_USER\SOFTWARE\Microsoft\Speech\CurrentUserLexicon")
        Set lex = lexToken.CreateInstance()
        Set words = lex.GetWords(1)
        Set fso = CreateObject("Scripting.FileSystemObject")
        Set file = fso.CreateTextFile(fileName, 1)

        For Each word in words
          If (word.LangId = 1033) Then

            Set prons = word.Pronunciations
            If prons.Count = 0 Then
              file.Write word.Word & vbCrLf       
            Else
              For Each pron in prons
                file.Write word.Word & "/"
                If pron.PartOfSpeech = 61440 Then
                  file.Write "BLOCKED" & vbCrLf
                Else
                  file.Write pron.Symbolic & vbCrLf
                End If
              Next
            End If

          End If
        Next
        file.Close
        Application.Run(fileName)
      ]]>
    </script>
  </command>

  <command>
    <listenFor>Sounds like [...]</listenFor>
    <listenFor>Insert sounds like [...]</listenFor>
    <script language="VBScript">
      <![CDATA[

        Application.SetTextFeedback("Sounds like...")
        Set pc = CreateObject("SAPI.SpPhoneConverter")
        pc.LanguageId = 1033

        pron = "/"
        firstElement = Result.PhraseInfo.Properties.Item(0).FirstElement
        numberOfElements = Result.PhraseInfo.Properties.Item(0).NumberOfElements

        For i = 1 To numberOfElements
          Set elem = Result.PhraseInfo.Elements.Item(firstElement + i - 1)
          pron = pron & "[" & elem.LexicalForm & "]" & "/" & pc.IdToPhone(elem.Pronunciation) & " "
        Next
        Application.Wait(0.25)
        Application.SetTextFeedback("Sounds like: " & pron)
        Application.InsertText(pron)

      ]]>
    </script>
  </command>

  <command>
    <listenFor>What's that sound like</listenFor>
    <listenFor>What does that sound like</listenFor>
    <script language="VBScript">
      <![CDATA[

        Application.SendKeys("{250 WAIT}{{CTRL}}c{250 WAIT}")
        that = Application.clipboardData.GetData("text")

        Application.EmulateRecognition("Go after that")
        Application.EmulateRecognition("Insert sounds like " & that)

      ]]>
    </script>
  </command>

  <command>
    <listenFor>Show phonemes</listenFor>
    <run command="http://msdn.microsoft.com/en-us/library/ms717239(VS.85).aspx"/>
  </command>

  <command>
    <appIsInForeground processName="notepad.exe" windowTitleContains="dictionary.txt"/>
    <listenFor>[operationPhrase] ?for that</listenFor>
    <emulateRecognition>{[operationPhrase]} that the speech dictionary</emulateRecognition>
  </command>

  <listenForList name="operationPhrase" propname="operation">
    <item propval="addpron">Add ?a pronunciation</item>
    <item propval="addpron">Add ?a pron</item>
    <item propval="add">Add</item>
    <item propval="remove">Remove</item>
    <item propval="block">Block</item>
    <item propval="remove">Unblock</item>
  </listenForList>

</speechMacros>

That's it! I know this is a lot of script to digest, but if you don't really want to, don't! Just use the macro as is. Questions? Comments? Let us know!

Congratulations to Myro Mykhalchuk and his team at Say2Go for winning 1st Prize in Microsoft's 2008 Partner Contest for ISV/Software Solutions!

Say2Go allows users to do something they're calling "Voicing" between IM users. Voicing is more like voicemail than IM. Say2Go allows users to press and hold a button, speak what they want to say to another Say2Go user, release the button, and what they said will be sent to their buddy both as an audio clip, as well as transcribed using Microsoft's Speech technologies included in Windows Vista. Pretty cool stuff...

Interestingly, the good folks over at TechCrunch did a review of Say2Go (here) to which Nik at TechCrunchIT responded with his own discussion of "Will we ever bury Voice Recognition?" In that discussion, unfortunately, Nik used Microsoft speech technology that's almost 8 years old trying to ascertain the current state of the art. Whoops. Nik, you should really upgrade to Windows Vista and see if you have better luck.

If you haven't heard about Say2Go, you should check it out at http://www.Say2Go.com.

Have you ever wanted to control your media player with your voice? Well now you can! With today’s Macro of the Day, you can say things like “Play Hotel California”, or “Play The Eagles”, or, “Play Genre Rock”, and even “Play something by The Eagles”.

Today’s macro is one of the first Macros I created when we were developing WSR Macros, and I’ve tweaked it off and on for the last couple years. It’s currently in really good shape, and demonstrates some neat concepts like clarification, using named states, Jscript, and for the first time, it demonstrates the wmpMediaControl, wmpMediaPlay, and wmpMediaItems XML elements from the WSR Macro schema.

Let’s take a look at the first command in the macro, and the one I use the most:

<command>

    <listenFor>play ?the artist [Artists]</listenFor>
    <listenFor>play ?the band [Artists]</listenFor>
    <listenFor>play ?the group [Artists]</listenFor>

    <setTextFeedback>Playing Artist {[*Artist]}</setTextFeedback>
    <wmpMediaControl command="pause"/>
    <disambiguate title="Which artist do you want to play?" prompt="Choose an Artist" timeout="25" propname="Artist"/>

    <setState name="playMediaTypeName" value="Artist"/>
    <setState name="playMediaTypeValue" value="{[*Artist]}"/>
    <setState name="playMediaAttrName" value="WM/AlbumArtist"/>
    <setState name="playMediaAttrValue" value="{[Artist]}"/>
    <emulateRecognition>Play what I asked for</emulateRecognition>

</command>

This command listens for a few different ways to say “Play the artist [artist]”. When that, or one if it’s alternatives is recognized, the macro set’s the text feedback, tells the Media Player to pause, and then prompts the user if necessary to clarify (or disambiguate as we say inside the Speech group). After which, it sets a few named states to keep track of the last verbal request, and then emulates “Play what I asked for”.

“Rob, why are you doing that?” … good question. The first reason is to demonstrate how you can sequence macros together. You’ll see with the other commands for albums and tracks, that they all do some very similar things. By using named states, we can in effect make a voice based sub-routine. The added bonus is that I can also say “Play what I asked for”, and the last media play command will be performed.

Let’s look at the Play what I asked for command:

<command>

    <listenFor>Play what I asked for ?again</listenFor>

    <wmpMediaControl command="pause"/>
    <setTextFeedback speak="true">Playing {[playMediaTypeName]} {[playMediaTypeValue]}</setTextFeedback>
    <wmpMediaPlay attrname="{[playMediaAttrName]}" attrvalue="{[playMediaAttrValue]}"/>

</command>

This command simply pauses the media player, set’s the text feedback to say what it’s about to play, and then it starts playing what was asked for. There are two parts to wmpMediaPlay element is that enable you to tell the media player what to play. You can think of these two as a name and value pair. You can tell it to play an artist (“WM/AlbumArtist”) named The Eagles (“The Eagles”). The first is called the attribute name, and the second is the attribute value. You can learn more about what’s available by looking at the documentation on MSDN for the Windows Media Player SDK.

Let’s see how the Play Album command is similar to the Play Artist.

<command>

    <listenFor>play ?the album ?named [Albums]</listenFor>
    <listenFor>play ?the C D ?named [Albums]</listenFor>
    <listenFor>play ?the [Albums] C D</listenFor>
    <listenFor>play ?the [Albums] album</listenFor>

    <setTextFeedback>Playing Album {[*Album]}</setTextFeedback>
    <wmpMediaControl command="pause"/>
    <disambiguate title="Which album do you want to play?" prompt="Choose an Album" timeout="25" propname="Album"/>

    <setState name="playMediaTypeName" value="Album"/>
    <setState name="playMediaTypeValue" value="{[*Album]}"/>
    <setState name="playMediaAttrName" value="WM/AlbumTitle"/>
    <setState name="playMediaAttrValue" value="{[Album]}"/>
    <emulateRecognition>Play what I asked for</emulateRecognition>

</command>

As you can see, it’s very similar to the Play Artist command. In fact, the only real differences are that what we listen for is slightly different, the prompts are slightly different, and the attribute that we tell the mediaPlayerPlay element to play (via the named states) is different.

Now both of these top level commands that use the Play what I asked for command to actually do the work also are referring to other “rules”. How do those get defined? Good question. The Answer: With the wmpMediaItems element. Let’s look at both of those for Artist and Albums:

<wmpMediaItems
    name="Artists"
    propname="Artist"
    attrname="WM/AlbumArtist"
      />

<wmpMediaItems
    name="Albums"
    propname="Album"
    attrname="WM/AlbumTitle"
      />

That’s it. It’s that simple. In fact, these elements will actually pay attention to changes in the media library and they’ll automatically update themselves with the new artists and albums. This could have been done with Jscript and the Media Player OCX, but since I really wanted this to work super well, I just went ahead and included it directly in the schema.

There are a lot of other commands in this macro, but instead of telling you all the details, I’m going to leave it for discussion in the comments…

One of my favorite commands here is the “Play something by [artist]” command. Try it out. See if you can see how it works.

If you don’t understand how something works, let me know…

Here’s the whole macro:

<?xml version="1.0" encoding="utf-8"?>

<!-- Windows Media Player -->
<speechMacros>

    <!-- Start Media Player -->
    <command>

        <listenFor>Media Player</listenFor>
        <emulateRecognition>start Media Player</emulateRecognition>

    </command>

    <!-- Play Artist Command -->
    <command>

        <listenFor>play ?the artist [Artists]</listenFor>
        <listenFor>play ?the band [Artists]</listenFor>
        <listenFor>play ?the group [Artists]</listenFor>

        <setTextFeedback>Playing Artist {[*Artist]}</setTextFeedback>
        <wmpMediaControl command="pause"/>
        <disambiguate title="Which artist do you want to play?" prompt="Choose an Artist" timeout="25" propname="Artist"/>

        <setState name="playMediaTypeName" value="Artist"/>
        <setState name="playMediaTypeValue" value="{[*Artist]}"/>
        <setState name="playMediaAttrName" value="WM/AlbumArtist"/>
        <setState name="playMediaAttrValue" value="{[Artist]}"/>
        <emulateRecognition>Play what I asked for</emulateRecognition>

    </command>

    <!-- Play Album Command -->
    <command>

        <listenFor>play ?the album ?named [Albums]</listenFor>
        <listenFor>play ?the C D ?named [Albums]</listenFor>
        <listenFor>play ?the [Albums] C D</listenFor>
        <listenFor>play ?the [Albums] album</listenFor>

        <setTextFeedback>Playing Album {[*Album]}</setTextFeedback>
        <wmpMediaControl command="pause"/>
        <disambiguate title="Which album do you want to play?" prompt="Choose an Album" timeout="25" propname="Album"/>

        <setState name="playMediaTypeName" value="Album"/>
        <setState name="playMediaTypeValue" value="{[*Album]}"/>
        <setState name="playMediaAttrName" value="WM/AlbumTitle"/>
        <setState name="playMediaAttrValue" value="{[Album]}"/>
        <emulateRecognition>Play what I asked for</emulateRecognition>

    </command>

    <!-- Play Genre Command -->
    <command>

        <listenFor>play ?the genre [Genres]</listenFor>

        <setTextFeedback>Playing Genre {[*Genre]}</setTextFeedback>
        <wmpMediaControl command="pause"/>
        <disambiguate title="Which genre do you want to play?" prompt="Choose a Genre" timeout="25" propname="Genre"/>

        <setState name="playMediaTypeName" value="Genre"/>
        <setState name="playMediaTypeValue" value="{[*Genre]}"/>
        <setState name="playMediaAttrName" value="WM/Genre"/>
        <setState name="playMediaAttrValue" value="{[Genre]}"/>
        <emulateRecognition>Play what I asked for</emulateRecognition>

    </command>

    <!-- Play Track Command -->
    <command>

        <listenFor>play ?the track ?named [TrackNames]</listenFor>

        <setTextFeedback>Playing Track {[*SourceURL]}</setTextFeedback>
        <wmpMediaControl command="pause"/>
        <disambiguate title="Which track do you want to play?" prompt="Choose a Track" timeout="25" propname="SourceURL"/>

        <setState name="playMediaTypeName" value="Track"/>
        <setState name="playMediaTypeValue" value="{[*SourceURL]}"/>
        <setState name="playMediaAttrName" value="SourceURL"/>
        <setState name="playMediaAttrValue" value="{[SourceURL]}"/>
        <emulateRecognition>Play what I asked for</emulateRecognition>

    </command>

    <!-- Play Anything Command -->
    <command>

        <listenFor>play [GenreArtistAlbumTrack]</listenFor>

        <setTextFeedback>Playing {[*GenreArtistAlbumTrack]}</setTextFeedback>
        <wmpMediaControl command="pause"/>

        <script language="JScript" >
        <![CDATA[
        genreArtistAlbumTrack = "{[*GenreArtistAlbumTrack]}";
        matchingGenres = CommandSet.RuleGenerators("Genres").Rule.Items.FindTextMatches(genreArtistAlbumTrack);
        matchingArtists = CommandSet.RuleGenerators("Artists").Rule.Items.FindTextMatches(genreArtistAlbumTrack);
        matchingAlbums = CommandSet.RuleGenerators("Albums").Rule.Items.FindTextMatches(genreArtistAlbumTrack);
        matchingTracks = CommandSet.RuleGenerators("TrackNames").Rule.Items.FindTextMatches(genreArtistAlbumTrack)
        addMediaItems("Genre:", matchingGenres, ChooseFromList.Items);
        addMediaItems("Artist:", matchingArtists, ChooseFromList.Items);
        addMediaItems("Album:", matchingAlbums, ChooseFromList.Items);
        addMediaItems("Track:", matchingTracks, ChooseFromList.Items);

        if (ChooseFromList.Items.Count == 0)
        {
            Command.Exit(1);
        }
        chosen = ChooseFromList.Items.Count == 1
            ? 0
            : ChooseFromList.Choose("What did you want to play?", "Play media");

        NamedStates.ClearNamedState("playMediaAttrName");
        NamedStates.ClearNamedState("playMediaAttrValue");
        NamedStates.ClearNamedState("playMediaTypeName");
        NamedStates.ClearNamedState("playMediaTypeValue");

        chosen = findMatchingItem(matchingGenres, chosen, "WM/Genre", "Genre");
        chosen = findMatchingItem(matchingArtists, chosen, "WM/AlbumArtist", "Artist");
        chosen = findMatchingItem(matchingAlbums, chosen, "WM/AlbumTitle", "Album");
        chosen = findMatchingItem(matchingTracks, chosen, "SourceURL", "Track");
        if (NamedStates.IsNamedStateSet("playMediaAttrName") && NamedStates.IsNamedStateSet("playMediaAttrValue"))
        {
            Application.EmulateRecognition("Play what I asked for");
        }

        function addMediaItems(prefix, itemsToAdd, collectionToAddTo)
        {
            for (i = 0; i < itemsToAdd.Count; i++)
            {
                item = itemsToAdd.item(i);
                collectionToAddTo.AddItem(prefix + " " + item.Phrase, item.Property);
            }
        }
        function findMatchingItem(matchingItems, chosen, playMediaAttrName, playMediaTypeName)
        {
            if (chosen >= 0 && chosen < matchingItems.Count)
            {
                NamedStates.SetNamedStateValue("playMediaAttrName", playMediaAttrName);
                NamedStates.SetNamedStateValue("playMediaAttrValue", matchingItems.Item(chosen).Property);
                NamedStates.SetNamedStateValue("playMediaTypeName", playMediaTypeName);
                NamedStates.SetNamedStateValue("playMediaTypeValue", matchingItems.Item(chosen).Phrase);
                chosen = -1;
            }
            else
            {
                chosen = chosen - matchingItems.Count;
            }
            return chosen;
        }

        ]]>
        </script>
    </command>

    <!-- Play Artist/Album/Track/Genre Command -->
    <command>

        <listenForList propname="PlayWhat">
            <item propval="Genres">Play Genre</item>
            <item propval="Genres">+Genres</item>
            <item propval="Artists">Play Artist</item>
            <item propval="Artists">+Artists</item>
            <item propval="Albums">Play Album</item>
            <item propval="Albums">Play C D</item>
            <item propval="Albums">+Albums</item>
            <item propval="Albums">+C +Ds</item>
            <item propval="TrackNames">Play Song</item>
            <item propval="TrackNames">Play Track</item>
            <item propval="TrackNames">+Song</item>
            <item propval="TrackNames">+Tracks</item>
        </listenForList>

        <setTextFeedback>Playing {[*PlayWhat]}</setTextFeedback>
        <wmpMediaControl command="pause"/>

        <script language="JScript" >
        <![CDATA[
        playWhat = "{[PlayWhat]}";
        matchingItems = CommandSet.RuleGenerators(playWhat).Rule.Items;
        addMediaItems("", matchingItems, ChooseFromList.Items);

        if (ChooseFromList.Items.Count == 0)
        {
            Command.Exit(1);
        }
        chosen = ChooseFromList.Items.Count == 1
            ? 0
            : ChooseFromList.Choose("What did you want to play?", "Play " + playWhat);

        NamedStates.ClearNamedState("playMediaAttrName");
        NamedStates.ClearNamedState("playMediaAttrValue");
        NamedStates.ClearNamedState("playMediaTypeName");
        NamedStates.ClearNamedState("playMediaTypeValue");
        if (playWhat == "Genres") {
            playMediaTypeName = "Genre";
            playMediaAttrName = "WM/Genre";
        }
        else if (playWhat == "Artists") {
            playMediaTypeName = "Artist";
            playMediaAttrName = "WM/AlbumArtist";
        }
        else if (playWhat == "Albums") {
            playMediaTypeName = "Album";
            playMediaAttrName = "WM/AlbumTitle";
        }
        else if (playWhat == "TrackNames") {
            playMediaTypeName = "Track";
            playMediaAttrName = "SourceURL";
        }

        chosen = findMatchingItem(matchingItems, chosen, playMediaAttrName, playMediaTypeName);
        if (NamedStates.IsNamedStateSet("playMediaAttrName") && NamedStates.IsNamedStateSet("playMediaAttrValue"))
        {
            Application.EmulateRecognition("Play what I asked for");
        }

        function addMediaItems(prefix, itemsToAdd, collectionToAddTo)
        {
            for (i = 0; i < itemsToAdd.Count; i++)
            {
                item = itemsToAdd.item(i);
                collectionToAddTo.AddItem(prefix + " " + item.Phrase, item.Property);
            }
        }
        function findMatchingItem(matchingItems, chosen, playMediaAttrName, playMediaTypeName)
        {
            if (chosen >= 0 && chosen < matchingItems.Count)
            {
                NamedStates.SetNamedStateValue("playMediaAttrName", playMediaAttrName);
                NamedStates.SetNamedStateValue("playMediaAttrValue", matchingItems.Item(chosen).Property);
                NamedStates.SetNamedStateValue("playMediaTypeName", playMediaTypeName);
                NamedStates.SetNamedStateValue("playMediaTypeValue", matchingItems.Item(chosen).Phrase);
                chosen = -1;
            }
            else
            {
                chosen = chosen - matchingItems.Count;
            }
            return chosen;
        }

        ]]>
        </script>
    </command>

    <!-- Play the album that has [track name] on it -->
    <command>

        <listenFor>Play the album that has [AlbumByTrackName] on it</listenFor>
        <listenFor>Play the C D that has [AlbumByTrackName] on it</listenFor>

        <setTextFeedback>Playing the Album that has {[*AlbumByTrackName]} on it</setTextFeedback>
        <wmpMediaControl command="pause"/>
        <disambiguate title="Which track's album do you want to play?" prompt="Choose a Track" timeout="25" propname="AlbumByTrackName"/>

        <setState name="playMediaTypeName" value="Album"/>
        <setState name="playMediaTypeValue" value="{[*AlbumByTrackName]}"/>
        <setState name="playMediaAttrName" value="WM/AlbumTitle"/>
        <setState name="playMediaAttrValue" value="{[AlbumByTrackName]}"/>

        <emulateRecognition>Play what I asked for</emulateRecognition>

    </command>

    <!-- Play the artist that sang [track name] -->
    <command>

        <listenFor>Play the artist that sang [ArtistByTrackName]</listenFor>
        <listenFor>Play the artist who sang [ArtistByTrackName]</listenFor>
        <listenFor>Play the band that sang [ArtistByTrackName]</listenFor>
        <listenFor>Play the band who sang [ArtistByTrackName]</listenFor>
        <listenFor>Play the group that sang [ArtistByTrackName]</listenFor>
        <listenFor>Play the group who sang [ArtistByTrackName]</listenFor>

        <setTextFeedback>Playing the artist that sang {[*ArtistByTrackName]}</setTextFeedback>
        <wmpMediaControl command="pause"/>
        <disambiguate title="Which track's artist do you want to play?" prompt="Choose a Track" timeout="25" propname="ArtistByTrackName"/>

        <setState name="playMediaTypeName" value="Artist"/>
        <setState name="playMediaTypeValue" value="{[*ArtistByTrackname]}"/>
        <setState name="playMediaAttrName" value="WM/AlbumArtist"/>
        <setState name="playMediaAttrValue" value="{[ArtistByTrackName]}"/>
        <emulateRecognition>Play what I asked for</emulateRecognition>

    </command>

    <!-- Play something sang by [TrackOrAlbumByArtist] -->
    <command>

        <listenFor>Play something ?sang by [TrackOrAlbumByArtist]</listenFor>
        <listenFor>Play something [TrackOrAlbumByArtist]</listenFor>
        <listenFor>Play something [TrackOrAlbumByArtist] sang</listenFor>

        <setTextFeedback>Playing something by {[*TrackOrAlbumByArtist]}</setTextFeedback>
        <wmpMediaControl command="pause"/>
        <script language="JScript" >
            <![CDATA[
        trackOrAlbumByArtist = "{[*TrackOrAlbumByArtist]}";
        matchingAlbums = CommandSet.RuleGenerators("AlbumByArtist").Rule.Items.FindTextMatches(trackOrAlbumByArtist);
        matchingTracks = CommandSet.RuleGenerators("TrackNameByArtist").Rule.Items.FindTextMatches(trackOrAlbumByArtist)
        addMediaItems("Album:", matchingAlbums, ChooseFromList.Items);
        addMediaItems("Track:", matchingTracks, ChooseFromList.Items);

        if (ChooseFromList.Items.Count == 0)
        {
            Command.Exit(1);
        }
        chosen = ChooseFromList.Items.Count == 1
            ? 0
            : ChooseFromList.Choose("What do you want to play?", "Play media");

        NamedStates.ClearNamedState("playMediaAttrName");
        NamedStates.ClearNamedState("playMediaAttrValue");
        NamedStates.ClearNamedState("playMediaTypeName");
        NamedStates.ClearNamedState("playMediaTypeValue");

        chosen = findMatchingItem(matchingAlbums, chosen, "WM/AlbumTitle", "Album");
        chosen = findMatchingItem(matchingTracks, chosen, "Name", "Track");
        if (NamedStates.IsNamedStateSet("playMediaAttrName") && NamedStates.IsNamedStateSet("playMediaAttrValue"))
        {
            Application.EmulateRecognition("Play what I asked for");
        }
        function addMediaItems(prefix, itemsToAdd, collectionToAddTo)
        {
            for (i = 0; i < itemsToAdd.Count; i++)
            {
                item = itemsToAdd.item(i);
                collectionToAddTo.AddItem(prefix + " " + item.Property + " (" + item.Phrase + ")", item.Property);
            }
        }
        function findMatchingItem(matchingItems, chosen, playMediaAttrName, playMediaTypeName)
        {
            if (chosen >= 0 && chosen < matchingItems.Count)
            {
                NamedStates.SetNamedStateValue("playMediaAttrName", playMediaAttrName);
                NamedStates.SetNamedStateValue("playMediaAttrValue", matchingItems.Item(chosen).Property);
                NamedStates.SetNamedStateValue("playMediaTypeName", playMediaTypeName);
                NamedStates.SetNamedStateValue("playMediaTypeValue", matchingItems.Item(chosen).Property);
                chosen = -1;
            }
            else
            {
                chosen = chosen - matchingItems.Count;
            }
            return chosen;
        }

        ]]>
        </script>

    </command>

    <!--
    Play something written in [TrackNameByYear]
    <command>

        <listenFor>Play something written in [TrackOrAlbumByYear]</listenFor>

        <setTextFeedback>Playing something written in {[*TrackOrAlbumByYear]}</setTextFeedback>
        <wmpMediaControl command="pause"/>
        <script language="JScript" >
            <![CDATA[
        trackOrAlbumByYear = "{[*TrackOrAlbumByYear]}";
        matchingAlbums = CommandSet.RuleGenerators("AlbumByYear").Rule.Items.FindTextMatches(trackOrAlbumByYear);
        matchingTracks = CommandSet.RuleGenerators("TrackNameByYear").Rule.Items.FindTextMatches(trackOrAlbumByYear)
        addMediaItems("Album:", matchingAlbums, ChooseFromList.Items);
        addMediaItems("Track:", matchingTracks, ChooseFromList.Items);

        if (ChooseFromList.Items.Count == 0)
        {
            Command.Exit(1);
        }
        chosen = ChooseFromList.Items.Count == 1
            ? 0
            : ChooseFromList.Choose("What do you want to play?", "Play media");

        NamedStates.ClearNamedState("playMediaAttrName");
        NamedStates.ClearNamedState("playMediaAttrValue");
        NamedStates.ClearNamedState("playMediaTypeName");
        NamedStates.ClearNamedState("playMediaTypeValue");

        chosen = findMatchingItem(matchingAlbums, chosen, "WM/AlbumTitle", "Album");
        chosen = findMatchingItem(matchingTracks, chosen, "Name", "Track");
        if (NamedStates.IsNamedStateSet("playMediaAttrName") && NamedStates.IsNamedStateSet("playMediaAttrValue"))
        {
            Application.EmulateRecognition("Play what I asked for");
        }
        function addMediaItems(prefix, itemsToAdd, collectionToAddTo)
        {
            for (i = 0; i < itemsToAdd.Count; i++)
            {
                item = itemsToAdd.item(i);
                collectionToAddTo.AddItem(prefix + " " + item.Property + " (" + item.Phrase + ")", item.Property);
            }
        }
        function findMatchingItem(matchingItems, chosen, playMediaAttrName, playMediaTypeName)
        {
            if (chosen >= 0 && chosen < matchingItems.Count)
            {
                NamedStates.SetNamedStateValue("playMediaAttrName", playMediaAttrName);
                NamedStates.SetNamedStateValue("playMediaAttrValue", matchingItems.Item(chosen).Property);
                NamedStates.SetNamedStateValue("playMediaTypeName", playMediaTypeName);
                NamedStates.SetNamedStateValue("playMediaTypeValue", matchingItems.Item(chosen).Property);
                chosen = -1;
            }
            else
            {
                chosen = chosen - matchingItems.Count;
            }
            return chosen;
        }

        ]]>
        </script>
    </command>
    -->

    <!-- Play what I asked for -->
    <command>

        <listenFor>Play what I asked for ?again</listenFor>

        <wmpMediaControl command="pause"/>
        <setTextFeedback speak="true">Playing {[playMediaTypeName]} {[playMediaTypeValue]}</setTextFeedback>
        <wmpMediaPlay attrname="{[playMediaAttrName]}" attrvalue="{[playMediaAttrValue]}"/>

    </command>
  <!-- Navigation -->
  <command>

    <listenFor>+next track</listenFor>
    <listenFor>+next song</listenFor>
    <listenFor>+go to ?the +next ?track</listenFor>
    <listenFor>+go to ?the +next song</listenFor>

    <wmpMediaControl command="pause"/>
    <speak>Playing the next track</speak>
    <wmpMediaControl command="next"/>
    <waitFor seconds=".5"/>
    <wmpMediaControl command="play"/>

  </command>

  <command>

    <listenFor>+previous track</listenFor>
    <listenFor>+previous song</listenFor>
    <listenFor>+go to ?the +previous ?track</listenFor>
    <listenFor>+go to ?the +previous song</listenFor>

    <wmpMediaControl command="pause"/>
    <speak>Playing the previous track</speak>
    <wmpMediaControl command="previous"/>
    <waitFor seconds=".5"/>
    <wmpMediaControl command="play"/>

  </command>

  <command>

    <listenFor>[GoBack] one track</listenFor>
    <listenFor>[GoBack] one song</listenFor>

    <listenFor>[GoBack] [1to20times] tracks</listenFor>
    <listenFor>[GoBack] [1to20times] songs</listenFor>

    <wmpMediaControl command="pause"/>
    <speak>Skipping back {[times]} tracks</speak>
    <wmpMediaControl command="previous" times="{[times]}"/>
    <waitFor seconds=".5"/>
    <wmpMediaControl command="play"/>

  </command>

  <command>

    <listenFor>[GoForward] one track</listenFor>
    <listenFor>[GoForward] one song</listenFor>

    <listenFor>[GoForward] [1to20times] tracks</listenFor>
    <listenFor>[GoForward] [1to20times] songs</listenFor>

    <wmpMediaControl command="pause"/>
    <speak>Skipping ahead {[times]} tracks</speak>
    <wmpMediaControl command="next" times="{[times]}"/>
    <waitFor seconds=".5"/>
    <wmpMediaControl command="play"/>

  </command>

  <command>
    <listenFor>+play music</listenFor>
    <wmpMediaControl command="play"/>
  </command>

  <command>
    <listenFor>+stop music</listenFor>
    <listenFor>+stop playing ?music</listenFor>
    <wmpMediaControl command="stop"/>
  </command>

  <command>
    <listenFor>+pause music</listenFor>
    <wmpMediaControl command="pause"/>
  </command>

  <command>
    <listenFor>+Repeat ?on</listenFor>
    <listenFor>Turn on +repeat</listenFor>
    <listenFor>Turn +repeat on</listenFor>
    <wmpMediaControl command="loop_on"/>
    <wmpMediaControl command="pause"/>
    <speak>Repeat is now turned on</speak>
    <wmpMediaControl command="play"/>
  </command>

  <command>
    <listenFor>+Repeat +off</listenFor>
    <listenFor>Turn +off +repeat</listenFor>
    <listenFor>Turn +repeat +off</listenFor>
    <wmpMediaControl command="loop_off"/>
    <wmpMediaControl command="pause"/>
    <speak>Repeat is now turned off</speak>
    <wmpMediaControl command="play"/>
  </command>

  <command>
    <listenFor>+Toggle +repeat ?setting</listenFor>
    <wmpMediaControl command="loop_toggle"/>
    <wmpMediaControl command="pause"/>
    <speak>Toggled the repeat setting</speak>
    <wmpMediaControl command="play"/>
  </command>

  <command>
    <listenFor>+Shuffle ?on</listenFor>
    <listenFor>Turn +on +shuffle</listenFor>
    <listenFor>Turn +shuffle +on</listenFor>
    <wmpMediaControl command="shuffle_on"/>
    <wmpMediaControl command="pause"/>
    <speak>Shuffle is now turned on</speak>
    <wmpMediaControl command="play"/>
  </command>

  <command>
    <listenFor>+Shuffle</listenFor>
    <listenFor>Turn +off +shuffle</listenFor>
    <listenFor>Turn +shuffle +off</listenFor>
    <wmpMediaControl command="shuffle_off"/>
    <wmpMediaControl command="pause"/>
    <speak>Shuffle is now turned off</speak>
    <wmpMediaControl command="play"/>
  </command>

  <command>
    <listenFor>+Toggle +shuffle ?setting</listenFor>
    <wmpMediaControl command="shuffle_toggle"/>
    <wmpMediaControl command="pause"/>
    <speak>Toggled the shuffle setting</speak>
    <wmpMediaControl command="play"/>
  </command>

  <!-- Command Set Rules -->

  <!-- Various ways to say, "Go Back" -->
  <listenForList name="GoBack">
    <item>+go +back</item>
    <item>+go +up</item>
    <item>+go +down</item>
    <item>+skip +back</item>
    <item>+skip +up</item>
    <item>+skip +down</item>
  </listenForList>

  <!-- Various ways to say, "Go Forward"-->
  <listenForList name="GoForward">
    <item>+go +forward</item>
    <item>+go +ahead</item>
    <item>+go +down</item>
    <item>+skip +forward</item>
    <item>+skip +ahead</item>
    <item>+skip +down</item>
  </listenForList>

  <numbers name="1to20times" propname="times" start="1" stop="20"/>

  <wmpMediaItems
      name="Artists"
      propname="Artist"
      attrname="WM/AlbumArtist"
        />

  <wmpMediaItems
      name="Albums"
      propname="Album"
      attrname="WM/AlbumTitle"
        />

  <wmpMediaItems
      name="Genres"
      propname="Genre"
      attrname="WM/Genre"
        />

  <wmpMediaItems
      name="TrackNames"
      propname="SourceURL"
      propvalue="[SourceURL]"
      attrname="MediaType"
      attrvalue="AUDIO"
      listenFor="[Name]"
      />

   <wmpMediaItems
      name="AlbumByTrackName"
      propname="Album"
      propvalue="[WM/AlbumTitle]"
      attrname="MediaType"
      attrvalue="AUDIO"
      listenFor="[Name]"
      />

   <wmpMediaItems
      name="ArtistByTrackName"
      propname="Artist"
      propvalue="[WM/AlbumArtist]"
      attrname="MediaType"
      attrvalue="AUDIO"
      listenFor="[Name]"
      />

    <rule name="GenreArtistAlbumTrack">
        <list>
            <ruleref name="TrackNames"/>
            <ruleref name="Artists"/>
            <ruleref name="Albums"/>
            <ruleref name="Genres"/>
        </list>
    </rule>

    <wmpMediaItems
        name="TrackNameByArtist"
        propname="TrackName"
        propvalue="[Name]"
        attrname="MediaType"
        attrvalue="AUDIO"
        listenFor="[WM/AlbumArtist]"
      />

    <wmpMediaItems
        name="AlbumByArtist"
        propname="Album"
        propvalue="[WM/AlbumTitle]"
        attrname="MediaType"
        attrvalue="AUDIO"
        listenFor="[WM/AlbumArtist]"
      />

    <rule name="TrackOrAlbumByArtist">
        <list>
            <ruleref name="TrackNameByArtist"/>
            <ruleref name="AlbumByArtist"/>
        </list>
    </rule>

    &l