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

<rdf:RDF
 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 xmlns="http://purl.org/rss/1.0/"
 xmlns:content="http://purl.org/rss/1.0/modules/content/"
 xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/"
 xmlns:dc="http://purl.org/dc/elements/1.1/"
 xmlns:syn="http://purl.org/rss/1.0/modules/syndication/"
 xmlns:admin="http://webns.net/mvcb/"
>

<channel rdf:about="http://tabletumlnews.powerblogs.com/">
<title>Tablet UML News</title>
<link>http://tabletumlnews.powerblogs.com/</link>
<description>News and commentary from Martin L. Shoemaker, author of Tablet UML</description>
<dc:language>en-us</dc:language>
<dc:date>2007-04-10T14:04+00:00</dc:date>
<items>
 <rdf:Seq>
  <rdf:li rdf:resource="http://tabletumlnews.powerblogs.com/posts/1176216254.shtml" />
  <rdf:li rdf:resource="http://tabletumlnews.powerblogs.com/posts/1122474050.shtml" />
  <rdf:li rdf:resource="http://tabletumlnews.powerblogs.com/posts/1175861713.shtml" />
  <rdf:li rdf:resource="http://tabletumlnews.powerblogs.com/posts/1144819538.shtml" />
 </rdf:Seq>
</items>
</channel>

<item rdf:about="http://tabletumlnews.powerblogs.com/posts/1176216254.shtml">
<title>I'll be there, too!</title>
<link>http://tabletumlnews.powerblogs.com/posts/1176216254.shtml</link>
<description>...</description>
<dc:creator>Martin L. Shoemaker</dc:creator>
<dc:date>2007-04-10T14:04+00:00</dc:date>
<content:encoded><![CDATA[<a href="http://www.grdotnet.org/DODN07/"><img src="http://www.grdotnet.org/DODN07/images/Site-Badge-I.gif" alt="WM Day of .Net May 19, 2007 - I'll be there!" /></a> <br />
<br />
Will you?]]></content:encoded>
</item>

<item rdf:about="http://tabletumlnews.powerblogs.com/posts/1122474050.shtml">
<title>My speaking and other travel schedule (Revised April 10, 2007)</title>
<link>http://tabletumlnews.powerblogs.com/posts/1122474050.shtml</link>
<description>UPDATE: To make it easier to find this entry, I've added a link to it in the right sidebar, right under the links for my books and my classes....</description>
<dc:creator>Martin L. Shoemaker</dc:creator>
<dc:date>2007-04-10T14:04+00:00</dc:date>
<content:encoded><![CDATA[UPDATE: To make it easier to find this entry, I've added a link to it in the right sidebar, right under the links for my books and my classes.<br />
<br />
<a href="http://www.grdotnet.org">West Michigan .NET User Group</a> in Grand Rapids MI. April 17. Topic: Dee Jay: A Voice-Controlled Juke Box for Windows Vista.<br />
<br />
<a href="http://www.dayofdotnet.org/Sessions.aspx">Ann Arbor Day of .NET</a> in Ann Arbor MI. May 5. Topic: Talking with Vista.<br />
<br />
<a href="http://www.grdotnet.org/DODN07/Sessions.aspx">West Michigan Day of .NET</a> in Grand Rapids MI. May 5. Topics: Do, Undo, Redo, Do Over: A Generics Command Pattern Implementation; Talking with Vista.<br />
<br />
<a href="http://huntug.org/DesktopDefault.aspx">Huntsville New Technology User Group</a> in Huntsville AL. September 11. Topic: Dee Jay: A Voice-Controlled Juke Box for Windows Vista.<br />
]]></content:encoded>
</item>

<item rdf:about="http://tabletumlnews.powerblogs.com/posts/1175861713.shtml">
<title>I'll be there!</title>
<link>http://tabletumlnews.powerblogs.com/posts/1175861713.shtml</link>
<description>...</description>
<dc:creator>Martin L. Shoemaker</dc:creator>
<dc:date>2007-04-06T12:04+00:00</dc:date>
<content:encoded><![CDATA[<a href="http://www.dayofdotnet.org"><img src="http://www.dayofdotnet.org/images/DoDNBadge.png" alt="Day of .Net May 5, 2007 - I'll be there!" /></a><br />
<br />
Will you?]]></content:encoded>
</item>

<item rdf:about="http://tabletumlnews.powerblogs.com/posts/1144819538.shtml">
<title>Vox: A text-to-speech application for keyboard and pen</title>
<link>http://tabletumlnews.powerblogs.com/posts/1144819538.shtml</link>
<description>It looks like I'll be speaking at the Day of .NET in Ann Arbor, MI. And in preparation for that event, the Ann Arbor Computer Society is having a...</description>
<dc:creator>Martin L. Shoemaker</dc:creator>
<dc:date>2006-04-12T06:04+00:00</dc:date>
<content:encoded><![CDATA[It looks like I'll be speaking at <a href="http://dayofdotnet.org/">the Day of .NET in Ann Arbor, MI</a>. And in preparation for that event, the <a href="http://www.ComputerSociety.org">Ann Arbor Computer Society</a> is having a night of <a href="http://www.groktalk.com/blog">Grok Talks</a> on .NET topics, and I was asked to participate. For those who haven't seen them before, a Grok Talk is a quick (ten minutes or less) talk on a small topic, with a focus on demonstrating just a little bit of code or a little technique people can use in their projects. Now normally, I have a bit of Ent in me: ten minutes is scarcely enough time to say good morning. But when I needed to come up with a topic for my Grok Talk and I hadn't enough time to think through it, I said, "Text to speech." And then afterwards, I thought about it, and realized: with Microsoft Speech API (SAPI) 5.1, text-to-speech takes three lines of code. Literally:<br />
<br />
<blockquote><br />
ISpVoice mVoice = new SpVoiceClass();<br />
uint uiStream;<br />
mVoice.Speak("Hello, world!", 1, out uiStream);<br />
</blockquote><br />
<br />
And one of those lines is a variable declaration! So this topic would take me less than a minute to cover. Whatever would I do with the remaining nine minutes?<br />
<br />
So being the Tablet PC guy that I am, I decided to mix in some Ink capabilities as well. So I had a vision of an application that would speak whatever the user writes.<br />
<br />
And thus, with some additional thought and design and coding, was born <a href="http://www.TabletUML.com/Demos/Vox.zip">Vox: A text-to-speech application for keyboard and pen</a>; and once I wrote it, I decided to use it as a springboard for discussing Ink and Speech programming under .NET.<br />
<br />
Vox is available free to anyone who's interested. I haven't tested it on a non-Tablet PC yet; but it's supposed to work just as well there, because I detect at run time whether the computer has the Tablet PC OS or not, and fall back to non-Tablet mode if not. You can type any message (or write it on a Tablet PC), and Vox will speak the message. It will also remember every message, so that you can repeat a message easily. And it has a user-editable list of Quick Words at the bottom of the form, so that you can tap a word or phrase to say it without writing or typing.<br />
<br />
If you like Vox, let me know; and if you would like to learn about the code...<br />
<br />
<div class="trigger" id="shelx8318f.11"><a href="#" onClick="document.getElementById('helx8318f.11').style.display = 'block'; document.getElementById('shelx8318f.11').style.display = 'none'; return false;">Read more about Vox</a></div><br />
<div class="hidden" style="display: none;" id="helx8318f.11"><br />
<br />
I started by creating a WinForms app. Then I added a reference to the SAPI COM DLL. That provides basic speech capabilities. Then I added the voice:<br />
<br />
<blockquote><br />
/// <summary><br />
/// Voice for speaking.<br />
/// </summary><br />
private static ISpVoice mVoice = new SpVoiceClass();<br />
</blockquote><br />
<br />
ISpVoice is the SAPI interface for text-to-speech, and SpVoiceClass is the class that implements it.<br />
<br />
Next I added the InkEdit control, a Tablet PC control that accepts text input through either the keyboard or the pen. But InkEdit inherits from RichTextBox, which is a standard WinForms control. So I used the .NET Assembly class to attempt to load the Tablet PC Ink library, and create an InkEdit control; and if that failed at any step, I just created a RichTextBox instead:<br />
<br />
<blockquote><br />
// Create the Ink Edit or Rich Text. We can't do<br />
// this in InitializeComponent because we want to<br />
// support InkEdit on Tablets or just RichTextBox<br />
// on non-Tablets.<br />
try<br />
{<br />
<blockquote><br />
    Assembly a = Assembly.Load("Microsoft.Ink, Version=1.7.2600.2180, Culture=neutral, PublicKeyToken=31bf3856ad364e35");<br />
    this.txtVox = a.CreateInstance(<br />
        "Microsoft.Ink.InkEdit") as RichTextBox;<br />
</blockquote><br />
}<br />
catch(Exception ex)<br />
{<br />
<blockquote><br />
    this.txtVox = new RichTextBox();<br />
</blockquote><br />
}<br />
</blockquote><br />
<br />
Next I added a handler for the TextChanged event of the text box:<br />
<br />
<blockquote><br />
/// <summary><br />
/// User changed text, either via voice or keyboard.<br />
/// Get ready to speak.<br />
/// </summary><br />
/// <param name="sender">Stock.</param><br />
/// <param name="e">Stock.</param><br />
private void txtVox_TextChanged(object sender, EventArgs e)<br />
{<br />
<blockquote><br />
    // We can't speak right away. If the user is typing,<br />
    // we'll get this message on every character. That<br />
    // would be REALLY annoying. So instead, we'll start<br />
    // or restart a timer, and speak when we pause.<br />
    StartTimer();<br />
</blockquote><br />
}<br />
</blockquote><br />
<br />
As per the comments, I chose not to make Vox speak after every TextChanged event. That would make it speak o n e l e t t e r   a t   a   t i m e , which would be really annoying. Instead, I start (or restart) a delay timer. (It defaults to one second, but the user can choose half a second or two seconds). If no new text comes in before the timer expires, <i>then</i> I make Vox speak:<br />
<br />
<blockquote><br />
/// <summary><br />
/// When the timer expires, speak the new text.<br />
/// </summary><br />
/// <param name="sender">Stock.</param><br />
/// <param name="e">Stock.</param><br />
private void tmrVox_Tick(object sender, EventArgs e)<br />
{<br />
<blockquote><br />
    SpeakNewText();<br />
</blockquote><br />
}<br />
</blockquote><br />
<br />
The SpeakNewText method ensures that we only speak the <i>new</i> text:<br />
<br />
<blockquote><br />
/// <summary><br />
/// Speak the text that has changed since the last call<br />
/// to this method.<br />
/// </summary><br />
private void SpeakNewText()<br />
{<br />
<blockquote><br />
    // Turn off the timer.<br />
    tmrVox.Enabled = false;<br />
<br />
    // Assume the full current text.<br />
    string message = txtVox.Text.Trim();<br />
<br />
    // Strip out prior text.<br />
    if (!string.IsNullOrEmpty(mLastMessage))<br />
    {<br />
<blockquote><br />
        // Find the prior text.<br />
        int idxPrevious = message.IndexOf(mLastMessage);<br />
        if (idxPrevious != -1)<br />
        {<br />
<blockquote><br />
            // If it's at the start or end, strip it off.<br />
            if (idxPrevious == 0)<br />
            {<br />
<blockquote><br />
                message = message.Substring(<br />
                    idxPrevious + mLastMessage.Length);<br />
</blockquote><br />
            }<br />
            else if ((idxPrevious + mLastMessage.Length) >= message.Length)<br />
            {<br />
<blockquote><br />
                message = message.Substring(0,<br />
                    idxPrevious);<br />
</blockquote><br />
            }<br />
</blockquote><br />
        }<br />
</blockquote><br />
    }<br />
<br />
    // Remember this for next time.<br />
    mLastMessage = txtVox.Text.Trim();<br />
<br />
    // Speak!<br />
    SpeakMessage(message, true);<br />
</blockquote><br />
}<br />
</blockquote><br />
<br />
First it searches for the old text within the current text:<br />
<br />
<blockquote><br />
    // Assume the full current text.<br />
    string message = txtVox.Text.Trim();<br />
<br />
    // Strip out prior text.<br />
    if (!string.IsNullOrEmpty(mLastMessage))<br />
    {<br />
<blockquote><br />
        // Find the prior text.<br />
        int idxPrevious = message.IndexOf(mLastMessage);<br />
        if (idxPrevious != -1)<br />
        {<br />
</blockquote><br />
</blockquote><br />
<br />
Then if the old text was found at either the start or end of the current text, it's stripped off:<br />
<br />
<blockquote><br />
            // If it's at the start or end, strip it off.<br />
            if (idxPrevious == 0)<br />
            {<br />
<blockquote><br />
                message = message.Substring(<br />
                    idxPrevious + mLastMessage.Length);<br />
</blockquote><br />
            }<br />
            else if ((idxPrevious + mLastMessage.Length)<br />
                >= message.Length)<br />
            {<br />
<blockquote><br />
                message = message.Substring(0,<br />
                    idxPrevious);<br />
</blockquote><br />
            }<br />
</blockquote><br />
<br />
And then we store the current text for next time; and then we speak the remainder with SpeakMessage:<br />
<br />
<blockquote><br />
/// <summary><br />
/// Speak a message and store it for recall.<br />
/// </summary><br />
/// <param name="message"></param><br />
/// <param name="record">True to add to the prior message log.</param><br />
public static void SpeakMessage(string message,bool record)<br />
{<br />
<blockquote><br />
    // Test for invalid message.<br />
    if (message == null)<br />
    {<br />
<blockquote><br />
        throw new ArgumentNullException("message");<br />
</blockquote><br />
    }<br />
<br />
    // Test for empty message.<br />
    message = message.Trim();<br />
    if (!string.IsNullOrEmpty(message))<br />
    {<br />
<blockquote><br />
        // Say it!<br />
        uint uiStream;<br />
        mVoice.Speak(message, 1, out uiStream);<br />
<br />
        // Record it.<br />
        if ((record) &&<br />
            (!sActiveForm.tcboxPrevious.Items.Contains(<br />
              message)))<br />
        {<br />
<blockquote><br />
            sActiveForm.tcboxPrevious.Items.Insert(0,<br />
                message);<br />
            sActiveForm.tcboxPrevious.SelectedIndex = -1;<br />
</blockquote><br />
        }<br />
</blockquote><br />
    }<br />
</blockquote><br />
}<br />
</blockquote><br />
<br />
The core of this method is this call:<br />
<br />
<blockquote><br />
        // Say it!<br />
        uint uiStream;<br />
        mVoice.Speak(message, 1, out uiStream);<br />
</blockquote><br />
<br />
The speak method performs the text-to-speech. The message parameter tells it what to speak. The uiStream output parameter will receive the stream number, which will increase as multiple Speak calls queue up. And the flag value, 1, indicates that we want asynchronous speech, allowing us to continue typing or writing while Vox speaks.<br />
<br />
The other important feature we need is a way to stop speaking. Imagine you have a page of text, and Vox has just finished speaking; and then imagine that you delete the last character from it. Then as SpeakNewText is written above, the old text will be longer than the new text, and so won't be found within it; so the entire page of text will be spoken again. Now I think that this design is the best we can do, over all, but we don't want to hear the whole page over again. So Vox includes a stop button.<br />
<br />
Now what's interesting is that ISpVoice doesn't include a Cancel or Stop method. There's no direct way to stop the voice once it's speaking. But ISpVoice does include two other useful methods: GetStatus and Skip. GetStatus allows you to test whether the voice is speaking; and skip allows you to skip ahead by some unit of speech. (The only unit supported in SAPI 5.1 is SENTENCE.) So our Stop method looks like this:<br />
<br />
<blockquote><br />
/// <summary><br />
/// Stop speaking the current message.<br />
/// </summary><br />
public static void StopSpeaking()<br />
{<br />
<blockquote><br />
    // Get the status.<br />
    SPVOICESTATUS vstat;<br />
    string mark;<br />
    mVoice.GetStatus(out vstat, out mark);<br />
<br />
    // While still speaking, skip ahead.<br />
    while (vstat.dwRunningState == 2)<br />
    {<br />
<blockquote><br />
        // Skip 1000 sentences. There appears to be no<br />
        // harm in skipping past the end.<br />
        uint ui;<br />
        mVoice.Skip("SENTENCE", 1000, out ui);<br />
<br />
        // Get the status again.<br />
        mVoice.GetStatus(out vstat, out mark);<br />
</blockquote><br />
    }<br />
</blockquote><br />
}<br />
</blockquote><br />
<br />
Originally, I made it skip ahead 1 sentence. That worked well for small pages; but I found that in longer pages, it would speak the first fractional syllable of many of the sentences, which would be really annoying. With a little experimentation, I found that there are no errors that occur if you skip too far and pass the end of the queued up Speak calls. So to be really sure to quit quickly, I jump ahead by 1000 sentences.<br />
<br />
Now there's more to Vox than that; but the rest of it is pretty straightforward WinForms programming. These are the parts that really show a little bit of Ink and Voice programming. If you would like to see the rest of the source code, leave me a comment, and I can send it to you.<br />
<br />
<div class="trigger"><a href="#" onClick="document.getElementById('shelx8318f.11').style.display = 'block';document.getElementById('helx8318f.11').style.display = 'none'; return false;">Now hide all this ugly code talk!</a></div></div>]]></content:encoded>
</item>

</rdf:RDF>