<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:copyright="http://blogs.law.harvard.edu/tech/rss" xmlns:image="http://purl.org/rss/1.0/modules/image/">
    <channel>
        <title>.NET</title>
        <link>http://geekswithblogs.net/UlteriorMotiveLounge/category/9125.aspx</link>
        <description>Discussions of .NET development with any language or tool.</description>
        <language>en-US</language>
        <copyright>Martin L. Shoemaker</copyright>
        <managingEditor>Martin@TheUMLGuy.com</managingEditor>
        <generator>Subtext Version 0.0.0.0</generator>
        <item>
            <title>Dr. Strained Memory or: How I Learned to Stop Worrying and Love Compact Framework Memory Management</title>
            <link>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/dr.-strained-memory-or-how-i-learned-to-stop-worrying.aspx</link>
            <description>&lt;div class="bvEntry" id="entrycns!2D0DCE281FBB99D!1416" bv:cns="cns!2D0DCE281FBB99D!1416" bv:ca="true" bv:cat=".NET Compact Framework Programming"&gt;
&lt;div class="bvMsg" id="msgcns!2D0DCE281FBB99D!1416"&gt;
&lt;div&gt;OK... So why has &lt;a href="http://tabletuml.spaces.live.com/blog/cns!2D0DCE281FBB99D!1415.entry"&gt;my DODO been incalculable&lt;/a&gt;? Well, because we've been chasing down memory problems in our .NET Compact Framework 2.0 app that runs under Windows CE 5.0.&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;And if that just sent a chill down your spine, then you've probably been here before. You have my sympathies.&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;If you haven't been here before but you're planning a CF/CE app: Be afraid. Be very afraid.&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;The following is a brief summary of what we've learned (the hard way) about memory management in a Compact Framework application. &lt;/div&gt;
&lt;h2&gt;"In the beginning, the Universe was created. This made a lot of people angry, and has been widely regarded as a bad idea."&lt;/h2&gt;
&lt;div&gt;My client's application comes in two versions: one that runs on a laptop, and one that runs on a handheld computer running &lt;a target="_blank" href="http://msdn2.microsoft.com/en-us/library/ms905511.aspx"&gt;Windows CE 5&lt;/a&gt;. Since both versions run .NET -- &lt;a href="http://support.microsoft.com/?scid=ph;en-us;8291"&gt;.NET Framework 2.0&lt;/a&gt; on the laptop, &lt;a target="_blank" href="http://msdn2.microsoft.com/en-us/netframework/aa497277.aspx"&gt;.NET Compact Framework 2.0&lt;/a&gt; on the handheld -- they figured to share a lot of code between the two platforms, including a homebrew implementation of a &lt;a rel="nofollow" href="http://msdn.microsoft.com/msdnmag/issues/06/08/DesignPatterns/default.aspx"&gt;Model-View-Presenter&lt;/a&gt; architecture. The implementation of View (and Presenter, to a lesser extent) had to change across platforms; but for the most part, what was a good architecture for the laptop was seen as a good architecture for the handheld.&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;First mistake, there...&lt;/div&gt;
&lt;div&gt; &lt;/div&gt;
&lt;div&gt;And although I wasn't here when that mistake was made, I probably would've made the same mistake myself. I didn't realize that on the handheld platform, you need different coding priorities.&lt;/div&gt;
&lt;h2&gt;"Do you know what the secret of life is? This. One thing. Just one thing. You stick to that and the rest don't mean ****."&lt;/h2&gt;
&lt;div&gt;When you code, the quality of the result depends significantly on your priorities: what do you want from the code? Every developer has an idea of the "correct" coding priorities. Some value small code, or fast code, or readable code, or clever code. Steve McConnell once advised deciding &lt;em&gt;and announcing&lt;/em&gt; your priorities for a given project before work begins, so everyone knows what's important for &lt;em&gt;this&lt;/em&gt; project. You might make a prioritized list, such as:&lt;/div&gt;
&lt;ol&gt;
    &lt;li&gt;Correctness. &lt;/li&gt;
    &lt;li&gt;Maintainability/readability. &lt;/li&gt;
    &lt;li&gt;Speed. &lt;/li&gt;
    &lt;li&gt;Extensibility. &lt;/li&gt;
    &lt;li&gt;Efficient use of memory. &lt;/li&gt;
    &lt;li&gt;Schedule. &lt;/li&gt;
    &lt;li&gt;Modularity. &lt;/li&gt;
    &lt;li&gt;Reusability.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And so on. Just let the team know what's expected from them, and you're more likely to get it from them. &lt;/p&gt;
&lt;p&gt;But for many teams, that list is too much bother. They can get by with a simple mantra: &lt;/p&gt;
&lt;blockquote dir="ltr" style="MARGIN-RIGHT: 0px"&gt;
&lt;p&gt;&lt;strong&gt;Make it run.&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Make it right.&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Make it fast.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p dir="ltr"&gt;Make it run, so users can give you feedback. Make it right from their feedback. And then optimize only when necessary to make the users happy, because optimization can be difficult to read and maintain and is prone to bugs. &lt;/p&gt;
&lt;p dir="ltr"&gt;Well, we've learned a new mantra for the handheld: &lt;/p&gt;
&lt;blockquote dir="ltr" style="MARGIN-RIGHT: 0px"&gt;
&lt;p dir="ltr"&gt;&lt;strong&gt;Make it small.&lt;/strong&gt; &lt;/p&gt;
&lt;p dir="ltr"&gt;&lt;strong&gt;Make it run.&lt;/strong&gt; &lt;/p&gt;
&lt;p dir="ltr"&gt;&lt;strong&gt;Make it right.&lt;/strong&gt; &lt;/p&gt;
&lt;p dir="ltr"&gt;&lt;strong&gt;Make it fast.&lt;/strong&gt; &lt;/p&gt;
&lt;p dir="ltr"&gt;&lt;strong&gt;Repeat, repeat, repeat...&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p dir="ltr"&gt;You have to make it small &lt;strong&gt;first&lt;/strong&gt;; because on CE 5, if it's not small, it will never run right, and it will be fast only in crashing. And then, when you take steps to make it run and right and fast, you'll inevitably make it larger, and risk crashing due to that. &lt;/p&gt;
&lt;p dir="ltr"&gt;So on CE 5, you have to design for small first. Adding small afterward is a great way to push your DODO to Infinity. I should know. &lt;/p&gt;
&lt;h2 dir="ltr"&gt;"A Man's Gotta Know His Limitations."&lt;/h2&gt;
&lt;p dir="ltr"&gt;So here's the short summary of what we've learned about our limits. First, keep in mind that this code comes in two general types: Native, and Managed. Native Code is code compiled directly for the platform, and responsible for managing and allocating its own memory and other resources. Managed Code is .NET Compact Framework code, which runs inside the Common Language Runtime, a Native Code "shell" that loads and runs and manages Managed Code. It takes care of allocating memory; and if -- &lt;strong&gt;if&lt;/strong&gt; -- you let it, it does its darnedest to clean up that memory when you're done with it. And Managed Code may very well load and call Native Code for certain purposes. (Native Code can load and run Managed Code as well; but for this discussion, that's irrelevant.) &lt;/p&gt;
&lt;p dir="ltr"&gt;Some of what follows applies only to Native Code. Some applies only to Managed Code. And some applies to both. &lt;/p&gt;
&lt;h3 dir="ltr"&gt;"I want to be an explorer, like the Great Magellan." "Oh, you're too late! There's nothing left to explore!"&lt;/h3&gt;
&lt;p dir="ltr"&gt;CE 5 uses Virtual Memory and memory mapping to give each running  process a "private" memory space. I'm no memory management guru; but as best I understand it, this means that each process thinks it "owns" the machine. The Operating System (CE 5) maps into part of that memory space. The app and all its data map into another part. On handheld devices, memory is usually pretty limited (128 MB on board, in our case, and 1 GB on card), and probably less than the addressable space in a process's memory map. &lt;/p&gt;
&lt;p dir="ltr"&gt;The OS itself loads into a given memory location. I can't say where, because I'm not a memory guru. I'm trying to give an overview here, not details. But the important part is that your process then receives an address space in which your process lives in the space from 0 GB to 1 GB, and "global" storage lives in 1 GB to 2 GB. The OS lives beyond that space. &lt;/p&gt;
&lt;h3 dir="ltr"&gt;"Game over, man! Game over!"&lt;/h3&gt;
&lt;p dir="ltr"&gt;OK, so your process's memory map lets it address 1 GB (between addresses 0 GB and 1 GB); but here's the kicker, and here's where our troubles began. The actual memory accessible to your app -- also known as the Virtual Memory, i.e., the chunk of memory mapped into that 1 GB space -- &lt;strong&gt;is restricted to 32 MB on Windows CE&lt;/strong&gt;. Period. No matter what you do. If your app needs more than 32 MB to store your currently loaded code and data, that's it. You're gonna crash. In our case, &lt;em&gt;we&lt;/em&gt; didn't crash; but we made calls to &lt;a target="_blank" href="http://www.microsoft.com/sql/editions/compact/default.mspx"&gt;SQL Mobile&lt;/a&gt; in a low-memory condition, and it'll crash like clockwork, every time. &lt;/p&gt;
&lt;p dir="ltr"&gt;So that's simple, right? Stay under 32 MB, right? What kind of freakin' &lt;em&gt;handheld&lt;/em&gt; app needs 32 MB, anyway? &lt;/p&gt;
&lt;p dir="ltr"&gt;Well, one that was ported from the laptop without sufficient consideration for &lt;strong&gt;Make it small, make it run, make it right, make it fast&lt;/strong&gt;, that's what kind. &lt;/p&gt;
&lt;p dir="ltr"&gt;And oh, if only it were as simple as waving a magic wand and saying "Keep it under 32 MB"... &lt;/p&gt;
&lt;h3 dir="ltr"&gt;"Take your stinking paws off me, you damned dirty ape!"&lt;/h3&gt;
&lt;p dir="ltr"&gt;Maybe, &lt;em&gt;maybe&lt;/em&gt; you can find a way to do what you need within 32 MB. (Probably not: when you tell customers that they're getting portability as a tradeoff for power and features, they never hear the tradeoff part. They'll want your handheld app to be portable &lt;em&gt;and&lt;/em&gt; as powerful as your laptop app &lt;em&gt;and&lt;/em&gt; as fast. Learn to tell them "no" as early as possible.) &lt;/p&gt;
&lt;p dir="ltr"&gt;But you're not the only one who gets to use that 32 MB. No, in fact, every app in the system has its stinking paws on your 32 MB, &lt;strong&gt;including&lt;/strong&gt; the OS! &lt;/p&gt;
&lt;p dir="ltr"&gt;See, when an app is actively running on the handheld, its Virtual Memory is mapped into a special reserved 32 MB slot in the system, slot 0. Your app itself is loaded at the bottom of slot 0, growing up. Any &lt;strong&gt;unmanaged&lt;/strong&gt; DLLs it loads are loaded at the top of slot 0, growing down as each is loaded. (Each DLL is allocated space in 64K chunks, so DLLs that are close to 64K in size -- or 128K, or 192K, or... -- are loaded most efficiently.) And the space in between is available for your app's data. You can also store data in the "global" area from 1 GB to 2 GB; but that's a lot more difficult to use, so you don't want to if you don't have to. &lt;/p&gt;
&lt;p dir="ltr"&gt;But here's the tricky part: because handhelds have limited memory, CE 5 optimizes the memory used by DLLs. If a DLL is loaded in one app, CE doesn't load it into another app that also uses it. CE loads it once, period. But in order to do this, CE loads the DLL into the same location in every app that uses it -- and effectively, &lt;strong&gt;every app that &lt;em&gt;doesn't&lt;/em&gt; use it.&lt;/strong&gt; The unmanaged DLLs are loaded into the top of slot 0 itself, and just left there as different apps are mapped into slot 0 and back out again. So your app takes the hit for every unmanaged DLL loaded anywhere in the device. That can &lt;em&gt;even&lt;/em&gt; include OS DLLs: if the OS runs out of room for its DLLs in its reserved memory, it loads them into slot 0. (In our case, after the vendor did some tuning, that only meant one unmanaged DLL loaded into slot 0 by the OS itself.) &lt;/p&gt;
&lt;h3 dir="ltr"&gt;"Invention, my dear friends, is 93% perspiration, 6% electricity, 4% evaporation, and 2% butterscotch ripple." "That's 105 percent."&lt;/h3&gt;
&lt;p dir="ltr"&gt;Now if the 32 MB limit sounds scary, and the external DLLs chewing up your 32 MB sounds even scarier... Well, you're right, they are scary. &lt;/p&gt;
&lt;p dir="ltr"&gt;But before you set out to try to work around them, ask yourself one thing: are you using .NET Compact Framework 2.0 (or later)? Because if you are, then the CF team has already given you an invention where they put in the perspiration, electricity, evaporation, and butterscotch ripple. Sadly, it's &lt;em&gt;not&lt;/em&gt; a 105% solution (more like an 80% solution in our case); but it's very likely better than you have time and resources to devise and implement and test and maintain yourself. So before you start jumping through hoops, let's look at the hoops they've already jumped through for you. &lt;/p&gt;
&lt;p dir="ltr"&gt;When you build a .NET app (Compact Framework or regular Framework), the &lt;em&gt;real&lt;/em&gt; application is the CLR itself. It's the thing that runs, not your code; and then it loads your code, and uses your code to tell it what to do. &lt;/p&gt;
&lt;p dir="ltr"&gt;So what this means on the Compact Framework is that the CLR can be smart: it &lt;em&gt;knows&lt;/em&gt; about the 32 MB limit, so it works overtime to try to get around it. The biggest technique it uses is that &lt;em&gt;all&lt;/em&gt; of your Managed DLLs &lt;em&gt;and&lt;/em&gt; your managed application are loaded into the global storage area between 1 GB and 2 GB. Then, as you call the code in your app and your DLLs, the CLR loads the pieces you use into an area within your 32 MB VM. This JIT (Just In Time) Heap starts at about 1 MB, and grows as needed; but if the CLR sees that memory is low, it "pitches" code out of the JIT Heap, keeping only the more recently used code. So the JIT Heap can be treated as a 1 MB block under normal circumstances. &lt;/p&gt;
&lt;p dir="ltr"&gt;And another way in which the CF CLR is smart: if any one object you allocate is larger than 2 MB (or it might be 1 MB, I'll need to check), it &lt;em&gt;automatically&lt;/em&gt; allocates from the global storage, &lt;em&gt;without&lt;/em&gt; making you jump through all the hoops involved. The CLR knows how to jump through the hoops. That's its job. &lt;/p&gt;
&lt;p dir="ltr"&gt;If you think you can write better memory management than the CLR provides, be my guest. Be sure to allow lots of time in your schedule for design, redesign, implementation, testing, fixing, retesting, refixing, reretesting, reredesign, nervous breakdowns... Now CLR memory management does not come without a cost, of course: the CLR itself requires another 1 MB block out of your VM. And other data used by the CLR can add up as well, in the form of different heaps: &lt;/p&gt;
&lt;ul dir="ltr"&gt;
    &lt;li&gt;
    &lt;div&gt;The AppDomain Heap describes the assemblies, modules, and types used by your app. The CLR uses these to find and load and decipher code as you call it. It grows as the overall app+DLLS grow.&lt;/div&gt;
    &lt;/li&gt;
    &lt;li&gt;
    &lt;div&gt;The Process Heap and the Short Term Heap are memory used and discarded by the CLR as it works. These are usually small, usually transitory, and pretty much always beyond your control.&lt;/div&gt;
    &lt;/li&gt;
    &lt;li&gt;
    &lt;div&gt;And finally, the GC (Garbage Collected) Heap itself, where the CLR allocates all of your data (other than large allocations stuck in global storage). Anything in here can be released by your code at any time, and the CLR will Garbage Collect it to free up space when it sees a need.&lt;/div&gt;
    &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So your real limit in a Managed app is: 32 MB, minus the size of Native DLLs in slot 0 (in our case, 5 MB, mostly due to SQL Mobile), minus 1 MB for the CLR, minus the sum of all the heaps (with a minimum around 2 MB). That's how much Native data you can afford. Because remember: &lt;strong&gt;your Native DLLs will allocate memory in your 32 MB VM as well.&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;And frankly, for many sorts of handheld apps, that's a lot. But for anything ambitious, anything that really makes customers excited... That's not enough. &lt;/p&gt;
&lt;p&gt;In testing of our app (explanations below), the JIT Heap, the AppDomain Heap, and the GC Heap have climbed to nearly 13 MB. Add in 5 MB for Native DLLs, 1 MB for the CLR, and 1 MB for the Process and Short Term Heap, and we're up to 20 MB out of 32. That's enough to make me nervous. And for large SQL Mobile queries, that may be enough to crash. (SQL Mobile usually crashes when we make a large query with less than 2 MB VM free; but we once saw it crash with 3.4 MB free.) &lt;/p&gt;
&lt;h2&gt;"No. Try not. Do... or do not. There is no try."&lt;/h2&gt;
&lt;p&gt;So now you know the CF 2.0/CE 5.0 memory landscape. You know, kind of, what your limitations are. &lt;/p&gt;
&lt;p&gt;So if your app starts crashing and memory errors seem to be the diagnosis, you need to throw out stuff that might waste memory, right? &lt;/p&gt;
&lt;p&gt;No. Wrong. Stop. Remember this important mantra:&lt;strong&gt; We're not guessing, we're profiling.&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;With a lot -- &lt;strong&gt;a lot&lt;/strong&gt; -- of experience, you can begin to guess where system performance problems are, and be right maybe half the time. (And by performance, I include memory usage, CPU usage, and other ways your app might bog down a machine.) But people with that much experience will usually tell you: don't guess, use a profiler. &lt;/p&gt;
&lt;p&gt;Now on the laptop, there are many profiling tools available. On CE 5, not so many. One of the best we could find is &lt;a target="_blank" href="http://www.microsoft.com/downloads/details.aspx?FamilyId=C8174C14-A27D-4148-BF01-86C2E0953EAB&amp;amp;displaylang=en"&gt;the .NET Compact Framework Remote Performance Monitor&lt;/a&gt;. (That's a link to the CF 3.5 Power Toys from Microsoft; but the RPM in there works fine with CF 2.0.) This tool will tell you the size of your different heaps. It will also take "snapshots" of your GC heap, which will let you see how many objects of different classes are in use, and how much space they consume. (In our case, the worst offender is &lt;strong&gt;14,408 Strings&lt;/strong&gt;, requiring &lt;strong&gt;0.89 MB of storage&lt;/strong&gt;. The top 10 classes for memory usage consume over 2.6 MB out of a 3.3 MB GC heap. I won't know why until I do more research. I have my guesses, but &lt;strong&gt;we're not guessing, we're profiling.&lt;/strong&gt;) &lt;/p&gt;
&lt;p&gt;Another useful memory profiling tool is &lt;a target="_blank" href="http://support.microsoft.com/kb/326164"&gt;DUMPMEM&lt;/a&gt;. (This is targeted at Pocket PC 2002, but still works OK for CE 5.) This tool is a bit verbose, with &lt;strong&gt;a lot&lt;/strong&gt; of nearly unreadable information; but near the end of the report, it spells out all of the apps running on your device, including which unmanaged DLLs each has loaded. &lt;/p&gt;
&lt;p&gt;Beyond those, we found it easiest to write our own profiling tools. They're crude, but they helped us match memory usage to user activities in the application. They rely heavily on &lt;a target="_blank" href="http://www.opennetcf.com/library/sdf/html/d5516377-cde7-1f1d-f035-bcff8a8278ec.htm"&gt;the MemoryManagement class&lt;/a&gt; from the &lt;a target="_blank" href="http://www.opennetcf.com/"&gt;OpenNETCF&lt;/a&gt; libraries. &lt;/p&gt;
&lt;h2&gt;"Tweaking? A project that needs tweaking?"&lt;/h2&gt;
&lt;p&gt;Finally, some general lessons we learned about memory management under CF 2.0 and CE 5.0: &lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;Design for small from the start. Don't try to add it later. Yes, I repeat myself. It bears repeating. &lt;/li&gt;
    &lt;li&gt;We're not guessing, we're profiling. Yes, I repeat myself. It bears repeating. &lt;/li&gt;
    &lt;li&gt;Don't waste time trying to "tweak" your way to sufficient memory. Tweaking is usually a sign that you're guessing, not profiling. We did a lot of little tweaks that saved us a few K here, a hundred K there... Those all made some difference, but at a lot of cost; whereas finding and eliminating the 14,408 cached strings will probably make the difference we need. When you try to tweak to fit, it's like &lt;a target="_blank" href="http://www.youtube.com/watch?v=Ll2kajMH2u0"&gt;Human Tetris&lt;/a&gt;: you &lt;em&gt;might&lt;/em&gt; twist yourself into exactly the right shape; but if you're wrong, you won't know it until you go &lt;strong&gt;splat!&lt;/strong&gt; Instead of tweaking, profile, identify and locate the major leaks, and fix them. Repeat, repeat, repeat. &lt;/li&gt;
    &lt;li&gt;Build memory diagnostics in from the start. You'll curse me now. You'll thank me later. &lt;/li&gt;
    &lt;li&gt;Graph your diagnostics, and identify on the graphs what the user was doing at each major change. That will help you in locating the major leaks. &lt;/li&gt;
    &lt;li&gt;Don't try to outsmart the CLR and the GC. Maybe you are smarter, but do you have time to build and test and maintain your smarter solution? &lt;/li&gt;
    &lt;li&gt;Sometimes, when your code is leaking, it can &lt;em&gt;appear&lt;/em&gt; that the GC is not collecting. It is, at least as much as it can, and I have the graphs to prove it. Trust the GC. &lt;/li&gt;
    &lt;li&gt;Never call GC.Collect() directly. It never helps -- &lt;em&gt;never!&lt;/em&gt; -- and it often hurts. It never helps because the GC is optimized to not waste too much CPU time. If called, it doesn't do any real garbage collection until a trigger has happened: cumulative Managed allocations have passed a megabyte boundary; GDI+ resources (pens, fonts, brushes, etc.) are used up; or memory is used up. If none of those have happened, no garbage is collected. But it &lt;em&gt;may&lt;/em&gt; hurt, because before testing the triggers, it suspends your whole app, including waiting for each thread to reach a "suspendable" state. So though calling GC.Collect() won't save you any memory, it can cost you time. &lt;/li&gt;
    &lt;li&gt;&lt;em&gt;Maybe&lt;/em&gt; call the unmanaged heap compact API. This seldom has had any effect for us; but on rare occasions, it has reclaimed some Virtual Memory for us. We're still kind of undecided. &lt;/li&gt;
    &lt;li&gt;If you can, wait for CE 6.0. It has a radically expanded memory mapping scheme, including 2 GB of VM per process. If that's not enough, you don't understand that the "C" in "CF" and "CE" stands for "Compact", as in "tiny". If 2 GB is too tiny for you, you're on the wrong platform. But as of this writing, there are &lt;em&gt;no&lt;/em&gt; production devices running CE 6.0. &lt;/li&gt;
    &lt;li&gt;Consider this: is a &lt;a target="_blank" href="http://www.microsoft.com/windows/products/winfamily/umpc/default.mspx"&gt;UMPC&lt;/a&gt; compact enough for your customers? It's small. It's portable. And it will run the same code as your laptop does. (For us, the answer is no; but maybe you'll have a different answer...) &lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;"Think it'll work?" "It would take a miracle."&lt;/h2&gt;
&lt;p&gt;Actually, no, you won't need a miracle to live within the memory limits of CF 2.0 and CE 5.0. You'll just need awareness, design, profiling, patience, perseverance, and maybe a little bit of luck. And maybe I've given you a few more rabbits for your hat. &lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=127125"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=127125" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/UlteriorMotiveLounge/aggbug/127125.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Martin L. Shoemaker</dc:creator>
            <guid>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/dr.-strained-memory-or-how-i-learned-to-stop-worrying.aspx</guid>
            <pubDate>Sat, 15 Nov 2008 23:53:26 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/UlteriorMotiveLounge/comments/127125.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/dr.-strained-memory-or-how-i-learned-to-stop-worrying.aspx#feedback</comments>
            <slash:comments>2</slash:comments>
            <wfw:commentRss>http://geekswithblogs.net/UlteriorMotiveLounge/comments/commentRss/127125.aspx</wfw:commentRss>
        </item>
        <item>
            <title>Project metrics they never taught you in Project Manager training</title>
            <link>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/project-metrics-they-never-taught-you-in-project-manager-training.aspx</link>
            <description>&lt;div class="post"&gt;Project management involves lots of metrics: data you gather, measure, and analyze to assess and predict the state of your project. But I find some of the most useful project metrics are often overlooked. Here are a few to add to your toolbox.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;WSR (Work-to-Sleep Ratio)&lt;/h3&gt;
This is a measure of how likely your team members are to make mistakes at crucial moments. If their WSR for the week is 1 or less, they're probably bored. 1.25 or even 1.5 are signs of a team moving at a good pace. Higher than that, though, can be a problem. 2 is about the limit for a typical team member, and they probably can't keep that up. Rare individuals can maintain a WSR of 3 for a time.&lt;br /&gt;
&lt;br /&gt;
At one point last year, my WSR was 7.5. That's just not good.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;DODO (Days On per Day Off)&lt;/h3&gt;
Often correlates with the WSR, and serves as another measure for the likelihood of mistakes. 2.5 is a normal work week; but honestly, how many of you work normal work weeks? 6 is a common work week for projects in a crunch. A monthly average of 13 or more is a sign that your team members may soon be tied up in family counseling or divorce court.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;HBT (Handbasket Temperature)&lt;/h3&gt;
"It's getting kinda warm in this handbasket. I wonder where we're going in it?" Although this can be hard to measure, your team members probably have opinions on what the HBT is. If they all think it's getting hot, maybe you need to ask where your project's going.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;GALB (Going-Away-Lunch Budget)&lt;/h3&gt;
Every team has transitions. That's normal. But watch your budget for going-away lunches. If it starts to grow, that's because &lt;s&gt;the rats are deserting the sinking ship&lt;/s&gt;the team members find other opportunities more appealing.&lt;br /&gt;
&lt;br /&gt;
Related to this is GAAB: the Going-Away-Alcohol Budget. If your team has some drinks at the going-away lunch, that could simply be because it gives them an excuse to drink during the day. But if the bar bill starts to exceed the food bill, it's probably because the ones who haven't found &lt;s&gt;escape hatches&lt;/s&gt;new opportunities yet are &lt;s&gt;drowning their sorrows&lt;/s&gt;celebrating the good fortune of their former coworkers.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;Dilbert Barometer&lt;/h3&gt;
Credit for this one goes to Scott Adams, creator of &lt;a href="http://www.dilbert.com/"&gt;Dilbert&lt;/a&gt;. (Well, OK, he'll take cash or check, too.)&lt;br /&gt;
&lt;br /&gt;
As Mr. Adams explained in an email I lost sometime last century, the Dilbert Barometer is a rather non-linear scale, where both extremes are bad.&lt;br /&gt;
&lt;br /&gt;
If the programmers are papering their cubicles with old Dilbert strips, that's a sign that they're troubled. Even worse is when they don't just put up any old strips, only selected strips that happen to reflect what's going on in your organization. That means they're making judgments and a statement about the pointy-haired bosses at your company. (At one time, three walls of my cubicle at one job were Dilbert strips from top to bottom.)&lt;br /&gt;
&lt;br /&gt;
But if there are no Dilbert strips anywhere, that means your organization is a rigid, humorless police state. All the people with talent and ambition (and humor) will leave. All that will be left will be those who have Abandoned All Hope. And since hope is the primary energy source for many projects, that's not a good thing.&lt;br /&gt;
&lt;br /&gt;
A healthy Dilbert Barometer measures somewhere from one to ten Dilbert strips per team member. (&lt;a href="http://www.comics.com/comics/dilbert/shop/html/books.html"&gt;Mr. Adams would be glad to sell them to you.&lt;/a&gt;) It's also healthy if the team members have scratched out the names in the strips and written in the names of their coworkers. That shows your team knows how to laugh. And that leads us to...&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;The Laugh Meter&lt;/h3&gt;
Productive, successful teams are happy. They form a bond of shared experiences. They take time out to share ideas. They laugh.&lt;br /&gt;
&lt;br /&gt;
Worried, stressed teams are unhappy. Their humor ranges from grim to none. They only talk about work, and mostly about problems. If you don't hear a few good laughs in a typical work day, your people have lost the energy they'll need to get through the project.&lt;br /&gt;
&lt;br /&gt;
On the other hand, if your people giggle uncontrollably with little or no provocation, check their WSR. When it gets up to 3 or so, uncontrollable fits of laughter are a common symptom.&lt;/div&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=127087"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=127087" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/UlteriorMotiveLounge/aggbug/127087.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Martin L. Shoemaker</dc:creator>
            <guid>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/project-metrics-they-never-taught-you-in-project-manager-training.aspx</guid>
            <pubDate>Sat, 15 Nov 2008 22:42:38 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/UlteriorMotiveLounge/comments/127087.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/project-metrics-they-never-taught-you-in-project-manager-training.aspx#feedback</comments>
            <wfw:commentRss>http://geekswithblogs.net/UlteriorMotiveLounge/comments/commentRss/127087.aspx</wfw:commentRss>
        </item>
        <item>
            <title>Dee Jay, Part 5: Homophones and Alternates</title>
            <link>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/dee-jay-part-5-homophones-and-alternates.aspx</link>
            <description>&lt;div class="post"&gt;So in Part 4, I said that recognizing the music key would be tricky.&lt;br /&gt;
&lt;br /&gt;
But why? Didn't I spend most of Part 3 explaining how cleverly I used M-SAPI so that users only had to say partial names to be recognized?&lt;br /&gt;
&lt;br /&gt;
Well, yes; but I've long said that programming has a Conservation of Complexity law: the less complex for the users, the more complex for the programmers. (Be glad: that's the short version. My long discussion on Conservation of Complexity would take up the rest of this post.)&lt;br /&gt;
&lt;br /&gt;
The reason why this flexibility leads to complexity is because one short phrase can match multiple long phrases. For instance, one album in my collection is &lt;a href="http://www.amazon.com/Forever-Gold-B-B-King/dp/B00000JFQF/ref=sr_1_2/104-7263427-9262344?ie=UTF8&amp;amp;s=music&amp;amp;qid=1176789968&amp;amp;sr=8-2"&gt;Forever Gold by B.B. King&lt;/a&gt;. It includes these songs:&lt;br /&gt;
&lt;br /&gt;
2. How Blue Can You Get? &lt;br /&gt;
3. Every Day I Have the Blues &lt;br /&gt;
10. Catfish Blues &lt;br /&gt;
14. Other Night Blues &lt;br /&gt;
&lt;br /&gt;
I also have some sample music provided with Windows Vista, including one track from Aaron Goldberg's &lt;a href="http://www.artistdirect.com/nad/store/artist/album/0,,3608062,00.html?src=search&amp;amp;artist=Aaron+Goldberg"&gt;Worlds&lt;/a&gt;: OAM's Blues. From &lt;a href="http://www.amazon.com/Sports-Huey-Lewis/dp/B000003JAP/ref=pd_bbs_sr_1/104-7263427-9262344?ie=UTF8&amp;amp;s=music&amp;amp;qid=1176790418&amp;amp;sr=1-1"&gt;Sports&lt;/a&gt; by Huey Lewis and the News, I have Honkytonk Blues. From &lt;a href="http://www.amazon.com/Jonathan-Richman/dp/B0000003JP/ref=sr_1_8/104-7263427-9262344?ie=UTF8&amp;amp;s=music&amp;amp;qid=1176790607&amp;amp;sr=1-8"&gt;Jonathan Richman's self-titled album&lt;/a&gt;, I have Blue Moon. From &lt;a href="http://www.amazon.com/Celebrating-Best-Jazz-Louis-Armstrong/dp/B00005QG6M/ref=sr_1_1/104-7263427-9262344?ie=UTF8&amp;amp;s=music&amp;amp;qid=1176790710&amp;amp;sr=1-1"&gt;Celebrating the Best of Jazz&lt;/a&gt; by Louis Armstrong, there's St. Louis Blues and Black and Blue. From &lt;a href="http://www.amazon.com/Am-I-Cool-What-Garfield/dp/B000008FW7/ref=sr_1_1/104-7263427-9262344?ie=UTF8&amp;amp;s=music&amp;amp;qid=1176790870&amp;amp;sr=1-1"&gt;Am I Cool or What?&lt;/a&gt; (yes, that's a Garfield CD — go ahead, laugh, but it has The Temptations, Patti LaBelle, Carl Anderson, Natalie Cole, The Pointer Sisters, Lou Rawls, Diane Schuur, Valerie Pinkston, Desiree Goyette, and B.B. King), there's Monday Morning Blues. From &lt;a href="http://www.amazon.com/True-Blue-Madonna/dp/B000002L9S/ref=pd_bbs_sr_1/104-7263427-9262344?ie=UTF8&amp;amp;s=music&amp;amp;qid=1176791069&amp;amp;sr=1-1"&gt;True Blue&lt;/a&gt; by Madonna, there's True Blue. From &lt;a href="http://www.amazon.com/Cargo-Men-at-Work/dp/B000088E76/ref=sr_1_1/104-7263427-9262344?ie=UTF8&amp;amp;s=music&amp;amp;qid=1176791161&amp;amp;sr=1-1"&gt;Cargo&lt;/a&gt; by Men at Work, there's Blue for You. From &lt;a href="http://www.amazon.com/All-Time-Top-100-TV-Themes/dp/B000AOENJK/ref=pd_bbs_sr_1/104-7263427-9262344?ie=UTF8&amp;amp;s=music&amp;amp;qid=1176791263&amp;amp;sr=1-1"&gt;All-Time Top 100 TV Themes&lt;/a&gt;, there's Hill Street Blues. From &lt;a href="http://www.amazon.com/Tropico-Pat-Benatar/dp/B000I73KYS/ref=pd_bbs_sr_1/104-7263427-9262344?ie=UTF8&amp;amp;s=music&amp;amp;qid=1176791374&amp;amp;sr=1-1"&gt;Tropico&lt;/a&gt;, there's Outlaw Blues. From &lt;a href="http://www.amazon.com/Forever-Gold-Ray-Charles/dp/B00000JCI2/ref=sr_1_2/104-7263427-9262344?ie=UTF8&amp;amp;s=music&amp;amp;qid=1176791478&amp;amp;sr=1-2"&gt;another Forever Gold title with Ray Charles&lt;/a&gt;, there's Sentimental Blues. From my fellow &lt;a href="http://www.aaduelist.org/"&gt;Duelist&lt;/a&gt; Geoff Nostrant (a.k.a. &lt;a href="http://silvercord.millim.com/?xc=2006&amp;amp;tid=silvercord"&gt;Silvercord&lt;/a&gt;), there's blueshift. From &lt;a href="http://www.amazon.com/Whos-Next-Who/dp/B000002OX7/ref=pd_bbs_sr_1/104-7263427-9262344?ie=UTF8&amp;amp;s=music&amp;amp;qid=1176791861&amp;amp;sr=1-1"&gt;Who's Next&lt;/a&gt; by The Who, there's Behind Blue Eyes.&lt;br /&gt;
&lt;br /&gt;
So if all I say to Dee Jay is "Dee Jay, Play Blue", Dee Jay will be really confused. Thirteen different songs have "Blue" in the title. Now that's my fault as the user; but we can't blame the users if we want happy users. We want to cope with what real users do, not just force them to do what we want.&lt;br /&gt;
&lt;br /&gt;
So how do we make Dee Jay understand all these potential matches? As in Part 3, there's the obvious way and the lazy way. And once again, the lazy way (relying on Microsoft to solve the problem) is the smart way. When M-SAPI returns a &lt;a href="http://msdn2.microsoft.com/en-us/library/system.speech.recognition.recognizedphrase.aspx"&gt;RecognizedPhrase&lt;/a&gt; (or the subclass, &lt;a href="http://msdn2.microsoft.com/en-us/library/system.speech.recognition.recognitionresult.aspx"&gt;RecognitionResult&lt;/a&gt;), it can include a list of equally good partial matches, called &lt;a href="http://msdn2.microsoft.com/en-us/library/system.speech.recognition.recognizedphrase.homophones.aspx"&gt;Homophones&lt;/a&gt;. Now we could quibble about that term: in grammar, homophones are words which sound the same but have different meanings. Here, the homophone phrases likely don't sound alike at all; but the recognized words form part of each phrase. But ignoring the terminology, the concept is easy: every phrase in the Homophones list is just as good of a match as the top-level phrase.&lt;br /&gt;
&lt;br /&gt;
So remember from Part 2 that Dee Jay is designed to select one or more songs or albums or artists (i.e., media descriptors) that match a given phrase. Well, now we want the media descriptors that match the phrase &lt;em&gt;and its Homophones&lt;/em&gt;. So the code for selecting all the matches looks something like this:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;// Music commands may include a specifier.&lt;br /&gt;
string specifier = "";&lt;br /&gt;
if (e.Result.Semantics.ContainsKey(_Specifier))&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;SemanticValue valSpecifier = e.Result.Semantics[_Specifier];&lt;br /&gt;
if (valSpecifier.Confidence &amp;gt;= 0.8)&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;specifier = e.Result.Semantics[_Specifier].Value.ToString();&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;br /&gt;
// Add the best match to the media phrase list.&lt;br /&gt;
List&amp;lt;RecognizedPhrase&amp;gt; testedPhrases = new List&amp;lt;RecognizedPhrase&amp;gt;();&lt;br /&gt;
List&amp;lt;MediaPhrase&amp;gt; phrases = new List&amp;lt;MediaPhrase&amp;gt;();&lt;br /&gt;
AddRecognizedMediaPhrase(command, e.Result, testedPhrases, phrases);&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
/// &amp;lt;summary&amp;gt;&lt;br /&gt;
/// Add a recognized phrase to a list of music phrases.&lt;br /&gt;
/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
/// &amp;lt;param name="command"&amp;gt;The command being built.&amp;lt;/param&amp;gt;&lt;br /&gt;
/// &amp;lt;param name="reco"&amp;gt;The recognized phrase.&amp;lt;/param&amp;gt;&lt;br /&gt;
/// &amp;lt;param name="testedPhrases"&amp;gt;The phrases which have already been tested.&amp;lt;/param&amp;gt;&lt;br /&gt;
/// &amp;lt;param name="phrases"&amp;gt;The current list of music phrases.&amp;lt;/param&amp;gt;&lt;br /&gt;
private void AddRecognizedMediaPhrase(string command,&lt;br /&gt;
RecognizedPhrase reco, List&amp;lt;RecognizedPhrase&amp;gt; testedPhrases, List&amp;lt;MediaPhrase&amp;gt; phrases)&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;// Avoid infinite recursion.&lt;br /&gt;
if (testedPhrases.Contains(reco))&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;return;&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
testedPhrases.Add(reco);&lt;br /&gt;
&lt;br /&gt;
// Only confident items with music.&lt;br /&gt;
if ((reco.Confidence &amp;gt;= 0.8) &amp;amp;&amp;amp; (reco.Semantics.ContainsKey(_MusicKey)))&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;// Only matching commands.&lt;br /&gt;
if ((reco.Semantics.ContainsKey(_Command)) &amp;amp;&amp;amp; (reco.Semantics[_Command].Value.ToString() == command))&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;// Add the key. Don't duplicate.&lt;br /&gt;
string key = reco.Semantics[_MusicKey].Value.ToString();&lt;br /&gt;
if (!phrases.Contains(_Map[key]))&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;phrases.Add(_Map[key]);&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;br /&gt;
// If we have homophones, add those, too.&lt;br /&gt;
if ((reco.Homophones.Count != null) &amp;amp;&amp;amp; (reco.Homophones.Count &amp;gt; 0))&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;foreach (RecognizedPhrase phrase in reco.Homophones)&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;AddRecognizedMediaPhrase(command, reco, testedPhrases, phrases);&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;/font&gt;&lt;br /&gt;
&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
So now we have a richer list of possible matches, based on the top phrase and its Homophones. But we could potentially make it richer still. While any RecognizedPhrase can have Homophones, a RecognitionResult can also have &lt;a href="http://msdn2.microsoft.com/en-us/library/system.speech.recognition.recognitionresult.alternates.aspx"&gt;Alternates&lt;/a&gt;, a list of lower confidence matches, each possibly including Homophones. So I could conceivably add code like this:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;// If we have alternates, add those, too.&lt;br /&gt;
if ((e.Result.Alternates != null) &amp;amp;&amp;amp; (e.Result.Alternates.Count &amp;gt; 0))&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;foreach (RecognizedPhrase alt in e.Result.Alternates)&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;AddRecognizedMediaPhrase(command, alt, testedPhrases, phrases);&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
But so far, I'm not very happy with the results when I do that. I need to experiment with different Confidence thresholds, and maybe tolerance on individual SemanticValues (as discussed in Part 4), to see if there's a good way to filter out "good" alternates from "bad".&lt;br /&gt;
&lt;br /&gt;
So now we have a great big list of possible media phrases that the user might have meant. How is Dee Jay to know which one is correct? Well, the same way any M-SAPI application should clarify user intentions: it's going to ask. But I have other commitments and some flaky hardware, so it will be a while before I can get to that.&lt;/div&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=127086"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=127086" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/UlteriorMotiveLounge/aggbug/127086.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Martin L. Shoemaker</dc:creator>
            <guid>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/dee-jay-part-5-homophones-and-alternates.aspx</guid>
            <pubDate>Sat, 15 Nov 2008 22:40:53 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/UlteriorMotiveLounge/comments/127086.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/dee-jay-part-5-homophones-and-alternates.aspx#feedback</comments>
            <wfw:commentRss>http://geekswithblogs.net/UlteriorMotiveLounge/comments/commentRss/127086.aspx</wfw:commentRss>
        </item>
        <item>
            <title>Dee Jay, Part 4: I recognize that!</title>
            <link>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/dee-jay-part-4-i-recognize-that.aspx</link>
            <description>&lt;div class="post"&gt;In Part 3, we built a &lt;a href="http://msdn2.microsoft.com/en-us/system.speech.recognition.grammar.aspx"&gt;Grammar&lt;/a&gt; for Dee Jay to recognize.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;Update to Part 3&lt;/h3&gt;
Driving around last night, it occurred to me that I can let the user specify what sort of media is expected. For example, I could say "Dee Jay, play song Has Been" to pay &lt;a href="http://www.amazon.com/gp/music/wma-pop-up/B0002RUPH4001009/ref=mu_sam_wma_001_009/102-7246755-3228150"&gt;the song&lt;/a&gt;, or "Dee Jay, play album Has Been" to play &lt;a href="http://www.amazon.com/Has-Been-William-Shatner/dp/B0002RUPH4/ref=pd_bbs_sr_1/102-7246755-3228150?ie=UTF8&amp;amp;s=music&amp;amp;qid=1175599519&amp;amp;sr=8-1"&gt;the album&lt;/a&gt;. This specifier should be optional, so the user only has to use it when the user knows there's a potential conflict. Besides making my Dee Jay experience a little more convenient, this also gives me a chance to demonstrate two more facets of M-SAPI Grammars: SemanticResultValue and repetitions.&lt;br /&gt;
&lt;br /&gt;
A &lt;a href="http://msdn2.microsoft.com/en-us/system.speech.recognition.semanticresultvalue_members.aspx"&gt;SemanticResultValue&lt;/a&gt; lets you map phrases to a given result value, which must be a bool, int, float, or string value. Recall from &lt;a href="http://tabletumlnews.powerblogs.com/posts/1175598857.shtml"&gt;Part 2&lt;/a&gt; that Dee Jay has three different types of MediaDescriptor: song, album, and collection. All sorts of musical information — artist, composer, publisher, genre, etc. — are all treated simply as collection descriptors; but I wanted the user to be able to say "singer" or "artist" or "composer", as made sense for a given song. (And I wanted a good example for SemanticResultValue...) So I made a Choices, and then wrapped it in a SemanticResultValue:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;private const string _Specifier = "Specifier";&lt;br /&gt;
&lt;br /&gt;
private const string _Album = "Album";&lt;br /&gt;
private const string _Song = "Song";&lt;br /&gt;
private const string _Collection = "Collection";&lt;br /&gt;
&lt;br /&gt;
private const string _Artist = "Artist";&lt;br /&gt;
private const string _Singer = "Singer";&lt;br /&gt;
private const string _Writer = "Writer";&lt;br /&gt;
private const string _Songwriter = "Song Writer";&lt;br /&gt;
private const string _Musician = "Musician";&lt;br /&gt;
private const string _Composer = "Composer";&lt;br /&gt;
private const string _Publisher = "Publisher";&lt;br /&gt;
private const string _Genre = "Genre";&lt;br /&gt;
&lt;br /&gt;
/// &amp;lt;summary&amp;gt;&lt;br /&gt;
/// The set of collection names.&lt;br /&gt;
/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
private string[] mCollectionTypes;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
mCollectionTypes = new string[] {_Collection, _Artist, _Singer, _Writer, _Songwriter, _Musician, _Composer, _Publisher, _Genre };&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
// Build the optional specifier.&lt;br /&gt;
Choices chcCollectionTypes = new Choices();&lt;br /&gt;
foreach (string collectionType in mCollectionTypes)&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;GrammarBuilder gbCollectionType = new GrammarBuilder(collectionType);&lt;br /&gt;
chcCollectionTypes.Add(gbCollectionType);&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
GrammarBuilder gbCollectionTypes = new GrammarBuilder(chcCollectionTypes);&lt;br /&gt;
SemanticResultValue semCollectionType = new SemanticResultValue(gbCollectionTypes, _Collection);&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
This code makes a Choices with all the different collection type phrases; and then it wraps them all up in a SemanticResultValue that maps all of them to the phrase "Collection". So the user can say...&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;br /&gt;
    &lt;li&gt;Dee Jay, play singer Jonathon Richman.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Dee Jay, play artist Jonathon Richman.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Dee Jay, play musician Jonathon Richman.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Dee Jay, play song writer Jonathon Richman.&lt;br /&gt;
    &lt;/li&gt;
&lt;/ul&gt;
But Dee Jay will hear "Dee Jay, play collection Jonathon Richman."&lt;br /&gt;
&lt;br /&gt;
Next, I add the other specifiers (song and album), and wrap these all in a &lt;a href="http://msdn2.microsoft.com/en-us/library/system.speech.recognition.semanticresultkey.aspx"&gt;SemanticResultKey&lt;/a&gt;:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;Choices chcSpecifiers = new Choices();&lt;br /&gt;
chcSpecifiers.Add(new GrammarBuilder(semCollectionType));&lt;br /&gt;
chcSpecifiers.Add(_Album);&lt;br /&gt;
chcSpecifiers.Add(_Song);&lt;br /&gt;
GrammarBuilder gbSpecifier = new GrammarBuilder(chcSpecifiers);&lt;br /&gt;
SemanticResultKey keySpecifier = new SemanticResultKey(_Specifier, gbSpecifier);&lt;br /&gt;
GrammarBuilder gbOptionalSpecifier = new GrammarBuilder(keySpecifier);&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
Now we need to modify the keyed commands to optionally include the specifier. &lt;a href="http://msdn2.microsoft.com/en-us/library/system.speech.recognition.grammarbuilder.aspx"&gt;GrammarBuilder&lt;/a&gt; includes a constructor which takes an existing GrammarBuilder and a minimum and maximum number of repetitions. The &lt;a href="http://msdn2.microsoft.com/en-us/library/system.speech.recognition.grammarbuilder.append.aspx"&gt;Append&lt;/a&gt; method has a similar overload:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;// Build the keyed command grammar by appending music key&lt;br /&gt;
// to each command.&lt;br /&gt;
Choices chcKeyedCommands = new Choices();&lt;br /&gt;
foreach (string cmd in mKeyedCommands)&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;GrammarBuilder gbKeyed = new GrammarBuilder(new SemanticResultKey(_Command, cmd));&lt;br /&gt;
gbKeyed.Append(gbOptionalSpecifier,0, 1);&lt;br /&gt;
gbKeyed.Append(gbMusic);&lt;br /&gt;
chcKeyedCommands.Add(gbKeyed);&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
With this code, any keyed command includes 0 or 1 specifier elements.&lt;br /&gt;
&lt;br /&gt;
And now...&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;On with Part 4!&lt;/h3&gt;
&lt;br /&gt;
&lt;br /&gt;
Now we need to create a &lt;a href="http://msdn2.microsoft.com/en-us/system.speech.recognition.speechrecognitionengine.aspx"&gt;SpeechRecognitionEngine&lt;/a&gt; and tell it to recognize the Grammar. And for any .NET programmer, this is honestly the easiest part:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;
/// The recognition engine.&lt;br /&gt;
/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
private SpeechRecognitionEngine mRecoEngine = new SpeechRecognitionEngine();&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
// Start listening.&lt;br /&gt;
mRecoEngine.LoadGrammar(mGrammar);&lt;br /&gt;
mRecoEngine.SetInputToDefaultAudioDevice();&lt;br /&gt;
mRecoEngine.SpeechRecognized += new EventHandler&lt;speechrecognizedeventargs&gt;&lt;/speechrecognizedeventargs&gt;(mEngine_SpeechRecognized);&lt;br /&gt;
mRecoEngine.RecognizeAsync(RecognizeMode.Multiple);&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
We create a SpeechRecognitionEngine. We load our Grammar. We connect to an audio source (in this case, the default audio input). We add an event handler. And we start listening. It's as simple as that.&lt;br /&gt;
&lt;br /&gt;
Only that's not so simple.&lt;br /&gt;
&lt;br /&gt;
First, we have to decide whether to use SpeechRecognitionEngine or &lt;a href="http://msdn2.microsoft.com/en-us/library/system.speech.recognition.speechrecognizer.aspx"&gt;SpeechRecognizer&lt;/a&gt;. SpeechRecognizer is higher level and simpler, but more limited. In particular, it is limited to the default audio input. SpeechRecognitionEngine is lower level and has more options, including the option to read audio from files or streams. &lt;a href="http://msdn2.microsoft.com/en-us/library/system.speech.recognition.speechrecognitionengine.aspx"&gt;The MS docs are confusing on this which you should use:&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
While &lt;strong&gt;SpeechRecognitionEngine&lt;/strong&gt; based applications can use the system default audio input and recognition engines, it is recommended that the &lt;strong&gt;SpeechRecognitionEngine&lt;/strong&gt; object be used instead for that purpose.&lt;br /&gt;
&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
Unless I'm missing something, I &lt;em&gt;think&lt;/em&gt; that should read:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
While &lt;strong&gt;SpeechRecognitionEngine&lt;/strong&gt; based applications can use the system default audio input and recognition engines, it is recommended that the &lt;strong&gt;SpeechRecognizer&lt;/strong&gt; object be used instead for that purpose.&lt;br /&gt;
&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
But regardless, I prefer to use SpeechRecognitionEngine. SpeechRecognizer pops up the &lt;a href="http://msdn2.microsoft.com/en-us/library/system.speech.recognition.speechui.aspx"&gt;SpeechUI&lt;/a&gt;, a window that shows progress and tips as the user speaks. I find that annoying, honestly. Plus I like the added flexibility of SpeechRecognitionEngine. And, well, SpeechRecognitionEngine was the first recognizer class I found, so it's what I use by default. Maybe I'll explore the choice in more detail at another time.&lt;br /&gt;
&lt;br /&gt;
Then we have to choose how we'll perform our recognition. There are two basic modes: synchronous and asynchronous. And then for asynchronous, we can choose to wait for just one event, or keep listening for multiple events. For Dee Jay, we choose asynchronous with multiple events, since that means Dee Jay listens continuously as it works.&lt;br /&gt;
&lt;br /&gt;
Next we have to implement our recognition event handler. And that's where the complexity can come in. I say &lt;em&gt;can&lt;/em&gt; come in, because you &lt;em&gt;can&lt;/em&gt; make it really simple; but simple for you is complex for your users, and vice versa. If you want satisfied users, you'll need to do some work.&lt;br /&gt;
&lt;br /&gt;
Let's look at the declaration of the event handler. This should be old hat to .NET developers:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;
/// A phrase was recognized.&lt;br /&gt;
/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
/// &amp;lt;param name="sender"&amp;gt;The engine.&amp;lt;/param&amp;gt;&lt;br /&gt;
/// &amp;lt;param name="e"&amp;gt;The details.&amp;lt;/param&amp;gt;&lt;br /&gt;
void mEngine_SpeechRecognized(object sender, SpeechRecognizedEventArgs e)&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
This is a standard &lt;a href="http://msdn2.microsoft.com/en-us/library/db0etb8x.aspx"&gt;EventHandler&lt;/a&gt;-style method, taking a sender and an argument object. In this case, the argument object is of type &lt;a href="http://msdn2.microsoft.com/en-us/library/system.speech.recognition.speechrecognizedeventargs.aspx"&gt;SpeechRecognizedEventArgs&lt;/a&gt;, a rich type with al the complexity you could ever want. The rest of our processing will focus on the contents of the SpeechRecognizedEventArgs.&lt;br /&gt;
&lt;br /&gt;
The main component of SpeechRecognizedEventArgs is Result, an object of type &lt;a href="http://msdn2.microsoft.com/en-us/library/system.speech.recognition.recognitionresult.aspx"&gt;RecognitionResult&lt;/a&gt;. This is a subclass of &lt;a href="http://msdn2.microsoft.com/en-us/library/system.speech.recognition.recognizedphrase.aspx"&gt;RecognizedPhrase&lt;/a&gt;, a more general class which we'll see more of later. RecognitionResult adds information about the audio stream, and also a list of aternate RecognizedPhrases.&lt;br /&gt;
&lt;br /&gt;
Result contains the matched phrase; but as we saw in Part 3, we want the recognition engine to automatically break the phrase into &lt;a href="http://search.msdn.microsoft.com/search/Redirect.aspx?title=SemanticValue+Class+(System.Speech.Recognition)+&amp;amp;url=http://msdn2.microsoft.com/en-us/system.speech.recognition.semanticvalue.aspx"&gt;SemanticValue&lt;/a&gt; objects for us. Here, for example, is the code for finding the command:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;// Read the command.&lt;br /&gt;
string command = "";&lt;br /&gt;
if (e.Result.Semantics.ContainsKey(_Command))&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;SemanticValue valCommand = e.Result.Semantics[_Command];&lt;br /&gt;
command = valCommand.Value.ToString();&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
e.Result.Semantics is a dictionary that maps text keys to SemanticValue objects. A SemanticValue then contains a Value field that is a bool, an int, a float, or a string.&lt;br /&gt;
&lt;br /&gt;
Now we can read our Dee Jay name:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;// All other commands require a name.&lt;br /&gt;
if (!e.Result.Semantics.ContainsKey(_DJ))&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;return;&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
SemanticValue valName = e.Result.Semantics[_DJ];&lt;br /&gt;
if (valName.Confidence &amp;lt; 0.8)&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;return;&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
Each SemanticValue includes a Confidence value from 0 to 1, indicating how strongly that element was matched. I found that it was easy for an entire command to be matched by casual conversation, without me ever actually saying "Dee Jay". So I separately test the Confidence of the name, just to be sure it was there. (RecognizedPhrases also have a Confidence value, which will be useful in other parts of Dee Jay.)&lt;br /&gt;
&lt;br /&gt;
Next we read the optional specifier:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;// Music commands may include a specifier.&lt;br /&gt;
string specifier = "";&lt;br /&gt;
if (e.Result.Semantics.ContainsKey(_Specifier))&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;SemanticValue valSpecifier = e.Result.Semantics[_Specifier];&lt;br /&gt;
if (valSpecifier.Confidence &amp;gt;= 0.8)&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;specifier = e.Result.Semantics[_Specifier].Value.ToString();&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
The most complicated part of Dee Jay's recognition, though, is the music phrase itself. That's complex, and my time here is short. So I'll save that for the next post.&lt;/div&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=127085"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=127085" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/UlteriorMotiveLounge/aggbug/127085.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Martin L. Shoemaker</dc:creator>
            <guid>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/dee-jay-part-4-i-recognize-that.aspx</guid>
            <pubDate>Sat, 15 Nov 2008 22:38:09 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/UlteriorMotiveLounge/comments/127085.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/dee-jay-part-4-i-recognize-that.aspx#feedback</comments>
            <wfw:commentRss>http://geekswithblogs.net/UlteriorMotiveLounge/comments/commentRss/127085.aspx</wfw:commentRss>
        </item>
        <item>
            <title>Dee Jay, Part 3: Building a Media Player Grammar</title>
            <link>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/dee-jay-part-3-building-a-media-player-grammar.aspx</link>
            <description>&lt;div class="post"&gt;In Part 2, we dug a little bit into MPM (Media Player Magic) to build a JukeBoxPhraseMap, mapping phrases from the Media Player to songs, albums, and collections. Now we need to turn those phrases into M-SAPI commands.&lt;br /&gt;
&lt;br /&gt;
In concept, we want a &lt;a href="http://msdn2.microsoft.com/en-us/system.speech.recognition.choices.aspx"&gt;Choices&lt;/a&gt; object, which represents a choice between two or more alternate phrases. We could turn the whole map into one giant Choices, and we will; but that Choices would be pretty unusable. No user is going to remember and correctly speak some of the song titles in my Media Player library:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;br /&gt;
    &lt;li&gt;The "Jamestown" Homeward Bound&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;"Krankenmal" Theme&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Adagio (from Toccata Adagio and Fugue in C major&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;After All [Love Theme from Chances Are]&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Parece Mentira&lt;br /&gt;
    &lt;/li&gt;
&lt;/ul&gt;
Users will probably only remember parts of these names, so we need partial matching. There are two approaches to the partial matching problem: the obvious way, and the lazy way...&lt;br /&gt;
&lt;br /&gt;
The obvious way is to decide that this is my problem, and I have to split every one of these phrases into its component pieces, and make those into phrases, and then combine those into larger phrases, and so on, and so on, and so on, and the phrase map gets incredibly cumbersome and pretty much impossible for me to ever manage.&lt;br /&gt;
&lt;br /&gt;
The lazy way is to let Microsoft spend I-don't-know-how-many millions of dollars on speech recognition technology &lt;em&gt;and&lt;/em&gt; programmability, and solve the problem for me. After all, how many problem domains include complex phrases which can be difficult for users to speak? No, scratch that: how many problem domains &lt;strong&gt;don't&lt;/strong&gt; include complex phrases which can be difficult for users to speak? The answer is: not many interesting domains. So M-SAPI includes a built-in partial match capability in one of the &lt;a href="http://msdn2.microsoft.com/en-us/library/system.speech.recognition.grammarbuilder.aspx"&gt;GrammarBuilder&lt;/a&gt; &lt;a href="http://msdn2.microsoft.com/en-us/library/ms554232.aspx"&gt;constructors&lt;/a&gt;:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;public GrammarBuilder (&lt;br /&gt;
string phrase,&lt;br /&gt;
SubsetMatchingMode subsetMatchingCriteria&lt;br /&gt;
)&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
The &lt;a href="http://msdn2.microsoft.com/en-us/library/system.speech.recognition.subsetmatchingmode.aspx"&gt;SubsetMatchingMode&lt;/a&gt; describes how the speech recognizer will recognize partial matches within the specified phrase. The options are:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;br /&gt;
    &lt;li&gt;&lt;strong&gt;OrderedSubset:&lt;/strong&gt; Matches one or more words in the phrase &lt;strong&gt;if&lt;/strong&gt; those words are spoken in the same order as in the phrase. "Same order" does not mean sequential, necessarily: the spoken phrase "dog cat" has the same order as "dog bird cat", even though there's a word missing in the middle.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;OrderedSubsetContentRequired:&lt;/strong&gt; Matches one or more words in the phrase &lt;strong&gt;if&lt;/strong&gt; those words are spoken in the same order as in the phrase; but ignores simple articles and prepositions.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Subsequence:&lt;/strong&gt; Matches one or more words in the phrase &lt;strong&gt;if&lt;/strong&gt; those words form a subsequence in the target phrase. The spoken phrase "dog cat" is &lt;strong&gt;not&lt;/strong&gt; a subsequence of "dog bird cat" because there's a word missing in the middle.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;SubsequenceContentRequired:&lt;/strong&gt; Matches one or more words in the phrase &lt;strong&gt;if&lt;/strong&gt; those words form a subsequence in the target phrase; but ignores simple articles and prepositions.&lt;br /&gt;
    &lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;br /&gt;
So I used SubsequenceContentRequired to turn each phrase into a partial matching grammar; and then I composed &lt;em&gt;those&lt;/em&gt; into a Choices:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;// Build the music key grammar by looping over map phrases.&lt;br /&gt;
Choices chcPhrases = new Choices();&lt;br /&gt;
foreach (string phrase in _Map.Phrases)&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;GrammarBuilder gbPhrase = new GrammarBuilder(phrase, SubsetMatchingMode.SubsequenceContentRequired);&lt;br /&gt;
chcPhrases.Add(gbPhrase);&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
So now I have a Choices of music phrases, and the speech recognizer can recognize them. (Well, it will when I get to that code...) So when I say, "Dee Jay, play &lt;a href="http://www.amazon.com/gp/music/wma-pop-up/B0002RUPH4001009/ref=mu_sam_wma_001_009/102-7246755-3228150"&gt;Has Been&lt;/a&gt;," all I have to do is pull the recognized text apart, find the music phrase, and look it up in the map. And once again, there are two ways to pull the recognized text apart: the obvious way (do it myself) or the lazy way (trust Microsoft to do it for me). Which one do you think I'm going to pick? (If you said "obvious", you don't know me very well...) M-SAPI includes the &lt;a href="http://msdn2.microsoft.com/en-us/library/system.speech.recognition.semanticresultkey.aspx"&gt;SemanticResultKey&lt;/a&gt; class, a class which allows you to attach a semantic tag to a GrammarBuilder so that the speech recognizer can parse the string for you. All you have to do is create a new SemanticResultKey and add it to a GrammarBuilder:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;private const string _MusicKey = "MusicKey";&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
// Assign the semantic result to _MusicKey.&lt;br /&gt;
GrammarBuilder gbMusic = new GrammarBuilder(new SemanticResultKey(_MusicKey, chcPhrases));&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
This GrammarBuilder can now be used to build commands that will include phrases from the Media Player library. "Play" is one music command, but not the only one. So I combine these all into a Choices:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;
/// The set of keyed commands.&lt;br /&gt;
/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
private string[] mKeyedCommands;&lt;br /&gt;
&lt;br /&gt;
private const string _Play = "Play";&lt;br /&gt;
private const string _PlaySome = "Play Some";&lt;br /&gt;
private const string _PlayAny = "Play Any";&lt;br /&gt;
private const string _PlayAll = "Play All";&lt;br /&gt;
private const string _Add = "Add";&lt;br /&gt;
private const string _AddSome = "Add Some";&lt;br /&gt;
private const string _AddAny = "Add Any";&lt;br /&gt;
private const string _AddAll = "Add All";&lt;br /&gt;
&lt;br /&gt;
private const string _Command = "Command";&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
mKeyedCommands = new string[] {_Play, _PlaySome, _PlayAny, _PlayAll, _Add, _AddSome, _AddAny, _AddAll};&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
// Build the keyed command grammar by appending music key&lt;br /&gt;
// to each command.&lt;br /&gt;
Choices chcKeyedCommands = new Choices();&lt;br /&gt;
foreach (string cmd in mKeyedCommands)&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;GrammarBuilder gbKeyed = new GrammarBuilder(new SemanticResultKey(_Command, cmd));&lt;br /&gt;
gbKeyed.Append(gbMusic);&lt;br /&gt;
chcKeyedCommands.Add(gbKeyed);&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
Note how I again used a SemanticResultKey to identify each of the phrases in the Choices as a command. Then note how after each command, I appended the gbKeyed GrammarBuilder. So "Play" is a Command, and "Has Been" is a MusicKey.&lt;br /&gt;
&lt;br /&gt;
I also defined a number of commands that don't require a MusicKey:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;
/// The set of unkeyed commands.&lt;br /&gt;
/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
private string[] mUnkeyedCommands;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
private const string _Pause = "Pause";&lt;br /&gt;
private const string _Resume = "Resume";&lt;br /&gt;
private const string _Skip = "Next";&lt;br /&gt;
private const string _Back = "Back";&lt;br /&gt;
private const string _5Stars = "5 Stars";&lt;br /&gt;
private const string _4Stars = "4 Stars";&lt;br /&gt;
private const string _3Stars = "3 Stars";&lt;br /&gt;
private const string _2Stars = "2 Stars";&lt;br /&gt;
private const string _1Star = "1 Star";&lt;br /&gt;
private const string _Louder = "Louder";&lt;br /&gt;
private const string _Softer = "Softer";&lt;br /&gt;
private const string _Shh = "Hush";&lt;br /&gt;
private const string _Shout = "Shout";&lt;br /&gt;
private const string _About = "About";&lt;br /&gt;
private const string _Exit = "Exit";&lt;br /&gt;
private const string _Hello = "Hello";&lt;br /&gt;
private const string _Rescan = "Rescan";&lt;br /&gt;
private const string _WhatsPlaying = "What's playing?";&lt;br /&gt;
private const string _ResetName = "Reset Name";&lt;br /&gt;
private const string _WhatCanISay = "What can I say?";&lt;br /&gt;
private const string _Help = "Help";&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
mUnkeyedCommands = new string[] {_Pause, _Resume, _Skip, _Back, _5Stars, _4Stars, _3Stars, _2Stars, _1Star, _1Star, _Louder, _Softer, _Shh, _Shout, _WhatCanISay, _Help, _About, _Exit, _Hello, _Rescan, _ResetName, _WhatsPlaying};&lt;br /&gt;
&lt;br /&gt;
// Build the unkeyed command grammar.&lt;br /&gt;
Choices chcUnkeyedCommands = new Choices();&lt;br /&gt;
foreach (string cmd in mUnkeyedCommands)&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;GrammarBuilder gbUnkeyed = new GrammarBuilder(new SemanticResultKey(_Command, cmd));&lt;br /&gt;
chcUnkeyedCommands.Add(gbUnkeyed);&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
I also wanted a command to let the user rename Dee Jay. Users love personalization, and this is an obvious one. So that required a special command, because I couldn't include a list of all possible names. Instead, I need a dictation, an element that matches any spoken phrase:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;// Build the rename grammar. Set Command to the rename command,&lt;br /&gt;
// and Name to the dictation contents.&lt;br /&gt;
GrammarBuilder gbRenameRoot = new GrammarBuilder(_Rename);&lt;br /&gt;
GrammarBuilder gbDictation = new GrammarBuilder();&lt;br /&gt;
gbDictation.AppendDictation();&lt;br /&gt;
GrammarBuilder gbName = new GrammarBuilder(new SemanticResultKey(_Name, gbDictation));&lt;br /&gt;
GrammarBuilder gbRename = new GrammarBuilder(new SemanticResultKey(_Command, gbRenameRoot));&lt;br /&gt;
gbRename.Append(gbName);&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
The &lt;strong&gt;AppendDictation&lt;/strong&gt; method adds a dictation to a GrammarBuilder. Note again how I used SemanticResultKeys to identify the elements of the command.&lt;br /&gt;
&lt;br /&gt;
So now I have three kinds of commands: keyed, unkeyed, and rename. I want to combine these into a single element, so that I can precede them with the current name:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;// Build the commands.&lt;br /&gt;
Choices chcCommands = new Choices(chcKeyedCommands, chcUnkeyedCommands, gbRename);&lt;br /&gt;
&lt;br /&gt;
// Build the DJ name.&lt;br /&gt;
GrammarBuilder gbDJNameOnly = new GrammarBuilder(new SemanticResultKey(_DJ, mDeeJayName));&lt;br /&gt;
GrammarBuilder gbDJ = new GrammarBuilder(gbDJNameOnly,1,1);&lt;br /&gt;
gbDJ.Append(chcCommands);&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
Finally, I need one special command: "Reset Name". Unlike the other commands, this one shouldn't require the Dee Jay name, because the user might have forgotten it. So this one stands alone:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;// Build the nameless commands.&lt;br /&gt;
GrammarBuilder gbResetName = new GrammarBuilder(new SemanticResultKey(_Command, _ResetName));&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
And now, finally, we can build a &lt;a href="http://search.msdn.microsoft.com/search/Redirect.aspx?title=Grammar+Class+(System.Speech.Recognition)+&amp;amp;url=http://msdn2.microsoft.com/en-us/library/system.speech.recognition.grammar.aspx"&gt;Grammar&lt;/a&gt; from all of these GrammarBuilders:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;
/// The current grammar.&lt;br /&gt;
/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
private Grammar mGrammar;&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
// Build the top-level grammar.&lt;br /&gt;
GrammarBuilder gbTop = new GrammarBuilder(new Choices(gbResetName, gbDJ));&lt;br /&gt;
mGrammar = new Grammar(gbTop);&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
So now we have a Grammar that represents commands we can speak to Dee Jay. In the next part, we'll start to listen for and recognize those commands.&lt;/div&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=127084"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=127084" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/UlteriorMotiveLounge/aggbug/127084.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Martin L. Shoemaker</dc:creator>
            <guid>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/dee-jay-part-3-building-a-media-player-grammar.aspx</guid>
            <pubDate>Sat, 15 Nov 2008 22:35:26 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/UlteriorMotiveLounge/comments/127084.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/dee-jay-part-3-building-a-media-player-grammar.aspx#feedback</comments>
            <wfw:commentRss>http://geekswithblogs.net/UlteriorMotiveLounge/comments/commentRss/127084.aspx</wfw:commentRss>
        </item>
        <item>
            <title>Dee Jay, Part 2: MPM, and more MPM</title>
            <link>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/dee-jay-part-2-mpm-and-more-mpm.aspx</link>
            <description>&lt;div class="post"&gt;In Part 1, we saw how the process of building a grammar is similar to the Decorator or Composite patterns, building a larger structure out of smaller pieces. In Part 2, we'll build and recognize a grammar to see how to define and identify parts of a command.&lt;br /&gt;
&lt;br /&gt;
In some ways, I wish I had chosen a different example for my first speech application. I think &lt;a href="http://tabletumlnews.powerblogs.com/posts/1174189935.shtml"&gt;Dee Jay&lt;/a&gt; is a really cool app, and I use it every day on my drive to work; but the Media Player rogramming is complex enough to be worthy of a few blog posts on its own, and that's really not what I'm trying to explain here. So I'll show some Media Player code here and there, but it won't be the main point of this post. If I get questions on the Media Player side, maybe I can delve into more detail at another time; but for now, I'll leave those details as Media Player Magic (MPM).&lt;br /&gt;
&lt;br /&gt;
I wrap most of the Media Player work in two classes, MediaDescriptor and MediaPhrase:&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://tabletumlnews.powerblogs.com/files/tabletumlnews-Media_Classes.bmp"&gt;&lt;img height="284" alt="Media Classes" width="600" src="http://tabletumlnews.powerblogs.com/files/tabletumlnews-Media_Classes-small.bmp" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
I started with a single, simple command in mind: "Dee Jay, play &lt;a href="http://www.amazon.com/gp/music/wma-pop-up/B0002RUPH4001009/ref=mu_sam_wma_001_009/102-7246755-3228150"&gt;Has Been&lt;/a&gt;." But "Has Been" denotes both a song and &lt;a href="http://www.amazon.com/Has-Been-William-Shatner/dp/B0002RUPH4/ref=pd_bbs_sr_1/102-7246755-3228150?ie=UTF8&amp;amp;s=music&amp;amp;qid=1175599519&amp;amp;sr=8-1"&gt;an album&lt;/a&gt;. If I asked &lt;em&gt;you&lt;/em&gt; to play Has Been, you wuldn't know which I meant. How could Dee Jay know?&lt;br /&gt;
&lt;br /&gt;
So I realized that any given phrase might match a song title, an album title, or an artist. Also, a given song or album might be identified by many different phrases: title, artist, abum, genre, etc. These concerns led me to create MediaPhrase, a class which links a given phrase to one or more MediaDescriptors:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;
/// Represents a phrase that maps to one or more media descriptors.&lt;br /&gt;
/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
public class MediaPhrase&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;
/// The phrase.&lt;br /&gt;
/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
private string mPhrase;&lt;br /&gt;
&lt;br /&gt;
/// &amp;lt;summary&amp;gt;&lt;br /&gt;
/// The phrase.&lt;br /&gt;
/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
public string Phrase&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;get { return mPhrase; }&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;br /&gt;
/// &amp;lt;summary&amp;gt;&lt;br /&gt;
/// The descriptors.&lt;br /&gt;
/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
private List&lt;mediadescriptor&gt;&lt;/mediadescriptor&gt; mDescriptors = new List&lt;mediadescriptor&gt;&lt;/mediadescriptor&gt;();&lt;br /&gt;
&lt;br /&gt;
/// &amp;lt;summary&amp;gt;&lt;br /&gt;
/// The descriptors.&lt;br /&gt;
/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
public List&lt;mediadescriptor&gt;&lt;/mediadescriptor&gt; Descriptors&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;get { return mDescriptors; }&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;br /&gt;
/// &amp;lt;summary&amp;gt;&lt;br /&gt;
/// Construct.&lt;br /&gt;
/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
///
&lt;param name="phrase" /&gt;The phrase.&lt;br /&gt;
public MediaPhrase(string phrase)&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;mPhrase = phrase;&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
Looking ahead, the plan will be simple: if a recognized phrase maps to exactly one MediaDescriptor, Dee Jay will just play the corresponding media; but if the phrase maps to multiple MediaDescriptors, then you and Dee Jay will have to identify which media you want.&lt;br /&gt;
&lt;br /&gt;
The other major class is MediaDescriptor, an abstract base class which represents one or more media items:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;
/// Describes a song or song collection.&lt;br /&gt;
/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
public abstract class MediaDescriptor&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;/// &lt;summary&gt;&lt;/summary&gt;&lt;br /&gt;
/// Play the media.&lt;br /&gt;
/// &lt;br /&gt;
///
&lt;param name="player" /&gt;Target player.&lt;br /&gt;
public abstract void Play(IWMPPlayer4 player);&lt;br /&gt;
&lt;br /&gt;
/// &lt;summary&gt;&lt;/summary&gt;&lt;br /&gt;
/// List the songs in the descriptor.&lt;br /&gt;
/// &lt;br /&gt;
/// &lt;returns&gt;&lt;/returns&gt;&lt;br /&gt;
public abstract List&lt;iwmpmedia3&gt;&lt;/iwmpmedia3&gt; GetMediaList();&lt;br /&gt;
&lt;br /&gt;
/// &lt;summary&gt;&lt;/summary&gt;&lt;br /&gt;
/// Describe the descriptor.&lt;br /&gt;
/// &lt;br /&gt;
/// &lt;returns&gt;&lt;/returns&gt;&lt;br /&gt;
public abstract string Describe();&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
The &lt;strong&gt;Play&lt;/strong&gt; method plays the media on an &lt;a href="http://msdn2.microsoft.com/en-us/bb249125.aspx"&gt;IWMPPlayer4&lt;/a&gt; object, which is the latest, most powerful interface to Windows Media Player. The &lt;strong&gt;GetMediaList&lt;/strong&gt; method returns a list of all &lt;a href="http://search.msdn.microsoft.com/search/Redirect.aspx?title=IWMPMedia3+Interface+&amp;amp;url=http://msdn2.microsoft.com/en-us/bb248947.aspx"&gt;IWMPMedia3&lt;/a&gt; objects within the descriptor (where IWMPMedia3 is the interface to a single media item). The &lt;strong&gt;Describe&lt;/strong&gt; method describes this descriptor.&lt;br /&gt;
&lt;br /&gt;
Of course, you don't want to play "descriptors"; you want to play songs, or albums, or artists. This leads to the three concrete subclasses of MediaDescriptor. SongDescriptor describes a single song, while AlbumDescriptor describes an entire album. CollectionDescriptor describes a collection of related songs, such as all songs by a particular artist or all songs in a particular genre. The details of these classes are all MPM, so we won't delve into them here.&lt;br /&gt;
&lt;br /&gt;
So given a phrase, we can find media; but now we need to pull the phrases from Media Player. This is the role of the JukeBoxPhraseMap class. There's a lot of MPM in this class, but the skeleton is shown here:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;
/// Represents a map of phrase strings to media phrases.&lt;br /&gt;
/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
public class JukeBoxPhraseMap : SortedDictionary&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;
/// Add a song to the phrase map.&lt;br /&gt;
/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
/// &amp;lt;param name="song"&amp;gt;The song.&amp;lt;/param&amp;gt;&lt;br /&gt;
public void AddSong(IWMPMedia3 song)&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;&lt;em&gt;MPM here...&lt;/em&gt;&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;br /&gt;
/// &amp;lt;summary&amp;gt;&lt;br /&gt;
/// The phrases in the map.&lt;br /&gt;
/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
public IEnumerable&lt;string&gt;&lt;/string&gt; Phrases&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;get { return this.Keys; }&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;br /&gt;
/// &amp;lt;summary&amp;gt;&lt;br /&gt;
/// Event fired when a media descriptor is scanned.&lt;br /&gt;
/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
public event EventHandler&lt;mediascanargs&gt;&lt;/mediascanargs&gt; MediaScanned;&lt;br /&gt;
&lt;br /&gt;
/// &amp;lt;summary&amp;gt;&lt;br /&gt;
/// Add a playlist to the map.&lt;br /&gt;
/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
/// &amp;lt;param name="playlist"&amp;gt;The playlist.&amp;lt;/param&amp;gt;&lt;br /&gt;
public void AddPlaylist(IWMPPlaylist playlist)&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;&lt;em&gt;MPM here...&lt;/em&gt;&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;br /&gt;
&lt;em&gt;Lots more MPM here...&lt;/em&gt;&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;br /&gt;
/// &amp;lt;summary&amp;gt;&lt;br /&gt;
/// Describes a scanned item.&lt;br /&gt;
/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
public class MediaScanArgs : EventArgs&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;
/// The descriptor.&lt;br /&gt;
/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
private MediaDescriptor mDescriptor;&lt;br /&gt;
&lt;br /&gt;
/// &amp;lt;summary&amp;gt;&lt;br /&gt;
/// The descriptor.&lt;br /&gt;
/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
public MediaDescriptor Descriptor&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;get { return mDescriptor; }&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;br /&gt;
/// &amp;lt;summary&amp;gt;&lt;br /&gt;
/// Construct.&lt;br /&gt;
/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
/// &amp;lt;param name="descriptor"&amp;gt;Source&amp;lt;/param&amp;gt;&lt;br /&gt;
public MediaScanArgs(MediaDescriptor descriptor)&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;mDescriptor = Descriptor;&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
This class is a SortedDictionary that maps strings to MediaPhrases. You can add songs to it, and you can also add &lt;a href="http://search.msdn.microsoft.com/search/Redirect.aspx?title=IWMPPlaylist+Interface+&amp;amp;url=http://msdn2.microsoft.com/en-us/library/aa391039.aspx"&gt;IWMPPlaylist&lt;/a&gt; objects (where IWMPPlaylist is the Media Player interface to standard and custom playlists). You can get the list of Phrases as a property; and the class fires a MediaScanned event for each new descriptor added. (This is useful for displaying progress as you scan your Media Player library.)&lt;br /&gt;
&lt;br /&gt;
The rest of this class is lots and lots of MPM, and not important for our topic. (That's speech recognition, in case you've forgotten...) These elements are enough for us to populate a phrase map using the following code excerpt:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;/// &amp;lt;summary&amp;gt;&lt;br /&gt;
/// Map of phrases to media&lt;br /&gt;
/// &amp;lt;/summary&amp;gt;&lt;br /&gt;
private JukeBoxPhraseMap _Map = new JukeBoxPhraseMap();&lt;br /&gt;
&lt;br /&gt;
...&lt;br /&gt;
&lt;br /&gt;
// Show the progress form.&lt;br /&gt;
using (MediaRescanForm frm = new MediaRescanForm())&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;frm.Map = _Map;&lt;br /&gt;
frm.Show();&lt;br /&gt;
&lt;br /&gt;
// Start empty.&lt;br /&gt;
_Map.Clear();&lt;br /&gt;
&lt;br /&gt;
// Loop over the media. Exit if stopped.&lt;br /&gt;
IWMPPlaylist playlist = wmp.mediaCollection.getAll();&lt;br /&gt;
for (int idx = 0; (idx &amp;lt; playlist.count) &amp;amp;&amp;amp; (!frm.Stopped); idx++)&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;// Add the song to the map.&lt;br /&gt;
try&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;IWMPMedia3 media = playlist.get_Item(idx) as IWMPMedia3;&lt;br /&gt;
_Map.AddSong(media);&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
catch { }&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;br /&gt;
// Loop over the playlists. Exit if stopped.&lt;br /&gt;
IWMPPlaylistArray playlists = wmp.playlistCollection.getAll();&lt;br /&gt;
for (int idx = 0; (idx &amp;lt; playlists.count) &amp;amp;&amp;amp; (!frm.Stopped); idx++)&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;// Add the playlist to the map.&lt;br /&gt;
try&lt;br /&gt;
{&lt;br /&gt;
&lt;/font&gt;&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;IWMPPlaylist list = playlists.Item(idx);&lt;br /&gt;
_Map.AddPlaylist(list);&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
catch { }&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;br /&gt;
// Done.&lt;br /&gt;
frm.Close();&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;}&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
MediaRescanForm is a simple class which subscribes to the &lt;strong&gt;MediaScanned&lt;/strong&gt; event of a JukeBoxPhraseMap and displays descriptors as they're scanned. The rest of this code should be obvious: it loops over songs and then playlists, adding them to the map.&lt;br /&gt;
&lt;br /&gt;
So alllllll of this MPM is prolog, simply to get us a list of phrases and a map from the phrases to media descriptors. Now we want to turn those into commands in a grammar. This will be the point of Part 3.&lt;/div&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=127083"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=127083" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/UlteriorMotiveLounge/aggbug/127083.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Martin L. Shoemaker</dc:creator>
            <guid>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/dee-jay-part-2-mpm-and-more-mpm.aspx</guid>
            <pubDate>Sat, 15 Nov 2008 22:32:52 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/UlteriorMotiveLounge/comments/127083.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/dee-jay-part-2-mpm-and-more-mpm.aspx#feedback</comments>
            <wfw:commentRss>http://geekswithblogs.net/UlteriorMotiveLounge/comments/commentRss/127083.aspx</wfw:commentRss>
        </item>
        <item>
            <title>Dee Jay, Part 1: Decorating, composing, or encompassing?</title>
            <link>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/dee-jay-part-1-decorating-composing-or-encompassing.aspx</link>
            <description>&lt;div class="post"&gt;To understand the code behind &lt;a href="http://tabletumlnews.powerblogs.com/posts/1174189935.shtml"&gt;Dee Jay&lt;/a&gt;, we first need to understand the basics of the M-SAPI speech recognition system. That means we need to understand three concepts:&lt;br /&gt;
&lt;ol&gt;&lt;br /&gt;
    &lt;li&gt;&lt;a href="http://msdn2.microsoft.com/en-us/system.speech.recognition.speechrecognitionengine.aspx"&gt;SpeechRecognitionEngine&lt;/a&gt;. This is the class that will listen for commands and phrases and fire events when it recognizes something. We're not ready to understand this class yet, even though it's a very simple class. Before we can look at the SpeechRecognitionEngine, though, we need to look at Grammar.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href="http://msdn2.microsoft.com/en-us/system.speech.recognition.grammar.aspx"&gt;Grammar&lt;/a&gt;. This class describes a complete set of phrases and options that a SpeechRecognitionEngine will recognize. There are a number of ways to create a Grammar, ranging from simple strings to &lt;a href="http://www.w3.org/TR/speech-grammar/"&gt;W3C Speech Recognition Grammar Specification&lt;/a&gt; (SRGS) documents. But for Dee Jay, we're going to concentrate on building a Grammar out of smaller elements, using the GrammarBuilder class.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href="http://www.w3.org/TR/speech-grammar/"&gt;GrammarBuilder&lt;/a&gt;. This is a class that represents a subset of a Grammar; and that subset can itself have subsets, and so on.&lt;br /&gt;
    &lt;/li&gt;
&lt;/ol&gt;
GrammarBuilder is the focus of this post; and I find that it helps to understand GrammarBuilder if you think of it in relation to two standard design patterns: Decorator and Composite. Neither one precisely describes the design of GrammarBuilder, but they'll help you to think about how it works.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;The Decorator Pattern&lt;/h3&gt;
&lt;br /&gt;
&lt;br /&gt;
Decorator is a pattern that allows you to dynamically add new behavior to an existing object, as shown in Figure 1:&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://tabletumlnews.powerblogs.com/files/tabletumlnews-Decorator_Pattern.bmp"&gt;&lt;img height="281" alt="Decorator Pattern" width="600" src="http://tabletumlnews.powerblogs.com/files/tabletumlnews-Decorator_Pattern-small.bmp" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;strong&gt;Figure 1: The Decorator Pattern&lt;/strong&gt;&lt;br /&gt;
&lt;br /&gt;
In this example, we have Things that DoStuff. Now at run time we want to make some Things also able to DoPlainStuff and others also able to DoFancyStuff. Now if we had the right sort of problem, we could solve this with Plain and Fancy subclasses of Thing; but what if we won't know when we first create a Thing whether it will be Plain or Fancy (or neither)?&lt;br /&gt;
&lt;br /&gt;
Another solution would be to create a converter that converts a Thing to Plain or Fancy; but as we get more varieties and the number of converters grows, this can get cumbersome. And what if we later find a Thing which we want to do &lt;em&gt;both&lt;/em&gt; Plain &lt;em&gt;and&lt;/em&gt; Fancy stuff?&lt;br /&gt;
&lt;br /&gt;
The Decorator Pattern says that the solution is not subclasses and subsubclasses and subsubsubclasses and a plethora of converters; rather, there is one base class (Base Thing in Figure 1) and two subclasses. One subclass is Thing itself; but the other is DecoratedThing, which isn't really a Thing at all. Instead, DecoratedThing &lt;em&gt;contains&lt;/em&gt; a Base Thing; and any time someone asks DecoratedThing to DoStuff, it does so by asking its "inner Thing" to do the real work. And that "inner Thing" might be a real Thing, &lt;em&gt;or&lt;/em&gt; it might be another DecoratedThing. The first DecoratedThing doesn't know, and doesn't care. It simply asks the inner Thing to do work.&lt;br /&gt;
&lt;br /&gt;
And now we can define Plain Things by creating PlainDecorator, a subclass of DecoratedThing, and sticking a real Thing inside it. And we can define Fancy Things with FancyDecorator. &lt;em&gt;And&lt;/em&gt; we could even stick a PlainDecorator inside a FancyDecorator. There's no limit.&lt;br /&gt;
&lt;br /&gt;
Now GrammarBuilders aren't Decorators, though I thought they were at first. I thought that because they have some Decorator-like behavior, in that a GrammarBuilder can be defined or built out of smaller GrammarBuilders. There's a definite sense of layers within layers, much as with Decorator. (Why &lt;em&gt;aren't&lt;/em&gt; GrammarBuilders Decorators? See below...)&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;The Composite Pattern&lt;/h3&gt;
Composite is a pattern very similar to Decorator; but instead of adding new behavior to an existing thing, you define a thing that contains other similar things. The distinction between the two patterns is subtle, and is more in intention than in implementation: you could take Composite code and use it in a Decorator fashion, so the code differences are minor. But in Decorator you think about adding behavior, while in Composite you think about adding contents.&lt;br /&gt;
&lt;br /&gt;
A typical example of Composite is shown in Figure 2:&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://tabletumlnews.powerblogs.com/files/tabletumlnews-Composite_Pattern.bmp"&gt;&lt;img height="163" alt="The Composite Pattern" width="600" src="http://tabletumlnews.powerblogs.com/files/tabletumlnews-Composite_Pattern-small.bmp" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;strong&gt;Figure 2: The Composite Pattern&lt;/strong&gt;&lt;br /&gt;
&lt;br /&gt;
In this example, we have two varieties of Widgets (Plain and Fancy), and then a CompositeWidget; and all three are subclasses of a base Widget class, and can do whatever Widgets do. But the Composite Widget contains 0 or more Widgets, which may themselves be Plain, Fancy, or Composite; and when asked to do its Widget stuff, it does so by asking each of its contained Widgets to do &lt;em&gt;their&lt;/em&gt; Widget stuff.&lt;br /&gt;
&lt;br /&gt;
GrammarBuilder isn't quite like Composite, either. Once a GrammarBuilder has been created, it really doesn't act like a collection with contents. Rather, it acts just as a single entity with a lot of rich detail.&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;The GrammarBuilder Class&lt;/h3&gt;
So what does GrammarBuilder look like? Well, something like Figure 3:&lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://tabletumlnews.powerblogs.com/files/tabletumlnews-Grammar_Builder.bmp"&gt;&lt;img height="363" alt="GrammarBuilder and Friends" width="600" src="http://tabletumlnews.powerblogs.com/files/tabletumlnews-Grammar_Builder-small.bmp" /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;strong&gt;Figure 3: GrammarBuilder and Friends&lt;/strong&gt;&lt;br /&gt;
&lt;br /&gt;
One look at Figure 3 will tell any UML-aware reader what's lacking for either the Decorator Pattern or the Composite Pattern: base classes! A GrammarBuilder is indeed made up of smaller pieces; but those smaller pieces don't have any common base classes. So GrammarBuilder may be inspired by one of these patterns, but it isn't implemented as either of them. (At least not publicly. If you dug inside, I suspect you would find something that looks a lot like Composite: a tree-like structure containing internal elements constructed from the external elements in Figure 3.)&lt;br /&gt;
&lt;br /&gt;
Figure 3 shows that Grammar Builder depends on itself and also on four other classes:&lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;&lt;br /&gt;
    &lt;li&gt;String. This is simply the .NET string class. It represents one word or phrase the user might say.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href="http://msdn2.microsoft.com/en-us/system.speech.recognition.choices.aspx"&gt;Choices&lt;/a&gt;. This class represents a choice between two or more alternate phrases. It is defined by the list of choices. Note that, somewhat like GrammarBuilder, Choices also depends on both string and GrammarBuilder. The alternates in a Choices list can be simple strings, or they can be more complex phrases built up through GrammarBuilders.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;SemanticResultKey. This takes an existing Grammar element (GrammarBuilder, Choices, string) and attaches a label to it so that you can find it as a member of a SemanticValue array after recognition. For instance, in Dee Jay, you could give the command "Play Graceland". I used SemanticResultKeys to define this command as [Command][MusicKey]"; and then when I ask for [Command], M-SAPI returns "Play"; and when I ask for [MusicKey], M-SAPI returns "Graceland". By using SemanticResultKeys, you tell the SpeechRecognitionEngine how to parse your phrases for you automatically.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href="http://msdn2.microsoft.com/en-us/system.speech.recognition.semanticresultvalue_members.aspx"&gt;SemanticResultValue&lt;/a&gt;. This element allows you to map a recognized phrase to a given bool, int, float, or string value. So for instance, you might map the word "score" to the number 20.&lt;br /&gt;
    &lt;/li&gt;
&lt;/ol&gt;
So a GrammarBuilder can be built from any of these classes, including another GrammarBuilder; and two GrammarBuilders can be combined to form a new GrammarBuilder, as can a GrammarBuilder and a string or a Choices. This may not be precisely the Composite Pattern, due to no common base classes; but it sure is a form of composition.&lt;br /&gt;
&lt;br /&gt;
To see a very simple pseudocode example of how GrammarBuilders can be used to build a Grammar, let's imagine a control with a background color and a foreground color; and let's further imagine that either color can only be red, green, or blue. Then our Grammar could be built like this:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
&lt;font face="Courier New"&gt;// Define the color choices.&lt;br /&gt;
chcColors = Choices("Red", "Green", "Blue");&lt;br /&gt;
&lt;br /&gt;
// Add the key, "Color".&lt;br /&gt;
keyColor = SemanticResultKey("Color", chcColors);&lt;br /&gt;
&lt;br /&gt;
// Make a GrammarBuilder.&lt;br /&gt;
gbColor = GrammarBuilder(keyColor);&lt;br /&gt;
&lt;br /&gt;
// Define the target choices.&lt;br /&gt;
chcTargets = Choices("Foreground", "Background");&lt;br /&gt;
&lt;br /&gt;
// Add the key, "Target".&lt;br /&gt;
keyTarget = SemanticResultKey("Target", chcTargets);&lt;br /&gt;
&lt;br /&gt;
// Make a GrammarBuilder.&lt;br /&gt;
gbTarget = GrammarBuilder(keyTarget);&lt;br /&gt;
&lt;br /&gt;
// Make the combined GrammarBuilder.&lt;br /&gt;
gbCommands = gbTarget + gbColor&lt;br /&gt;
&lt;/font&gt;&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
Once converted into a Grammar, this GrammarBuilder will match any of the following phrases:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;br /&gt;
    &lt;li&gt;Foreground Red&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Foreground Green&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Foreground Blue&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Background Red&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Background Green&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Background Blue&lt;br /&gt;
    &lt;/li&gt;
&lt;/ul&gt;
But it &lt;em&gt;won't&lt;/em&gt; match any of these phrases:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;br /&gt;
    &lt;li&gt;Foreground Yellow&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Foreground Color&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Target Blue&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Target Color&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Target Earth&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;What?&lt;br /&gt;
    &lt;/li&gt;
&lt;/ul&gt;
Keep in mind that "Target" and "Color" are red herrings (so to speak) in these bad examples. "Target" and "Color" aren't recognized phrases in the Grammar; rather, they're keys to look up parts of the recognized result, as in the following bit of pseudo-code:&lt;br /&gt;
&lt;br /&gt;
&lt;blockquote&gt;&lt;br /&gt;
// Read the command pieces.&lt;br /&gt;
target = result.SemanticValues["Target"];&lt;br /&gt;
color = result.SemanticValues["Color"];&lt;br /&gt;
&lt;/blockquote&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;Where Next?&lt;/h3&gt;
Now that we understand the basics of building a GrammarBuilder, we'll need to build a Grammar and recognize it. We'll look at how to do that when I get time to continue this series.&lt;/div&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=127082"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=127082" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/UlteriorMotiveLounge/aggbug/127082.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Martin L. Shoemaker</dc:creator>
            <guid>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/dee-jay-part-1-decorating-composing-or-encompassing.aspx</guid>
            <pubDate>Sat, 15 Nov 2008 22:30:55 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/UlteriorMotiveLounge/comments/127082.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/dee-jay-part-1-decorating-composing-or-encompassing.aspx#feedback</comments>
            <wfw:commentRss>http://geekswithblogs.net/UlteriorMotiveLounge/comments/commentRss/127082.aspx</wfw:commentRss>
        </item>
        <item>
            <title>Dee Jay: A Voice-Controlled Juke Box for Windows Vista!</title>
            <link>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/dee-jay-a-voice-controlled-juke-box-for-windows-vista.aspx</link>
            <description>&lt;div class="post"&gt;I wrote Dee Jay as an example for a proposed talk for &lt;a href="http://www.dayofdotnet.org/"&gt;the Ann Arbor Day of .NET&lt;/a&gt;, and as a way to learn more about the Managed Speech API in Microsoft Windows Vista. Dee Jay works with M-SAPI and Windows Media Player to give you a totally voice-controlled way to play your music. You simply say a command like "Dee Jay, play some Dire Straits", and it searches your song catalog for songs by Dire Straits, picks one, and plays it. Or you can name a specific title, or even a genre. If there are multiple matches for a given name or title, Dee Jay will list them until you choose one by saying "Play." And there are a number of other commands, which you can learn by saying "What can I say?" &lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://www.inkonsoftware.com/DeeJay.aspx"&gt;Now Dee Jay is available as a free download.&lt;/a&gt; Just download the zip file, unzip it, and run Setup.exe. I can't promise any support for it right now, but I can try to answer questions. And I look forward to your feedback. I'm already enjoying the freedom of voice-controlled music on my daily commute, and I hope you will enjoy it, too! &lt;br /&gt;
&lt;br /&gt;
Now to forestall the obvious first questions... No, it doesn't work on any OS but Vista (or if it does, it's news to me). It doesn't work with any media software but Windows Media Player. I wrote this code for a demo for a one hour presentation. It had to be simple; and with Vista, Microsoft has made speech recognition programming extremely simple. While I've been thinking about this program for about three weeks, I wrote the actual code in my spare time over the past work. And I billed 62 hours this week, plus probably 8 hours of travel, so there wasn't a lot of spare time. And of that coding time, over 75% of it was spent writing code to catalog your music library! The speech code was so easy, it felt like cheating. (I programmed .NET speech recognition with SAPI 5.1. Now that was a challenge. I would've needed weeks, maybe months to do this same work with SAPI 5.1.) &lt;br /&gt;
&lt;br /&gt;
This is why I upgraded to Vista: not for Dee Jay, but for the ability to write Dee Jay and other voice-controlled applications. There have been pretty decent commercially available speech recognition tools out there for a while, but they were a royal pain to program. With Vista, writing speech applications just got as easy as writing desktop applications (and the recognition accuracy took a giant leap, too). Designing a good speech grammar and a good conversation model takes some work (&lt;a href="http://www.tabletuml.com/"&gt;maybe even some UML&lt;/a&gt; to think through it), but implementing that design is nearly effortless. I'll be exploring the code in subsequent blog posts; but for those who don't want the gory techie details, just download Dee Jay, start it up, and say "What can I say?" Dee Jay will talk you through the rest. &lt;br /&gt;
&lt;br /&gt;
It's a great time to be a programmer! &lt;br /&gt;
&lt;br /&gt;
(P.S. If anyone has Vista and a really large song library, I would be curious to know how long the Dee Jay catalog takes to build. My catalog loads in less than a second, but I've only got 135 albums.)&lt;br /&gt;
&lt;br /&gt;
&lt;strong&gt;UPDATE:&lt;/strong&gt; In response to a question from &lt;a href="http://www.benday.com/"&gt;Ben Day&lt;/a&gt;, I've added this list of the Dee Jay commands. Note that you can change Dee Jay's name, so replace "Dee Jay" with your chosen name in these commands.&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;br /&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, Play MUSICKEY.&lt;/strong&gt; Plays a song, an album, or a named collection. Replace MUSICKEY with a phrase that identifies a song. (See below for details on MUSICKEY.) If there are multiple matches for the MUSICKEY, Dee Jay lists them one at a time, giving you a chance to say "Play" (which also ends the list),"Back up", "Next", or "Cancel".&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, Play Some MUSICEY.&lt;/strong&gt; Dee Jay picks one song from the MUSICKEY at random.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, Play Any MUSICKEY.&lt;/strong&gt; Same as Play Some.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, Play All MUSICKEY.&lt;/strong&gt; Plays all songs from a MUSICKEY, in a random order.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, Add MUSICKEY.&lt;/strong&gt; Adds a single song to the current playlist.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, Add Some MUSICEY.&lt;/strong&gt; Dee Jay adds one song from the MUSICKEY at random to the current playlist.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, Add Any MUSICKEY.&lt;/strong&gt; Same as Add Some.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, Add All MUSICKEY.&lt;/strong&gt; Adds all songs from a MUSICKEY to the current playlist, in a random order.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, Pause.&lt;/strong&gt; Pauses play.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, Resume.&lt;/strong&gt; Resumes play.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, Next.&lt;/strong&gt; Skips to the next song in the play list.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, Back.&lt;/strong&gt; Jumps to the previous song in the play list.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, 5 Stars.&lt;/strong&gt; Rates the current song as 5 stars. Other commands (of course) are 4 Stars, 3 Stars, 2 Stars, and 1 Star.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, Louder.&lt;/strong&gt; Raise volume by 10%.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, Softer.&lt;/strong&gt; Lower volume by 10%.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, Hush.&lt;/strong&gt; Drop volume to 10%.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, Shout.&lt;/strong&gt; Raise volume to 100%.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, About.&lt;/strong&gt; Describe Dee Jay and its current version.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, Exit.&lt;/strong&gt; Exit Dee Jay.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, Hello.&lt;/strong&gt; Dee Jay greets you.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, Rescan.&lt;/strong&gt; Looks for new music.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, What's playing?&lt;/strong&gt; Identifies the current song.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, Rename NAME.&lt;/strong&gt; Changes the name Dee Jay responds to. Replace NAME with your Dee Jay name.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, Reset Name.&lt;/strong&gt; Changes the name back to Dee Jay.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Reset Name.&lt;/strong&gt; Same as Dee Jay, Reset Name. I figured people might forget their Dee Jay name and need a way to default it.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, What can I say?&lt;/strong&gt; Describes the commands.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Dee Jay, Help.&lt;/strong&gt; Same as Dee Jay, What can I say?&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;What can I say?&lt;/strong&gt; Same as Dee Jay, What can I say?&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Help.&lt;/strong&gt; Same as Dee Jay, What can I say?&lt;br /&gt;
    &lt;/li&gt;
&lt;/ul&gt;
A MUSICKEY is a phrase which helps identify a song, an album, or a collection. (It also ought to identify play lists, but I forgot to implement that.) Dee Jay scans your music library and finds the following information for each song (not ever song has all of these fields):&lt;br /&gt;
&lt;ul&gt;&lt;br /&gt;
    &lt;li&gt;Title. This doesn't form a collection (see below for collections), but is used to uniquely identify a song. (What if two songs have the same name? See below...)&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Album. This doesn't form a collection, but is used to identify all songs in a single album.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Author.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Artist.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Composer.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Conductor.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Publisher.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Category. No, I don't know what this means; but it's one of the fields Media Player will report.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Genre.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Language.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Mood. Another one that Media Player reports, but I don't know where it's defined.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Period. Another one that Media Player reports, but I don't know where it's defined.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;User Rating. This is one a 0 to 100 scale; but I convert it to 1 to 5 stars, like the Media Player UI does. This is supposed to define 5 different collections; but honestly, I haven't rated enough of my songs to test it yet.&lt;br /&gt;
    &lt;/li&gt;
&lt;/ul&gt;
Except for Title and Album (as described above), each of these fields is used to define collections of rated songs, one collection per value. So for example, my library includes songs by Pat Benatar, Kronos Quartet, and Adrianna Culcanhotto (among others); and it also includes comedy albums by Bill Cosby and Bob Newhart. From these examples, Dee Jay would create the following collections:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;br /&gt;
    &lt;li&gt;Pat Benatar.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Rock.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Kronos Quartet.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Classical.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Adrianna Culcanhotto.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;World.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Bill Cosby.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Bob Newhart.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Comedy.&lt;br /&gt;
    &lt;/li&gt;
&lt;/ul&gt;
It would create a lot of other collections as well, for publisher, composer, star rating, etc. Then all collections, songs, and albums are entered into a phrase map which will recognize a particular phrase and find the corresponding music.&lt;br /&gt;
&lt;br /&gt;
Note also that, thanks to the magic of M-SAPI, you don't have to precisely match phrases in the phrase map. You simply have to get some of the non-articles right and in sequence. If you have the song "After All [Love Theme from Chances Are]", no user is going to remember that whole title (I can't, and it was Sandy's and my wedding song); but they don't have to. Dee Jay will recognize any of these phrases as possible matches for that title:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;br /&gt;
    &lt;li&gt;After All [Love Theme from Chances Are].&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;After All.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Chances Are.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Love Theme from Chances Are.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Love Theme.&lt;br /&gt;
    &lt;/li&gt;
    &lt;li&gt;Theme from Chances.&lt;br /&gt;
    &lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
But it won't recognize a jumbled phrase, like "After Are All Chances Love". (M-SAPI does include a mode which would recognize that; but I decided that it was better to require the user to get the words in the right sequence. Otherwise, a lot of songs with similar titles can too easily be confused.)&lt;br /&gt;
&lt;/div&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=127081"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=127081" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/UlteriorMotiveLounge/aggbug/127081.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Martin L. Shoemaker</dc:creator>
            <guid>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/dee-jay-a-voice-controlled-juke-box-for-windows-vista.aspx</guid>
            <pubDate>Sat, 15 Nov 2008 22:28:38 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/UlteriorMotiveLounge/comments/127081.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/dee-jay-a-voice-controlled-juke-box-for-windows-vista.aspx#feedback</comments>
            <slash:comments>3</slash:comments>
            <wfw:commentRss>http://geekswithblogs.net/UlteriorMotiveLounge/comments/commentRss/127081.aspx</wfw:commentRss>
        </item>
        <item>
            <title>Jason's hearing voices...</title>
            <link>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/jasons-hearing-voices.aspx</link>
            <description>&lt;div class="post"&gt;&lt;a href="http://jasonf-blog.blogspot.com/2007/03/it-talks.html"&gt;...and they're listening to him.&lt;/a&gt; Jason built a C# implementation of a Z-machine, the engine that powered classic old text adventures. Now &lt;a href="http://www.imaginativeuniversal.com/"&gt;James Ashley&lt;/a&gt; has added a Managed SAPI user interface, allowing you to talk to the game and have it respond. Jason knows I'm very excited by M-SAPI, so he sent me a link. Now I'm sharing it with what few readers I have; and I'll be keeping an eye on James's blog.&lt;br /&gt;
&lt;br /&gt;
And yes, Jason, I &lt;em&gt;am&lt;/em&gt; very excited about M-SAPI. Witness my next post...&lt;/div&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=127080"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=127080" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/UlteriorMotiveLounge/aggbug/127080.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Martin L. Shoemaker</dc:creator>
            <guid>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/jasons-hearing-voices.aspx</guid>
            <pubDate>Sat, 15 Nov 2008 22:26:44 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/UlteriorMotiveLounge/comments/127080.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/jasons-hearing-voices.aspx#feedback</comments>
            <wfw:commentRss>http://geekswithblogs.net/UlteriorMotiveLounge/comments/commentRss/127080.aspx</wfw:commentRss>
        </item>
        <item>
            <title>The 21st Century Cocktail Napkin presentation is now available on-line!</title>
            <link>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/the-21st-century-cocktail-napkin-presentation-is-now-available-on-line.aspx</link>
            <description>&lt;div class="post"&gt;&lt;strong&gt;The 21st Century Cocktail Napkin&lt;/strong&gt; is a talk I presented to &lt;a target="_blank" href="http://www.aadnd.org/"&gt;the Ann Arbor .NET Developers group&lt;/a&gt; on June 14. It's an example of a smart cocktail napkin application built using the Tablet PC API. In a a smart cocktail napkin application, you draw shapes as part of some design you'll share with other readers; but as you draw, the Tablet PC also recognizes and understands what you draw, and creates information behind the drawing. (For an example of a smart cocktail napkin application, you can &lt;a href="http://www.tabletuml.com/"&gt;start here&lt;/a&gt;.)&lt;br /&gt;
&lt;br /&gt;
Now, thanks to &lt;a target="_blank" href="http://www.techsmith.com/camtasia.asp"&gt;Camtasia Studio&lt;/a&gt;, I have a recording of this presentation. And thanks to &lt;a target="_blank" href="http://www.youtube.com/"&gt;YouTube&lt;/a&gt;, I can now present it to you on-line:&lt;br /&gt;
&lt;br /&gt;
&lt;embed src="http://www.youtube.com/v/TgZubvggaAg" width="600" height="350" type="application/x-shockwave-flash"&gt;&lt;/embed&gt;&lt;br /&gt;
&lt;br /&gt;
And you can also &lt;a href="http://www.tabletuml.com/21stCenturyCocktailNapkin.zip"&gt;download a ZIP file of the slides and the sample code&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Look for more recorded presentations soon. And if you're looking for an easy-to-use UML tool for Tablet PCs, check out &lt;a href="http://www.tabletuml.com/"&gt;Tablet UML&lt;/a&gt;.&lt;/div&gt;&lt;p&gt;&lt;a href="http://www.pheedo.com/click.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=127076"&gt;&lt;img src="http://www.pheedo.com/img.phdo?x=6cda6ad746d942b9a1110d0715a4fa12&amp;u=127076" border="0"/&gt;&lt;/a&gt;&lt;/p&gt;&lt;iframe src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;PageID=31016&amp;amp;SiteID=1" width=1 height=1 Marginwidth=0 Marginheight=0 Hspace=0 Vspace=0 Frameborder=0 Scrolling=No&gt;
&lt;script language='javascript1.1' src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Browser=NETSCAPE4&amp;amp;NoCache=True&amp;PageID=31016&amp;amp;SiteID=1"&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Click&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" target="_blank"&gt;
&lt;img src="http://ads.geekswithblogs.net/a.aspx?ZoneID=5&amp;amp;Task=Get&amp;amp;Mode=HTML&amp;amp;SiteID=1&amp;amp;PageID=31016" width="1" height="1" border="0"  alt=""&gt;&lt;/a&gt;
&lt;/noscript&gt;
&lt;/iframe&gt;
&lt;img src="http://geekswithblogs.net/UlteriorMotiveLounge/aggbug/127076.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Martin L. Shoemaker</dc:creator>
            <guid>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/the-21st-century-cocktail-napkin-presentation-is-now-available-on-line.aspx</guid>
            <pubDate>Sat, 15 Nov 2008 22:18:24 GMT</pubDate>
            <wfw:comment>http://geekswithblogs.net/UlteriorMotiveLounge/comments/127076.aspx</wfw:comment>
            <comments>http://geekswithblogs.net/UlteriorMotiveLounge/archive/2008/11/15/the-21st-century-cocktail-napkin-presentation-is-now-available-on-line.aspx#feedback</comments>
            <wfw:commentRss>http://geekswithblogs.net/UlteriorMotiveLounge/comments/commentRss/127076.aspx</wfw:commentRss>
        </item>
    </channel>
</rss>