Welcome to MSDN Blogs Sign in | Join | Help

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>

    <!--
    <wmpMediaItems
        name="TrackNameByYear"
        propname="TrackName"
        propvalue="[Name]"
        attrname="MediaType"
        attrvalue="AUDIO"
        listenFor="[WM/Year]"
      />

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

    <rule name="TrackOrAlbumByYear">
        <list>
            <ruleref name="TrackNameByYear"/>
            <ruleref name="AlbumByYear"/>
        </list>
    </rule>
    -->
</speechMacros>

I've been posting macros for a few weeks now, and one question that comes up from time to time is, "Hey Rob, How do I use the macros that you've created? They look really cool, but I'm not sure how to save them onto my PC. Please help!"

It's a good question, and right now it's not as obvious as it should be. Ideally we'll extend the WSR Macro tool to make this incredibly easy, but for now, it's a bit of a manual process to copy a macro from text on a web site and save it on your local PC.

Here's how you do it using Notepad in 4 steps:

  1. Copy the macro source:
    1. For example, you can use my Search Commands macro from a couple weeks ago).
    2. Select all of the macro source from the web page, including both the starting element (<speechMacros>) and the ending element (</speechMacros).
    3. Copy it by saying "Copy that", or pressing <ctrl-c>.
  2. Paste the macro source into a text editor:
    1. Open a text editor of your choice that can save plain text files. I typically use Notepad or Visual Studio for this step. I'll use Notepad in this example.
    2. Paste the contents of the macro into Notepad by saying "Paste that", or pressing <ctrl-v>.
  3. Save the macro source:
    1. Select File / Save As by clicking thru the menu, or by saying "File", "Save As".
    2. Change the directory to your Speech Macros directory. Your Speech Macros directory is located underneath your Documents directory under your user account.
    3. Change the Save as Type to All files (this will allow you to use a file extension other than .TXT).
    4. Give the file whatever name you'd like, such as "Search Commands.wsrMac"
    5. Click OK.
  4. Sign the macro:
    1. If your WSR Macro Security level is set to high, you'll get a cryptic error message telling you that No Signature was present in the subject. That means that the macro hasn't been signed yet to be used on your PC.
    2. Right click on the WSR Macros icon in the system tray.
    3. Click "Security"
    4. Click "Sign Speech Macros..."
    5. Select your macro you just saved (e.g. "Search Commands.wsrMac")
    6. Click OK, and then click OK again.

That's it. Copy, Paste, Save, Sign.

If you don't want to mess with step #4 (signing), you can instead disable the security features of WSR Macros by setting the Security Level to Low. I wouldn't recommend this for most people, but if you must, you certainly can.

A common question I get is, "Hey, Rob, How do I make a speech macro application specific?" I've posted a couple examples of that embedded in other more complex macros, but here's the simple version.

To make a macro application specific, you'll have to edit the XML by hand. To do that, you can open up the XML file in a text editor, such as NOTEPAD, and add a line that looks like this:

<appIsInForeground processName="IEXPLORE.EXE"/>

So, that would make a simple text insertion macro look like this:

<speechMacros>
  <command>
    <appIsInForeground processName="IEXPLORE.EXE"/>
    <listenFor>Insert my signature</listenFor>
    <insertText>--robch</insertText>
  </command>
</speechMacros>

That's it! Do you have a question about Speech Macros? If so, let us know!

Today's macro of the day is a cool trick involving another free tool from Microsoft called Search Commands. Search Commands helps you find commands, options, wizards and other cool stuff in Office 2007 applications (Word, Excel, and PowerPoint). It's like search, but instead of searching content, it searches the tasks that are available in Microsoft Office.

Search Commands is built in the newly developed Office Labs team. They build software prototypes that are to the software world what concept cars to the automotive world.

A year or so ago, when I met up with some of the folks on the Search Commands team I fell in love with the Office add-in.

As implemented today without using the macro of the day, Search Commands is a new "tab" at the top of Office 2007 applications. If you want to find something, you click the tab, click inside the edit box, type what you're looking for, and amazingly quickly it shows you exactly what you're looking for as a set of search results in the Office Ribbon.

Well ... Being a speech guy, I thought, why can't I just say what I want and get it, right away, in a single command. That's what today's Macro of the Day is.

Basically, today's macro listens for a series of commands, like "Search commands for [...]", and will automatically switch to the right tab, place focus in the edit control, type what you said in the [...] section, and allow you to pick from the results. I no longer have to care at all where features are on the ribbon tabs. I just say what I want and get it, in one utterance.

Let's take a look at the commands in the macro.

The first command, listens for "Search Commands" if the application that the user is currently using is either Word, Excel, or PowerPoint. Then, if WSR Macros hears that, it'll set the text feedback and send some keys to the application. Those keys (<alt-y> s c o <ctrl-y>) are the magic keyboard shortcuts to put focus into the right field. the <ctrl-a> selects all the text if there's any text already there, and then the {delete} deletes that text. This command helps you get to the right tab, and puts focus in the right field, but only does part of the task for you...

Let's take a look:

<command>
  <condition operator="or">
    <appIsInForeground processName="winword.exe"/>
    <appIsInForeground processName="excel.exe"/>
    <appIsInForeground processName="powerpnt.exe"/>
  </condition>
  <listenFor>search commands</listenFor>
  <setTextFeedback speak="false">Search for a command</setTextFeedback>
  <sendKeys>%y</sendKeys>
  <waitFor seconds=".25"/>
  <sendKeys>sco^a{delete}</sendKeys>
</command>

The next command, listens for the same sorts of things, but it also allows you to say what you want, and it'll do the tab switching, as well as filling in the field to do the search for you. Take a look:

<command>
  <condition operator="or">
    <appIsInForeground processName="winword.exe"/>
    <appIsInForeground processName="excel.exe"/>
    <appIsInForeground processName="powerpnt.exe"/>
  </condition>
  <listenFor>search for ?a command [...]</listenFor>
  <listenFor>search for ?a command [...] [number]</listenFor>
  <listenFor>search commands ?for [...]</listenFor>
  <listenFor>search commands ?for [...] [number]</listenFor>
  <setTextFeedback speak="false">Search for command: {[...]}</setTextFeedback>
  <sendKeys>%y</sendKeys>
  <waitFor seconds=".25"/>
  <sendKeys>sco^a</sendKeys>
  <waitFor seconds=".25"/>
  <insertText>{[...]}</insertText>
  <waitFor seconds=".25"/>
  <sendKeys>^a</sendKeys>
  <sendKeys>{[number]}</sendKeys>
</command>

And finally, there's another command that listen's for you to say the same sorts of things, but then just tells you that it can't do what you're asking. This is generally a good GUI/VUI design, to prevent you from trying to use the same voice command in applications that don't currently support it (like Outlook).

Here's that command:

<command>
  <condition operator="not">
    <condition operator="or">
      <appIsInForeground processName="winword.exe"/>
      <appIsInForeground processName="excel.exe"/>
      <appIsInForeground processName="powerpnt.exe"/>
    </condition>
  </condition>
  <listenFor>search commands</listenFor>
  <listenFor>search commands ?for [...]</listenFor>
  <listenFor>search commands ?for [...] [number]</listenFor>
  <listenFor>search for ?a command [...]</listenFor>
  <listenFor>search for ?a command [...] [number]</listenFor>
  <setTextFeedback style="warning" speak="false">What was that?</setTextFeedback>
</command>

There's one more command in the macro that's pretty useful. It's a command that turns the user's voice saying a number between 1 and 9 into a digit key press, as opposed to a textual insertion in the document. This is important when using Search Commands because if I use the 2nd command above, and say "Search Commands for Background color", Search Commands will do the right thing, showing the results in the ribbon, and allow the user to press 1 for the first command, 2 for the second command, etc. But ... The speech system on Vista by default doesn't know that, so this command listens for digits, and presses the keys when in Word, PowerPoint, and Excel, instead of allowing you to insert text...

Here's what that command looks like:

<command>
  <condition operator="or">
    <appIsInForeground processName="winword.exe"/>
    <appIsInForeground processName="excel.exe"/>
    <appIsInForeground processName="powerpnt.exe"/>
  </condition>
  <listenFor>[number]</listenFor>
  <setTextFeedback speak="false">{[number]}</setTextFeedback>
  <sendKeys>{[number]}</sendKeys>
</command>

So ... If we put it all together, here's the full macro:

<speechMacros>

  <command>
    <condition operator="or">
      <appIsInForeground processName="winword.exe"/>
      <appIsInForeground processName="excel.exe"/>
      <appIsInForeground processName="powerpnt.exe"/>
    </condition>
    <listenFor>search commands</listenFor>
    <setTextFeedback speak="false">Search for a command</setTextFeedback>
    <sendKeys>%y</sendKeys>
    <waitFor seconds=".25"/>
    <sendKeys>sco^a{delete}</sendKeys>
  </command>

  <command>
    <condition operator="or">
      <appIsInForeground processName="winword.exe"/>
      <appIsInForeground processName="excel.exe"/>
      <appIsInForeground processName="powerpnt.exe"/>
    </condition>
    <listenFor>search for ?a command [...]</listenFor>
    <listenFor>search for ?a command [...] [number]</listenFor>
    <listenFor>search commands ?for [...]</listenFor>
    <listenFor>search commands ?for [...] [number]</listenFor>
    <setTextFeedback speak="false">Search for command: {[...]}</setTextFeedback>
    <sendKeys>%y</sendKeys>
    <waitFor seconds=".25"/>
    <sendKeys>sco^a</sendKeys>
    <waitFor seconds=".25"/>
    <insertText>{[...]}</insertText>
    <waitFor seconds=".25"/>
    <sendKeys>^a</sendKeys>
    <sendKeys>{[number]}</sendKeys>
  </command>

  <command>
    <condition operator="or">
      <appIsInForeground processName="winword.exe"/>
      <appIsInForeground processName="excel.exe"/>
      <appIsInForeground processName="powerpnt.exe"/>
    </condition>
    <listenFor>[number]</listenFor>
    <setTextFeedback speak="false">{[number]}</setTextFeedback>
    <sendKeys>{[number]}</sendKeys>
  </command>

  <command>
    <condition operator="not">
      <condition operator="or">
        <appIsInForeground processName="winword.exe"/>
        <appIsInForeground processName="excel.exe"/>
        <appIsInForeground processName="powerpnt.exe"/>
      </condition>
    </condition>
    <listenFor>search commands</listenFor>
    <listenFor>search commands ?for [...]</listenFor>
    <listenFor>search commands ?for [...] [number]</listenFor>
    <listenFor>search for ?a command [...]</listenFor>
    <listenFor>search for ?a command [...] [number]</listenFor>
    <setTextFeedback style="warning" speak="false">What was that?</setTextFeedback>
  </command>

  <numbers name="number" start="1" stop="9"/>

</speechMacros>

Remember, to take advantage of the the power of Search Commands and WSR Macros combined, you'll have to download both, and install this macro in your Speech Macros directory.

Related links:

Let us know what you think!

I love Disneyland, and I love technology, so it's a no-brainer for me to absolutely love Disney's new "Innovention Dream Home". Basically, it's a home of the future, as envisioned by Microsoft, HP, Life/ware, and Disneyland situated right inside the Disneyland park in Southern CA in Tomorrowland.

I saw a video about the 5,000 square foot home this morning on CNET, and I just had to share it here.

Special Note

Most of the scenarios that are demonstrated with speech recognition and synthesis technology are actually possible today with real live technology built into Windows Vista.

So, if you want to deck out your house with speech technology, check out Windows Speech Recognition included in Windows Vista for the basic speech technology to control the Operating System and all your applications running on Vista, and check out Windows Speech Recognition Macros (aka WSR Macros) for extending control to just about anything you can imagine.

Do you have an idea of what you'd like to control in your house? Leave me a comment in the comment section and I'll show you how easy it is...

Other cool links about the home:

Today's macro let's you say things like "Send email to Rob", or "Send email to Rob Chambers", and as long as I'm a contact in your Outlook Contacts, you can send me mail! It also allows you to say "Send email to Rob Chambers about That cool outlook macro", it'll open up Outlook, create a new email, put me as the recipient, and it'll set the subject to "That cool outlook macro". Neat, eh?

Today's macro was inspired by Brad, over in the ms-speech Yahoo! Group, who asked me if WSR Macros could dynamically create rule content last week. Well, Brad, yes it can! Here's an example.

The most interesting part of today's macro, is the ruleScript element that allows you to define a rule programmatically. The key to this script is that it will find content to add to the rule by hooking up to Outlook's object model (using Create Object("Outlook.Application")), and then call the Rule.Items.AddItem method for each email address it finds.

That ruleScript looks like this:

  <ruleScript name="OutlookContact" propname="EmailAddr" language="VBScript">
    <![CDATA[
      Call AddOutlookContacts

      Sub AddOutlookContacts

        REM Debug.DebugBreak
        Debug.Trace chr(13) & "Adding OutlookContacts..." & chr(13)

        Const olFolderContacts = 10
        Const olMSG = 3

        Set objOutlook = CreateObject("Outlook.Application")
        Set objNamespace = objOutlook.GetNamespace("MAPI")

        Set colContacts = objNamespace.GetDefaultFolder(olFolderContacts).Items

        For Each objContact in colContacts
          If TypeName(objContact) = "ContactItem" Then
            If objContact.FullName <> "" Then
              If objContact.Email1Address <> "" Then Rule.Items.AddItem objContact.FullName, objContact.Email1Address, True
              If objContact.Email2Address <> "" Then Rule.Items.AddItem objContact.FullName, objContact.Email2Address, True
              If objContact.Email3Address <> "" Then Rule.Items.AddItem objContact.FullName, objContact.Email3Address, True
            End If
          End If
        Next
        For Each objRuleItem in Rule.Items
          Debug.Trace objRuleItem.Phrase & " (" & objRuleItem.Property & ")" & chr(13)
        Next
        Debug.Trace "--" & chr(13) & "Added " & Rule.Items.Count & " items!" & chr(13)

      End Sub     
    ]]>
  </ruleScript>

There are a couple other interesting things in today's macro:

  1. I used a Debug.DebugBreak method (currently commented out above), to allow me to debug the macro interactively in Microsoft Visual Studio Express. This is a handy way of single stepping over each and every line in the code. If you have VS installed, when the macro hits that line (if you uncomment it), you'll be asked what debugger to use to debug the macro's script. You pick VS, and then you have F10 single-step control over the macro as it executes. You can do fun things like inspect variables, change flow, etc... All really cool stuff that developers using Microsoft Development tools have grown accustomed to doing.
  2. I also used Debug.Trace to output to VS what's going on in the macro, so even if I'm not single step debugging it, I can see what contacts got added.
  3. I used the disambiguate tag to allow the user to disambiguate what email address to use if it's ambiguous. So, for example, if I have 3 people named Rob in my contacts, it'll allow me to pick which one I wanted to send email to. In fact, it'll even show me the email addresses in parenthesis after the person's full name.

Here's the entire macro:

<speechMacros>

  <command priority="2">
    <listenFor>send email to [OutlookContact]</listenFor>
    <listenFor>send email to [OutlookContact] about [subject...]</listenFor>
    <disambiguate title="Send email to whom?" prompt="Who would you like to send email to?" timeout="30" propname="OutlookContact"/>
    <script language="VBScript">
      <![CDATA[
        Set outlookApp = CreateObject("Outlook.Application")

        olMailItem = 0
        Set newMessage = outlookApp.CreateItem(olMailItem)

        newMessage.Recipients.Add("{[OutlookContact.EmailAddr]}")
        newMessage.Subject = "{[subject...]}"
        newMessage.Display
      ]]>
    </script>
  </command>

  <command priority="1">
    <listenFor>send email</listenFor>
    <listenFor>send email to *</listenFor>
    <listenFor>send email to * about [subject...]</listenFor>
    <script language="VBScript">
      <![CDATA[
        Set contacts = CommandSet.RuleGenerators("OutlookContact")
        For Each item in contacts.Rule.Items
          ChooseFromList.Items.AddItem item.Phrase, item.Property, True
        Next
        ChooseFromList.Items.Sort
        chose = ChooseFromList.Choose("Send email to whom?", "Who would you like to send email to?")
        If chose > 0 Then
          Set outlookApp = CreateObject("Outlook.Application")

          olMailItem = 0
          Set newMessage = outlookApp.CreateItem(olMailItem)
          newMessage.Recipients.Add(ChooseFromList.Items(chose).Property)
          newMessage.Subject = "{[subject...]}"
          newMessage.Display
        End If   
      ]]>
    </script>
  </command>
  <command>
    <listenFor>Refresh ?Outlook Email Contacts</listenFor>
    <listenFor>Refresh Outlook ?Email Contacts</listenFor>
    <script language="VBScript">
      <![CDATA[
        Set contacts = CommandSet.RuleGenerators("OutlookContact")
        contacts.Rule.Items.RemoveAll
        contacts.Script.AddOutlookContacts
        contacts.Rule.Commit
      ]]>
    </script>
  </command>

  <ruleScript name="OutlookContact" propname="EmailAddr" language="VBScript">
    <![CDATA[
      Call AddOutlookContacts

      Sub AddOutlookContacts

        REM Debug.DebugBreak
        Debug.Trace chr(13) & "Adding OutlookContacts..." & chr(13)

        Const olFolderContacts = 10
        Const olMSG = 3

        Set objOutlook = CreateObject("Outlook.Application")
        Set objNamespace = objOutlook.GetNamespace("MAPI")

        Set colContacts = objNamespace.GetDefaultFolder(olFolderContacts).Items

        For Each objContact in colContacts
          If TypeName(objContact) = "ContactItem" Then
            If objContact.FullName <> "" Then
              If objContact.Email1Address <> "" Then Rule.Items.AddItem objContact.FullName, objContact.Email1Address, True
              If objContact.Email2Address <> "" Then Rule.Items.AddItem objContact.FullName, objContact.Email2Address, True
              If objContact.Email3Address <> "" Then Rule.Items.AddItem objContact.FullName, objContact.Email3Address, True
            End If
          End If
        Next
        For Each objRuleItem in Rule.Items
          Debug.Trace objRuleItem.Phrase & " (" & objRuleItem.Property & ")" & chr(13)
        Next
        Debug.Trace "--" & chr(13) & "Added " & Rule.Items.Count & " items!" & chr(13)

      End Sub     
    ]]>
  </ruleScript>

</speechMacros>

Have you ever found yourself giving a presentation about speech recognition and wished you could simply advance from slide to slide using speech recognition? Well ... I have.

I've been using this really cool microphone I got from eMicrophones, called a RevoLabs xTag. It let's me walk around the audience, and still be able to talk to my PC tethered to the video projection system way back at the front of the room. It makes for an impressive Vista speech demo.

That's pretty cool, but then how can I go from slide to slide. Good question. That's where today's macro of the day comes in: "Next Slide".

It let's you say things like "Next Slide", or "Previous Slide", or even more cool than that, "Go to slide [1to99]". That's really impressive, but only if you can remember the slide numbers for all your slides. I try and do that now for a handful of the important slides in my presentations.

Check it out:

<speechMacros>

  <appIsInForeground processName="powerpnt.exe"/>

  <command>
    <listenFor>Next ?Slide</listenFor>
    <sendKeys>{right}</sendKeys>
  </command>

  <command>
    <listenFor>Previous ?Slide</listenFor>
    <sendKeys>{left}</sendKeys>
  </command>

  <command>
    <listenFor>?Go Forward [1to20] ?Slide</listenFor>
    <sendKeys times="{[1to20]}">{right}</sendKeys>
  </command>

  <command>
    <listenFor>?Go Back [1to20] ?Slide</listenFor>
    <sendKeys times="{[1to20]}">{left}</sendKeys>
  </command>

  <command>
    <listenFor>Go To Slide [1to99]</listenFor>
    <sendKeys>{[1to99]}{enter}</sendKeys>
  </command>

  <numbers name="1to20" start="1" stop="20"/>
  <numbers name="1to99" start="1" stop="99"/>

</speechMacros>

Another good request for a macro came in the other day, this time from Brian (Thanks Brian). Here's what he wanted: A macro that can tell him the weather forecast.

Once you install this macro, you'll be able to say things like "What is the weather like in Redmond", or "Tell me the weather forecast for Redmond". It's only got a few cities in it right now, but you should be able to expand it to have as many cities as you care about.

Check it out here:

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

    <command>
        <listenFor>what is the weather like in [CityName]</listenFor>
        <run command="http://www.weather.com/weather/local/{[CityName.zipCode]}"/>
    </command>

    <command>
        <listenFor>Tell me the weather forecast for [CityName]</listenFor>
        <speak>OK</speak>
        <script language="JScript">

            var xml_doc = new ActiveXObject("Microsoft.XMLDOM");
            xml_doc.async = false;
            xml_doc.load("http://www.rssweather.com/zipcode/{[CityName.zipCode]}/rss.php");

            var titles = xml_doc.getElementsByTagName("title");
            var descriptions = xml_doc.getElementsByTagName("description");

            Application.Speak(titles.item(2).text + " in {[*CityName]}");
            Application.Speak(descriptions.item(2).text);

        </script>
    </command>

    <listenForList name="CityName" propname="zipCode">
        <item propval="45255">Cincinnati</item>
        <item propval="98075">Sammamish</item>
        <item propval="98052">Redmond</item>
        <item propval="98004">Bellevue</item>
        <item propval="98101">Seattle</item>
    </listenForList>

</speechMacros>

Quote of the day from the blogosphere (from Carl Franklin) that caught my eye: "I've started using the speech recognition features and I'm blown away!"

Thanks Carl. Now ... Have you tried using the WSR Macros we just released? You might like that too...

Following up the macro of the day from last week, here's "Read That". Instead of inserting quotes, double quotes, and parentheses and/or formatting text, today's macro allows you to say "Read That", or "Read the next 3 paragraphs"...

I also threw in "What time is it?" and "What's today's date" just for fun...

You'll need to download WSRMacros to use it, but that's easy. Just go here.

Check it out:

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

<!-- Read that -->
<command priority="3">
  <listenFor>read that</listenFor>
  <script language="JScript">
    <![CDATA[
    // Copy the selected text into the clipboard and wait 1/4 second
    Application.SendKeys("{250 WAIT}^c{250 WAIT}");     
    // Get the data out of the clipboard
    var text = Application.clipboardData.getData("text");
    Application.Speak(text);
    ]]>
  </script>
</command>

<command priority="2">
  <listenFor>read [something]</listenFor>
  <emulateRecognition>select {[something]}</emulateRecognition>
  <waitFor seconds=".25"/>
  <emulateRecognition>read that</emulateRecognition>
</command>

<command priority="1">
  <listenFor>read [textInDocument]</listenFor>
  <listenFor>read the word [textInDocument]</listenFor>
  <emulateRecognition waitForDisambiguation="15">select {[textInDocument]}</emulateRecognition>
  <waitFor seconds=".25"/>
  <emulateRecognition>read that</emulateRecognition>
</command>

<command priority="0">
  <listenFor>read *</listenFor>
  <setTextFeedback style="warning">What was that?</setTextFeedback>
</command>

<!-- Stop Reading -->
<command>
  <listenFor>?please stop reading</listenFor>
  <speak speakFlags="2">Stopped</speak>
</command>

  <!-- Time and date -->

  <!-- What time is it? -->
  <command>
    <listenFor>what time is it</listenFor>
    <script language="JScript">
      <![CDATA[
      var date = new Date();
      var whatToSay = "It's ";
      var hours = date.getHours();
      var AMorPM;
      if (date.getHours() >= 12)
      {
        AMorPM = " P. M.";
        if (date.getHours() != 12)
        {
          hours -= 12
        }
      }
      else
      {
        AMorPM = " A. M.";
      }
      whatToSay += hours;
      if (date.getMinutes() == 0)
      {
        whatToSay += " o'clock";
      }
      else
      {
        if (date.getMinutes() < 10)
        {
          whatToSay += " o ";
        }