Macro of the Day: Mute the speakers!

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: 
  https://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>