Matthew van Eerde's web log

  • Matthew van Eerde's web log

    Car Talk puzzler analysis - Limerick equation


    Car Talk's puzzler of the week has some mathematical interest. See Car Talk's transcript of the question.

    In brief, we are given the equation...

    (12 + 144 + 20 + 3√4) / 7 + 5(11) = 92 + 0

    (which holds true, by the way)
    ... and asked to transcribe it as a limerick.

    The last line of the limeric is given: "Is nine squared, and not a bit more."

    The answer, ROT13'd for your convenience until the Car Talk folks reveal it:

    N qbmra, n tebff, naq n fpber
    Cyhf guerr gvzrf gur fdhner ebbg bs sbhe
    Qvivqrq ol frira
    Cyhf svir gvzrf ryrira
    Is nine squared, and not a bit more.

    This is not the only equation to be immortalized in a limerick. There's also this old chestnut. It relies on pronouncing the letter z as "zee" (rather than "zed",) as well as reading "log base e" (usually spelled "ln") for "log".

    The integral z2 dz
    From one to the cube root of three
    All times the cosine
    Of three pi over nine
    Equals log of the cube root of e.

    This equation also holds true.

    There's also this classic from Lewis Carroll which waxes a bit philosophical:

    Yet what mean all such gaieties to me
    Whose life is full of indices and surds
    x2 + 7x + 53
    = 11/3.

    Perhaps fittingly, this last equation has only imaginary solutions.

  • Matthew van Eerde's web log

    Anand vs. Gelfand world chess championship 2012 oldest pair of contenders since 1886


    In 2012, World Chess Champion Viswanathan Anand will attempt to defend his title aqainst challenger Boris Gelfand. This is a very unusual match in that both players are fairly old by World Chess Champion contender standards. I decided to see just how unusual it was, and do so with some degree of rigor.

    One tricky bit is that chess championships (usually) have (at least) two players, so we have to define an age metric for pairs of people. Creating a well-ordering on tuples is sometimes controversial. I chose to have the comparison routine be:
        age(contenders) = min(age(contender) : contender ∈ contenders)
    which is to say, the age of the youngest contender.

    Another tricky bit was deciding which matches were definitively "world chess championship matches." I pulled the list of world chess championship matches from For time periods where the organizational ownership of the title is in question, this includes matches sponsored by all contending organizations.

    As a naïve first pass, I looked up the birth years for all the contenders and subtracted that from the year of the championship to get an estimated age. This could be off by a year if the youngest contender's birthday comes after (or during?) the match. Nevertheless, this was accurate enough to give me a short list of matches to investigate further:.

    Year Player 1 Estimated Age Player 2 Estimated Age Minimum
    2012 Viswanathan Anand 43 Boris Gelfand 44 43
    2010 Viswanathan Anand 41 Veselin Topalov 35 35
    2008 Viswanathan Anand 39 Vladimir Kramnik 33 33
    2006 Vladimir Kramnik 31 Veselin Topalov 31 31
    2005 Veselin Topalov 30 Many N/A1 30
    2004 Vladimir Kramnik 29 Peter Leko 25 25
    2004 Rustam Kasimdzhanov 25 Michael Adams 33 25
    2001 Ruslan Ponomariov 18 Vassily Ivanchuk 32 18
    2000 Vladimir Kramnik 25 Garry Kasparov 37 25
    2000 Viswanathan Anand 31 Alexey Shirov 28 28
    1999 Alexander Khalifman 33 Vladimir Akopian 28 28
    1998 Anatoly Karpov 47 Viswanathan Anand 29 29
    1996 Anatoly Karpov 45 Gata Kamsky 22 22
    1995 Garry Kasparov 32 Viswanathan Anand 26 26
    1993 Garry Kasparov 30 Nigel Short 28 28
    1993 Anatoly Karpov 42 Jan Timman 42 42
    1990 Garry Kasparov 27 Anatoly Karpov 39 27
    1987 Garry Kasparov 24 Anatoly Karpov 36 24
    1986 Garry Kasparov 23 Anatoly Karpov 35 23
    1985 Garry Kasparov 22 Anatoly Karpov 34 22
    1984 Anatoly Karpov 33 Garry Kasparov 21 21
    1981 Anatoly Karpov 30 Viktor Korchnoi 50 30
    1978 Anatoly Karpov 27 Viktor Korchnoi 47 27
    1972 Bobby Fischer 29 Boris Spassky 35 29
    1969 Boris Spassky 32 Tigran Petrosian 40 32
    1966 Tigran Petrosian 37 Boris Spassky 29 29
    1963 Tigran Petrosian 34 Mikhail Botvinnik 52 34
    1961 Mikhail Botvinnik 50 Mikhail Tal 25 25
    1960 Mikhail Tal 24 Mikhail Botvinnik 49 24
    1958 Mikhail Botvinnik 47 Vasily Smyslov 37 37
    1957 Vasily Smyslov 36 Mikhail Botvinnik 46 36
    1954 Mikhail Botvinnik 43 Vasily Smyslov 33 33
    1951 Mikhail Botvinnik 40 David Bronstein 27 27
    1948 Mikhail Botvinnik 37 Vasily Smyslov 27 27
    1937 Alexander Alekhine 45 Max Euwe 36 36
    1935 Max Euwe 34 Alexander Alekhine 43 34
    1934 Alexander Alekhine 42 Efim Bogolyubov 45 42
    1929 Alexander Alekhine 37 Efim Bogolyubov 40 37
    1927 Alexander Alekhine 35 José Raúl Capablanca 39 35
    1921 José Raúl Capablanca 33 Emanuel Lasker 53 33
    1910 Emanuel Lasker 42 Dawid Janowski 42 42
    1910 Emanuel Lasker 42 Carl Schlecter 36 36
    1908 Emanuel Lasker 40 Siegbert Tarrasch 46 40
    1907 Emanuel Lasker 39 Frank Marshall 30 30
    1896 Emanuel Lasker 28 Wilhelm Steinitz 60 28
    1894 Emanuel Lasker 26 Wilhelm Steinitz 58 26
    1892 Wilhelm Steinitz 56 Mikhail Chigorin 42 42
    1890 Wilhelm Steinitz 54 Isidor Gunsberg 36 36
    1889 Wilhelm Steinitz 53 Mikhail Chigorin 39 39
    1886 Wilhelm Steinitz 50 Johannes Zukertort 44 44

    Closer investigation of each of the highlighted matches revealed that, astonishingly, in every case the youngest contender's birthday came after the match:

    • 2012 Viswanathan Anand (43?) vs. Boris Gelfand: Anand's birthday (December 11) comes after the match (starts in May) so he will still be 42.
    • 1993 Anatoly Karpov (42?) vs. Jan Timman (42?): Timman's birthday (December 14) came after the match (finished November 1) so he was still 41.
    • 1934 Alexander Alekhine (42?) vs. Efim Bogolyubov: Alekhine's birthday (October 31) came after the match (April to June) so he was still 41.
    • 1910 Emanuel Lasker (42?) vs. Dawid Janowski (42?): Janowski was born May 25; Lasker December 24. Lasker's birthday came after the match (finished December 8) so he was still 41.
    • 1892 Wilhelm Steinitz vs. Mikhail Chigorin (42?): Chigorin's birthday November 12 (October 31 old style) came after the match (finished February 28) so he was still 41.
    • 1886 Wilhelm Steinitz vs. Johannes Zukertort (44?): Zukertort's birthday (September 7) came after the match (finished March 29) so he was still 43.

    We conclude that Anand vs. Gelfand (2012) features the oldest contenders since the very first World Chess Championship Steinitz vs. Zukertort (1886) - and is within a year of even that! If the 2014 championship is a rematch, it will set the record.

    1 Topalov was the clear winner of the 2005 FIDE World Championship Tournament so there was no need for a runoff.

  • Matthew van Eerde's web log

    Implementing a "listen" command using ISpRecoContext from the Microsoft Speech API


    Earlier today I posted a quick "say.exe" sample app which you give text and it speaks the text aloud using the text-to-speech part of the Windows Speech API.  It was very straightforward - only 67 lines of C++ code.

    It took me a little longer to figure out how to do this "listen.exe" sample app; you run it, speak into the microphone, and it uses the speech-to-text part of the Windows Speech API to print what you're saying to the console.  This is a little more involved: 202 lines of C++ code.


    pSpRecoContext->SetInterest(recognition events only, thanks)
    while(...) {
        wait for a speech event (or the user to press Enter)
        for each speech event {
            make sure SPEVENT.eEventId is SPEI_RECOGNITION
            event.lParam is an ISpRecoResult
            print the text


    Speak into the microphone naturally; I will print what I understand.
    Press ENTER to quit.
    (At this point you start talking into the microphone. Text shows up here shortly after you say it.)

    Source and binaries attached.

  • Matthew van Eerde's web log

    How many numbers are straights?


    As many people, I have to deal with a lot of numbers all the time (bug numbers, changelist numbers, tracking numbers, ...) and as a mathematician I sometimes notice when numbers have peculiar properties.

    For example, I recently ran into 152463, which is a straight (made up of consecutive digits, not necessarily in order.)  I then ran into 35426, which is also a straight.  Is this odd?

    To put it another way - what proportion of numbers are straights?

    We consider only non-negative integer numbers {0, 1, 2, ... }.  No duplicate digits are allowed, so the very last straight is 9,876,543,210.

    Break it down by the length of the number in digits.  For example, how many four-digit numbers are there?  We can choose any number from 1-9 for the first digit, and any number from 0-9 for each of the other three digits; so there are 9 * 103 numbers of length 4.

    How many of these are straights?  There are seven ways to choose a set of four consecutive digits to make up a four-digit straight: {0,1, 2, 3}, {1, 2, 3, 4}, {2, 3, 4, 5}, {3, 4, 5, 6}, {4, 5, 6, 7}, {5, 6, 7, 8}, {6, 7, 8, 9}.

    Each of these seven ways can then be shuffled to make the straights themselves.  For six of these seven sets of digits, all possible shufflings are allowed; there are 4! shufflings for each set.  This gives 6 * 4! straights.

    But for {0, 1, 2, 3}, we have to be careful not to end up with 0 as a leading digit.  We choose the leading digit first out of the set {1, 2, 3} (there are three ways to do this) and then we have complete freedom to shuffle the three remaining digits (including 0.)  This gives 3 * 3! straights.

    So there are 6 * 4! + 3 * 3! straights which are four digits long.

    Considering all possible lengths from 1 to 10 (we'll consider 0 as a number of length 1) gives the following table:

    Length Numbers Straights % Straights
    1 10 10 100.000%
    2 9 * 101 17 18.889%
    3 9 * 102 46 5.111%
    4 9 * 103 162 1.800%
    5 9 * 104 696 0.773%
    6 9 * 105 3480 0.387%
    7 9 * 106 19,440 0.216%
    8 9 * 107 115,920 0.129%
    9 9 * 108 685,440 0.076%
    10 9 * 109 3,265,920 0.036%
    Total 1010 40,91,131 0.041%

    Since roughly 1% of five-digit numbers are straights, it is not surprising that I see them that often.

  • Matthew van Eerde's web log

    Welcome Yuk Lai Suen to the blogosphere!


    Please help to welcome my colleague fellow Windows Sound team test dev Yuk Lai Suen to the blogosphere!  Yuk Lai's first post discusses the utility of manual testing.

    Yuk Lai Suen

  • Matthew van Eerde's web log

    Disney princess pedigree: Belle


    Is Belle a Disney princess?

    The case in favor

    As is clearly established in the opening monologue, the male lead is a prince. Only his outward form is temporarily changed by the nasty enchantress who entraps him into refusing shelter (as if this was a crime.) Nevertheless, he retains property rights to his castle and the surrounding dominions - even were this not the case, Once a Prince, Always a Prince.

    Throughout the movie Belle handles herself with princessly aplomb:

    • She evades the unwanted attentions of Gaston without overtly hurting his feelings.
    • She is able to find and rescue her father.
    • She keeps her word not to leave the castle even when afforded an opportunity to escape by the Beast's injury.
    • She wins the hearts of the castle servants.
    • She civilizes the prince, taming his notorious temper and finding a modified set of table manners that are within the physical limitations imposed by his enchantment.

    The enchantress's spell serves as a litmus test for true love. The restoring of the prince's human form is proof that Belle and the prince love one another; they then kiss, and are married. Thus Belle has the clear title of Princess by Marriage.

    The case against

    It is granted that the male lead is a prince during the opening monologue. Granted, too, is his status as a prince in the closing scenes. One might question the practical effect of his princely status in the interim, especially since no-one outside of his castle is apparently aware of his existence. Certainly his behavior at several points during the movie is extremely unbecoming of a prince, or even a decent commoner:

    • He refuses shelter to an old woman, exposing both himself and the population of the castle to the wrath of an enchantress (who was admittedly overreacting a little.)
    • He withdraws from society.
    • He frequently loses his temper with his servants and others.
    • He imprisons Maurice, whose only crime was seeking refuge from wolves.
    • He imprisons Belle, whose only crime was looking for Maurice.
    • His table manners are decidedly unroyal.

    Belle's achievements as a young lady, though they do her credit (with the possible exception of passing up the opportunity to escape,) are irrelevant to her claim to the title of princess. Many a commoner has virtue; their lack of a princess title in no way diminishes that virtue.

    It pains me to say this, but Belle displays consistently poor social abilities throughout the movie - she is established as a withdrawn, introverted character who prefers the company of books to that of people. It is small wonder that she is easy prey for the sociopathic Beast. It is clear to me that over a prolonged period in a captor/hostage relationship, she eventually succumbs to Stockholm syndrome.

    The transformation is not necessarily indicative of true love between the Beast and Belle. It is true that the transformation was coincident with Belle's profession of love to the (as she perceived it) dying Beast. But it was also coincident with the falling of the last petal from the rose. Why believe that the former, rather than the latter, ended the enchantment? We have only the enchantress's word for this, and enchantresses are not known to be women of their words. In any case, Being a Prince's Girlfriend Does Not Suffice.

    One might challenge the validity of a marriage contract entered into when one of the parties was not of sound mind. But is there a marriage contract at all to challenge? There is no direct evidence that Beauty and the Beast are married at all.

    The verdict

    Belle has no claim to being a Princess by Birth; only to being a Princess by Marriage. It is clear that the Beast is a prince. What we have to decide is, was there a marriage?

    The final scene is quite artistic in its ambiguity. The penultimate scene culminates in a fairly passionate kiss (by Disney standards.) This is followed up by a formal dance, with Belle and the prince wearing their best outfits. And yet... No Dress, No Kiss, No Wedding. It is almost as if the scene were crafted so that all the young ladies in the audience could watch the scene and come away with the firm impression that Belle and the prince were married, and all of their fathers could come away with the firm impression that there was still hope that Belle would come to her senses. Note especially Chip's question "are they going to live happily ever after, Mama?", and Ms. Potts' pat answer "of course".

    In that critical final scene, Belle is wearing gloves, but the presence of a ring on the prince's finger would help Belle's case for princesshood significantly; I was unable to see one.

    Verdict: Controversial

  • Matthew van Eerde's web log

    Translating Ada Lovelace - mathematical science is an instrument


    Lady Augusta Ada, Countess of Lovelace is credited with being the first computer programmer.

    The short version: her associate Charles Babbage gave a lecture on his Analytical Engine in Italy; an Italian engineer wrote up a report on his lecture, in French; Lady Ada then translated the report into English, and added her own notes. Her own notes include a procedure for calculating Bernoulli numbers using the Analytical Engine; it is this procedure which is regarded as being the first computer program, making her the first computer programmer.

    Her translation and notes, in full

    The program (very large image)

    I was skimming through this and stumbled on this sentence which immediately struck me.

    Those who view mathematical science, not merely as a vast body of abstract and immutable truths, whose intrinsic beauty, symmetry and logical completeness, when regarded in their connexion together as a whole, entitle them to a prominent place in the interest of all profound and logical minds, but as possessing a yet deeper interest for the human race, when it is remembered that this science constitutes the language through which alone we can adequately express the great facts of the natural world, and those unceasing changes of mutual relationship which, visibly or invisibly, consciously or unconsciously to our immediate physical perceptions, are interminably going on in the agencies of the creation we live amidst: those who thus think on mathematical truth as the instrument through which the weak mind of man can most effectually read his Creator's works, will regard with especial interest all that can tend to facilitate the translation of its principles into explicit practical forms.
        -- Augusta Ada Lovelace

    Wow, I thought.

    That's a heckuva sentence.

    (I'm a sucker for the "connexion" spelling.)

    ... I have no idea what it means, though.

    I reread it a couple of times until I thought I knew what it meant. (Go ahead. I'll wait.)

    I looked at the last few words: "the translation of its principles into explicit practical forms." As a test I asked myself: "What does 'it' refer to?"

    I didn't immediately know, which revealed that I still didn't really understand the sentence.

    Gosh darn it, I said to myself, I'm going to lick this sentence. I didn't resort to a sentence diagram, but I did a much more forceful attempt at parsing it. Here's how I rewrote it:

    There are two ways to view mathematical science.

    Most people are "normal". Normal people view mathematical science as merely a vast body of truths. These truths are abstract and immutable. They have intrinsic beauty, symmetry, and logical completeness. They connect together to form a whole. Normal people think that all profound and logical minds give a prominent place to these truths.

    But mathematical science possess a deeper interest for the human race. The natural world has great facts. Also, the creation we live amidst has agencies. These agencies have mutual relationships which are unceasingly changing. Sometimes these changes are visible, or otherwise conscious to our immediate physical perceptions. Sometimes they are not. Only the language of mathematical science can adequately express these facts, and these changes.

    Some people are "geeks". Geeks think that mathematical truth is an instrument. They think the weak mind of man can most effectually read his Creator's works through this instrument.

    Sometimes a special kind of technique is discovered. These techniques tend to translate the principles of mathematical science into explicit practical forms.

    Geeks are specially interested in all such techniques.

        -- Augusta Ada Lovelace, paraphrased
  • Matthew van Eerde's web log

    Programmatically setting a local user account to never expire its password


    As a Windows tester, I install Windows on my own machines a lot (this is known internally as "selfhosting", or "dogfooding", or "ice cream-ing".)

    One of my little idiosyncracies is I like to run as a non-administrative user.  That is, I don't add my domain account to the local Administrators group.

    Instead, I create a local "Admin" account with a known (to me) password; every time I need to elevate, I get a prompt that asks for credentials rather than just "Yes/No".  To this prompt I pass the credentials of the local "Admin" account.

    Although I usually install fresh builds regularly (on my multiple machines), sometimes one machine gets a little stale.  In fact, it happened once that my local .\Admin account got so stale that I had to change the password!  This was annoying enough that I devoted some energy into figuring out how to check the "Password never expires" box on the local account properties programmatically.

    The result was the following script: call as cscript.exe never-expire-admin-password.wsf  This version hardcodes the username "Admin"; a production version would probably allow passing a username in via the command line.

    If the Admin password already has the box checked, this script does nothing.

    ' LDAP doesn't work for controlling local users
    ' (unless you're a domain controller, of course)
    ' have to use WinNT provider instead


    ' hardcoding "Admin" username
    Dim admin: Set admin = GetObject("WinNT://localhost/Admin,user")

    WScript.Echo "Admin's userFlags are 0x" & Hex(admin.userFlags)

    If Not admin.userFlags And ADS_UF_DONT_EXPIRE_PASSWD Then
        WScript.Echo "Setting local admin account to never expire password"
        admin.userFlags = (admin.userFlags Or ADS_UF_DONT_EXPIRE_PASSWD)

        ' Save
    End If

  • Matthew van Eerde's web log

    shellproperty.exe v2: read all properties on a file; set properties of certain non-VT_LPWSTR types


    I updated my toy app to set/read shell properties from the command line. New features:

    1. Read all the properties from a given file in one go.
    2. Recognize properties by their canonical name (if they have one.)
    3. Set a property to VT_EMPTY (removing it), or "VT_VECTOR | VT_LPWSTR", or VT_UI4, in addition to VT_LPWSTR.


    shellproperty read [ <key> | all ] from <filename>
    shellproperty set <key> on <filename> to <vartype> <vartype-specific-arguments>

    <vartype>: VT_EMPTY | VT_LPWSTR | "VT_VECTOR | VT_LPWSTR" | VT_UI4

    Example of reading all properties from a file:

    >shellproperty read all from "I 01 Track 1.mp3" | sort
    {9E5E05AC-1936-4A75-94F7-4704B8B01923} 0: VT_BSTR I 01 Track 1.mp3
    {CFA31B45-525D-4998-BB44-3F7D81542FA4} 1: VT_LPWSTR MP3
    System.Audio.ChannelCount: 2 (stereo)
    System.Audio.EncodingBitrate: 320kbps
    System.Audio.Format: {00000055-0000-0010-8000-00AA00389B71}
    System.Audio.IsVariableBitRate: No
    System.Audio.PeakValue: 23841
    System.Audio.SampleRate: 44 kHz
    System.Audio.SampleSize: 16 bit
    System.Audio.StreamNumber: 0
    System.Author: Unknown artist
    System.ComputerName: MATEER-D (this PC)
    System.ContentType: audio/mpeg
    System.DateAccessed: 9/3/2013 5:55 PM
    System.DateCreated: 9/3/2013 5:55 PM
    System.DateImported: 9/3/2013 5:55 PM
    System.DateModified: 9/24/2013 3:21 PM
    System.Document.DateCreated: 9/3/2013 5:55 PM
    System.Document.DateSaved: 9/24/2013 3:21 PM
    System.DRM.IsProtected: No
    System.FileAttributes: A
    System.FileExtension: .mp3
    System.FileName: I 01 Track 1.mp3
    System.FileOwner: REDMOND\mateer
    System.FilePlaceholderStatus: 7
    System.IsFolder: Files
    System.IsShared: No
    System.ItemAuthors: Unknown artist
    System.ItemDate: 9/3/2013 5:55 PM
    System.ItemFolderNameDisplay: Les Misérables (concept album)
    System.ItemFolderPathDisplay: C:\music\Claude-Michel Schönberg & Alain Boublil\Les Misérables (concept album)
    System.ItemFolderPathDisplayNarrow: Les Misérables (concept album) (C:\music\Claude-Michel Schönberg & Alain Boublil)
    System.ItemName: I 01 Track 1.mp3
    System.ItemNameDisplay: I 01 Track 1.mp3
    System.ItemNameDisplayWithoutExtension: I 01 Track 1
    System.ItemParticipants: Unknown artist
    System.ItemPathDisplay: C:\music\Claude-Michel Schönberg & Alain Boublil\Les Misérables (concept album)\I 01 Track 1.mp3
    System.ItemPathDisplayNarrow: I 01 Track 1 (C:\music\Claude-Michel Schönberg & Alain Boublil\Les Misérables (concept album))
    System.ItemType: MP3 File
    System.ItemTypeText: MP3 File
    System.Kind: Music
    System.KindText: Music
    System.Media.AverageLevel: 4219
    System.Media.ClassPrimaryID: {D1607DBC-E323-4BE2-86A1-48A42A28441E}
    System.Media.ClassSecondaryID: {00000000-0000-0000-0000-000000000000}
    System.Media.CollectionGroupID: {3B02CC9D-BE3E-43A4-81AA-DC23DFD20083}
    System.Media.CollectionID: {3B02CC9D-BE3E-43A4-81AA-DC23DFD20083}
    System.Media.ContentID: {3780156C-B516-4897-B6AC-CB632A0CA4A5}
    System.Media.DlnaProfileID: MP3
    System.Media.Duration: 00:04:47
    System.Media.MCDI: E+96+54E9+98AD+A23C+DBD5+F62C+11889+15B50+170F9+1C1EC+1E01E+221A7+2916C+2C6EB+2F21A
    System.Media.MetadataContentProvider: AMG
    System.Media.Publisher: Colosseum
    System.Media.UniqueFileIdentifier: AMGt_id=T 987037;AMGp_id=P 1857378;AMGa_id=R 189777;X_id={9D0F0F00-0500-11DB-89CA-0019B92A3933};XA_id={51E50200-0400-11DB-89CA-0019B92A3933};XAP_id={6357088C-778C-11DC-9403-0019B9B20868}
    System.Media.Year: 1989
    System.MIMEType: audio/mpeg
    System.Music.AlbumArtist: Various Artists
    System.Music.AlbumID: Various Artists - Les Miserables - French Concept Album: 1 of 2
    System.Music.AlbumTitle: Les Miserables - French Concept Album: 1 of 2
    System.Music.Artist: Unknown artist
    System.Music.Composer: Alain Boublil; Claude-Michel Schönberg
    System.Music.DisplayArtist: Various Artists
    System.Music.Genre: Unknown genre
    System.Music.PartOfSet: 1/1
    System.Music.TrackNumber: 1
    System.NotUserContent: No
    System.OfflineAvailability: Available offline
    System.ParsingName: I 01 Track 1.mp3
    System.ParsingPath: C:\music\Claude-Michel Schönberg & Alain Boublil\Les Misérables (concept album)\I 01 Track 1.mp3
    System.PerceivedType: Audio
    System.SFGAOFlags: 1077936503
    System.ShareScope: music\Claude-Michel Schönberg & Alain Boublil\Les Misérables (concept album)
    System.SharingStatus: Not shared
    System.Shell.SFGAOFlagsStrings: filesys; stream
    System.Size: 10.9 MB
    System.ThumbnailCacheId: 16520045390528741485
    System.Title: Track 1
    System.VolumeId: {14FF6E9D-14F5-11E3-824C-806E6F6E6963}
    System.ZoneIdentifier: 0

    Example of updating a file:

    >type _fixup.bat
    @echo off

    for /f "usebackq delims=" %%f in (`dir /s /b "I *.mp3"`) do (
        shellproperty set System.Music.AlbumTitle on "%%f" to VT_LPWSTR "Madama Butterfly - Sinopoli / Freni: 1 of 3"

    for /f "usebackq delims=" %%f in (`dir /s /b "II *.mp3"`) do (
        shellproperty set System.Music.AlbumTitle on "%%f" to VT_LPWSTR "Madama Butterfly - Sinopoli / Freni: 2 of 3"

    for /f "usebackq delims=" %%f in (`dir /s /b "III *.mp3"`) do (
        shellproperty set System.Music.AlbumTitle on "%%f" to VT_LPWSTR "Madama Butterfly - Sinopoli / Freni: 3 of 3"

    Source and binaries (x86 and amd64) attached.

  • Matthew van Eerde's web log

    Troubleshooting default audio device heuristics


    In Windows 7 we published a white paper which shows how Windows chooses which audio device should be the default. This remains true for Windows 8 and Windows 8.1.

    The six factors that are considered for each device are:

    1. Jack detection capability
      1. Whether KSJACK_DESCRIPTION2.JackCapabilities has the JACKDESC2_PRESENCE_DETECT_CAPABILITY flag set
      2. USB audio devices have jack detection capability grandfathered in
    2. Form factor: PKEY_AudioEndpoint_FormFactor
    3. Kernel Streaming node type: PKEY_AudioEndpoint_JackSubType (this comes from KSPIN_DESCRIPTOR.Category field set by the driver)
    4. Bus type: HD Audio, Bluetooth, USB, etc.
    5. General location: IKsJackDescription's KSJACK_DESCRIPTION.GenLocation
    6. Geometric location: IKsJackDescrption's KSJACK_DESCRIPTION.GeoLocation

    Each factor has an associated preference list and a weight. Generally speaking, items that appear higher in the list above have priority over items that appear lower.

    I wrote a tool that uses public APIs to query each of these six factors for all devices on the system. Source and binaries are attached.

    Here's what it outputs on my system that has a built-in HD Audio device and an external USB device plugged in:

    Render devices: 4
        Speakers (High Definition Audio Device)
        Id: {}.{2089b0a1-893b-45fa-b6e8-ec84ee827c06}
        KSJACK_DESCRIPTION2.JackCapabilities: presence detection not supported
        PKEY_AudioEndpoint_FormFactor: 1 (Speakers)
        PKEY_AudioEndpoint_JackSubType: {DFF21CE1-F70F-11D0-B917-00A0C9223196} (KSNODETYPE_SPEAKER)
        PKEY_Device_EnumeratorName: HDAUDIO
        KSJACK_DESCRIPTION.GenLocation: 0 (eGenLocPrimaryBox)
        KSJACK_DESCRIPTION.GeoLocation: 13 (eGeoLocATAPI)

        Speakers (USB Audio)
        Id: {}.{4bf6c88a-9b54-46cc-9eb0-91c5fa468ed6}
        IKsJackDescription2 not supported
        PKEY_AudioEndpoint_FormFactor: 1 (Speakers)
        PKEY_AudioEndpoint_JackSubType: {DFF21CE1-F70F-11D0-B917-00A0C9223196} (KSNODETYPE_SPEAKER)
        PKEY_Device_EnumeratorName: USB
        IKsJackDescription not supported

        Speakers (High Definition Audio Device)
        Id: {}.{89482885-856d-4b7b-88ed-3ffa4944f14b}
        KSJACK_DESCRIPTION2.JackCapabilities: presence detection supported
        PKEY_AudioEndpoint_FormFactor: 1 (Speakers)
        PKEY_AudioEndpoint_JackSubType: {DFF21CE1-F70F-11D0-B917-00A0C9223196} (KSNODETYPE_SPEAKER)
        PKEY_Device_EnumeratorName: HDAUDIO
        KSJACK_DESCRIPTION.GenLocation: 0 (eGenLocPrimaryBox)
        KSJACK_DESCRIPTION.GeoLocation: 1 (eGeoLocRear)

        SPDIF Interface (USB Audio)
        Id: {}.{c2c47ae1-abe1-43fa-825a-871bab831cf4}
        IKsJackDescription2 not supported
        PKEY_AudioEndpoint_FormFactor: 8 (SPDIF)
        PKEY_AudioEndpoint_JackSubType: {DFF21FE5-F70F-11D0-B917-00A0C9223196} (KSNODETYPE_SPDIF_INTERFACE)
        PKEY_Device_EnumeratorName: USB
        IKsJackDescription not supported

    Capture devices: 3
        Microphone (USB Audio)
        Id: {}.{139b7b57-28d2-46de-9fce-702c0944c03f}
        IKsJackDescription2 not supported
        PKEY_AudioEndpoint_FormFactor: 4 (Microphone)
        PKEY_AudioEndpoint_JackSubType: {DFF21BE1-F70F-11D0-B917-00A0C9223196} (KSNODETYPE_MICROPHONE)
        PKEY_Device_EnumeratorName: USB
        IKsJackDescription not supported

        Line (USB Audio)
        Id: {}.{67982570-bdea-4524-82ac-3d15d2b5ae89}
        IKsJackDescription2 not supported
        PKEY_AudioEndpoint_FormFactor: 2 (LineLevel)
        PKEY_AudioEndpoint_JackSubType: {DFF21FE3-F70F-11D0-B917-00A0C9223196} (KSNODETYPE_LINE_CONNECTOR)
        PKEY_Device_EnumeratorName: USB
        IKsJackDescription not supported

        SPDIF Interface (USB Audio)
        Id: {}.{72b7efb6-c21a-4415-84e5-292d8a5b2b11}
        IKsJackDescription2 not supported
        PKEY_AudioEndpoint_FormFactor: 8 (SPDIF)
        PKEY_AudioEndpoint_JackSubType: {DFF21FE5-F70F-11D0-B917-00A0C9223196} (KSNODETYPE_SPDIF_INTERFACE)
        PKEY_Device_EnumeratorName: USB
        IKsJackDescription not supported

    Jack Detection Capability

    The first render device does not have jack presence detection, and the other three do. (Note the USB audio devices have grandfathered-in jack presence detection, even though the USB audio class driver does not support IKsJackDescription2.) This puts the first render device out of the running.

    Form Factor

    The fourth render device has form factor SPDIF, and the second and third have form factor Speakers. This puts the fourth render device out of the running, since the heuristics consider Speakers more attractive than SPDIF.

    Kernel Streaming node type

    The two remaining render devices both have form factor KSNODETYPE_SPEAKER, so they are tied.

    Bus type

    The second render device has bus type USB, and the third render device has bus type HD Audio. The default device heuristics prefer USB to HD Audio, so the second render device becomes the default audio render device.

  • Matthew van Eerde's web log

    Sieving irreducible monic polynomials over a finite field


    Last time we talked about the addition and multiplication tables for GF(2²); GF(28) is relevant for the Rijndael encryption scheme.

    Joan Daemen and Vincent Rijmen use a representation of GF(28) where each element is a polynomial of the form b7x7 + b6x6 + b5x5 + b4x4 + b3x3 + b2x2 + b1x + b0, with the bi being bits. A polynomial is represented by a single byte. Addition is component-wise (modulo 2); two polynomials can be added with a single XOR operation. Multiplication is a little more complicated.

    Rijndael defines multiplication as normal polynomial multiplication, followed by taking a modulus using the reduction polynomial mx8 + x4 + x3 + x + 1. Last time we showed that, in GF(2²), x2 + x + 1 was used to reduce polynomial multiplication, and also that a necessary quality for a reduction polynomial is that it be irreducible/prime.

    Last time I hinted at the question: how do we show that m is irreducible? Well, one way is to do trial division of all polynomials up to degree 4.

    But that's no fun. Instead, let's write a script to generate all irreducible polynomials, and see if m is on it! The approach is very similar to the Sieve of Eratosthenes: generate a list of all the polynomials, then traverse it from the low end to the high end; circle each element that hasn't been crossed out, then cross out all multiples of that element.

    The first argument q is the modulus of the coefficients (in this case, 2) and the second argument d (in this case, 8) is how high the degree can go. Here is the output of the script:

    >perl 2 8
    Finding monic irreducible polynomials of degree up to 8 and coefficients mod 2
    Generating all monic polynomials...
    Sieving out all reducible polynomials...
    x + 1
    x^2 + x + 1
    x^3 + x + 1
    x^3 + x^2 + 1
    x^4 + x + 1
    x^4 + x^3 + 1
    x^4 + x^3 + x^2 + x + 1
    x^5 + x^2 + 1
    x^5 + x^3 + 1
    x^5 + x^3 + x^2 + x + 1
    x^5 + x^4 + x^2 + x + 1
    x^5 + x^4 + x^3 + x + 1
    x^5 + x^4 + x^3 + x^2 + 1
    x^6 + x + 1
    x^6 + x^3 + 1
    x^6 + x^4 + x^2 + x + 1
    x^6 + x^4 + x^3 + x + 1
    x^6 + x^5 + 1
    x^6 + x^5 + x^2 + x + 1
    x^6 + x^5 + x^3 + x^2 + 1
    x^6 + x^5 + x^4 + x + 1
    x^6 + x^5 + x^4 + x^2 + 1
    x^7 + x + 1
    x^7 + x^3 + 1
    x^7 + x^3 + x^2 + x + 1
    x^7 + x^4 + 1
    x^7 + x^4 + x^3 + x^2 + 1
    x^7 + x^5 + x^2 + x + 1
    x^7 + x^5 + x^3 + x + 1
    x^7 + x^5 + x^4 + x^3 + 1
    x^7 + x^5 + x^4 + x^3 + x^2 + x + 1
    x^7 + x^6 + 1
    x^7 + x^6 + x^3 + x + 1
    x^7 + x^6 + x^4 + x + 1
    x^7 + x^6 + x^4 + x^2 + 1
    x^7 + x^6 + x^5 + x^2 + 1
    x^7 + x^6 + x^5 + x^3 + x^2 + x + 1
    x^7 + x^6 + x^5 + x^4 + 1
    x^7 + x^6 + x^5 + x^4 + x^2 + x + 1
    x^7 + x^6 + x^5 + x^4 + x^3 + x^2 + 1
    x^8 + x^4 + x^3 + x + 1
    x^8 + x^4 + x^3 + x^2 + 1
    x^8 + x^5 + x^3 + x + 1
    x^8 + x^5 + x^3 + x^2 + 1
    x^8 + x^5 + x^4 + x^3 + 1
    x^8 + x^5 + x^4 + x^3 + x^2 + x + 1
    x^8 + x^6 + x^3 + x^2 + 1
    x^8 + x^6 + x^4 + x^3 + x^2 + x + 1
    x^8 + x^6 + x^5 + x + 1
    x^8 + x^6 + x^5 + x^2 + 1
    x^8 + x^6 + x^5 + x^3 + 1
    x^8 + x^6 + x^5 + x^4 + 1
    x^8 + x^6 + x^5 + x^4 + x^2 + x + 1
    x^8 + x^6 + x^5 + x^4 + x^3 + x + 1
    x^8 + x^7 + x^2 + x + 1
    x^8 + x^7 + x^3 + x + 1
    x^8 + x^7 + x^3 + x^2 + 1
    x^8 + x^7 + x^4 + x^3 + x^2 + x + 1
    x^8 + x^7 + x^5 + x + 1
    x^8 + x^7 + x^5 + x^3 + 1
    x^8 + x^7 + x^5 + x^4 + 1
    x^8 + x^7 + x^5 + x^4 + x^3 + x^2 + 1
    x^8 + x^7 + x^6 + x + 1
    x^8 + x^7 + x^6 + x^3 + x^2 + x + 1
    x^8 + x^7 + x^6 + x^4 + x^2 + x + 1
    x^8 + x^7 + x^6 + x^4 + x^3 + x^2 + 1
    x^8 + x^7 + x^6 + x^5 + x^2 + x + 1
    x^8 + x^7 + x^6 + x^5 + x^4 + x + 1
    x^8 + x^7 + x^6 + x^5 + x^4 + x^2 + 1
    x^8 + x^7 + x^6 + x^5 + x^4 + x^3 + 1

    Note that m is not just prime, it is the lowest of the monic 8-degree polynomials with coefficients mod 2.

    The script itself (in Perl) is attached. It makes no claims to being pretty or efficient.

    As a check, there is an elegant formula for the number of irreducible monic polynomials with coefficients in a finite field:

    N(q, n) = (Σd|n μ(d) qn/d)/n

    where μ(x) is the Möbius function.

    In particular:

    N(2, 1) = ((1)21/1)/1 = 2
    N(2, 2) = ((1)22/1 - (1)22/2)/2 = 1
    N(2, 3) = ((1)23/1 - (1)23/3)/3 = 2
    N(2, 4) = ((1)24/1 - (1)24/2 + (0)24/4)/4 = 3
    N(2, 5) = ((1)25/1 - (1)25/5)/5 = 6
    N(2, 6) = ((1)26/1 - (1)26/2 - (1)26/3 + (1)26/6)/6 = 9
    N(2, 7) = ((1)27/1 - (1)27/7)/7 = 18
    N(2, 8) = ((1)28/1 - (1)28/2 + (0)28/4 + (0)28/8)/8 = 30

    This checks out with the script.

  • Matthew van Eerde's web log

    Microsoft etiquette: calendar appointments when going out of office


    A common convention within Microsoft when going out of office is to create two calendar appointments in Outlook:

    1. An appointment which allows people who are trying to schedule a meeting with you to know that you're out of office at the time
      1. Attendees: just you
      2. Show As: Out of Office
    2. An informational appointment which allows people who work closely with you to know when you're going to be out of office
      1. Attendees: people who work closely with you (e.g., your immediate team)
      2. Show As: Free
      3. Request Responses: Off
      4. Allow New Time Proposals: Off
      5. Reminder: None

    I'm always forgetting one of the steps under 2., so I'm creating this blog post. Next time I go out of office I'll remember to check this post.

  • Matthew van Eerde's web log

    Wheel of Fortune conundrum

    What happens if Pat gets a Bankrupt on the Final Spin?
  • Matthew van Eerde's web log

    Troubleshooting: how to install the Microsoft HD Audio class driver


    Most on-the-motherboard audio devices support the Intel High Definition Audio standard.  Windows Vista (and later) includes a "class driver", hdaudio.sys, which should work with any such audio device.

    Usually systems come with a vendor-supplied driver installed.  This driver is designed specifically for the hardware it runs on (as opposed to being designed to the standard) and so it comes with additional functionality.

    Occasionally, for troubleshooting purposes, it is useful to switch from one driver to the other... either to get the additional functionality provided by the vendor-supplied driver, or to see what happens if the class driver is installed.

    Here's how to switch back and forth.

    Click "Start".

    Type "devmgmt.msc" (without the quotes) to launch Device Manager.

    Expand the "Sound, video and game controllers" node and note the list of audio devices.  In this case, I have one audio device, and by the "High Definition Audio Device" name I deduce that I have the class driver installed.  (If the device name included a company name, I would infer that I had a vendor driver installed.)

    Right-click the device you want to change the software on.

    Windows offers to automatically detect the driver that should be installed.  No thanks, we want to pick a particular driver:

    Windows asks where we want to look - do we have a set of driver files, or is the driver already in the list of installed drivers?  In this case, we want to look at the list of drivers for this hardware that are already installed.

    Windows shows us a list of drivers that are already installed and usable for this hardware.  At this point you would expect to see two drivers listed: the vendor driver, and "High Definition Audio Device."  (When I made this blog post, I was lazy, so I didn't bother to make a screenshot that showed two drivers.)

    To install the class driver, pick "High Definition Audio Device" and click "Next."

    "High Definition Audio Device" is a class driver, so you get this warning.  Click "Yes."

    Windows does its thing...

    ... and eventually tells you that it's done.  If your audio device was in use, you may get the "you have to restart" message.  Regardless, click "Close".

    If you got the "you will need to restart" message, Windows helpfully offers to restart right away.

    Make sure you're ready for a restart (no unsaved documents or anything) and restart either by clicking "Yes" or by using the Start menu.

    When you're done experimenting, you can go back to the vendor-supplied driver by going through the same steps and choosing the vendor-supplied driver in the list of drivers.

  • Matthew van Eerde's web log

    UI text flow of various languages


    This point came up today in a spec review. The text had language like "put the choices 1-N in rows of three, starting with 1 in the top left corner of the screen, and in rows of three to four going down the screen."

    Western Europeans and 'mericans (like me) are accustomed to text layout that looks like this, so the text makes sense to us.

    Left-to-right, top-down:

    But there's a significant number of people in the world (some of whom would use a product designed to the spec) that read right-to-left.  They would be much more comfortable with a text layout like this:

    Right-to-left, top-down:

    These people include those who read Arabic or Hebrew, for example.

    There's also a third text layout that needs a little introduction.  There are two different layouts for Japanese.

    The traditional way to write Japanese is in vertical columns, each of which is read/written top down, and the columns are themselves read right-to-left.  A Japanese would expect a product with a "traditional feel" to be laid out like this:

    Top-down, right-to-left:

    To digress a little...

    If vertical space is constrained (for example, for the sign above a storefront) there might only be room for one character per column:

    Top-down, right-to-left, columns are one character high:

    To the casual viewer, this is easily confused with right-to-left / top-down.  Japanese is sometimes incorrectly thought of as an RTL language.  There's a similar effect in Western European languages on some hotel signs that have lots of vertical space but only enough horizontal room for one column.

    Finally, I should mention that Japanese also has a modern text layout.  For products that are aiming for a modern feel as opposed to a traditional feel, it is usual to write the Japanese characters in left-to-right / top-down layout.

    (Footnote: note that single-row-high Japanese text can be read either left-to-right, or right-to-left, depending on context.  There are often clues available to allow a reader to know which way to read; for example, I believe some characters are written differently depending on which layout is in effect.  Also, the font or the setting may give hints as to whether a "traditional" or "modern" feel is intended.)

  • Matthew van Eerde's web log

    Downmixing stereo to mono


    Suppose you have a stereo stream that you want to downmix to mono.  Why would you do this?  Maybe you're playing stereo music to a Bluetooth headset that is in a call, and thus in "headset / handsfree" mode.  Maybe you're capturing from a stereo mic and you want to show a visualization based on a mono reduction of the signal.

    The basic formula to use is M = L / 2 + R / 2 but there are a couple of things to be aware of.

    Consider the simplest case first where the left and right channels are identical.  Naturally, the resulting mono signal is identical to both of the channels:

    In particular, downmixing a stereo signal of identical full scale sine waves (power = -3 dB FS) results in a -3 dB FS mono sine wave.  Well and good.

    This, however, is a simple coincidence few could ever have counted upon.  (A similar effect is the lack of spectral leakage if your signal period exactly matches up to your FFT window period.)  As a rule, downmixing results in a loss of power.  To get a basic idea of why this is, let's take two different sine waves and downmix to a two-tone:

    Note that downmixing these two totally uncorrelated signals results in a loss of power of 3 dB FS; the power of the two-tone is -6 dB FS, 3 dB FS lower than each of the individual -3 dB FS signals that went into it.

    It is tempting to conclude that mixing two signals of power P gives a resultant signal of power between P - 3 dB and P, depending on the degree of correlation.  However, this conclusion is incorrect: signals can be correlated; uncorrelated; or anticorrelated.

    Once in a while you get a stereo microphone which captures heavily correlated L and R channels, but (due to one reason and another) inverts one of the channels.  Instead of being heavily correlated, the L and R signals are now heavily anticorrelated. This is bad enough when you try to listen to it: but when you downmix to mono, the signal disappears!

    The effect with a "real" stereo signal is somewhat less dramatic because it's receiving only very highly correlated signals (not perfectly correlated.)  So the downmix to mono only almost totally destroys the signal.

  • Matthew van Eerde's web log

    optimal tic-tac-toe (XKCD)


    Today's XKCD strip purports to show a complete map of tic-tac-toe including optimal moves.

    I'm guessing the optimality of the move takes into account both the game-theoretic value of the move, assuming a perfect opponent:

    • Good moves
      • Preserves the win
      • Preserves the draw
      • "Preserves the loss" (every move in a lost position is of this type)
    • "Bad" moves
      • Converts won position to drawn position
      • Converts drawn position to lost position
    • "Very bad" moves
      • Converts won position to lost position

    And the practical value of the move, which allows for the possibility of error either on the player's part or the opponent's part.

    In particular, I'm guessing that at each level Randall eliminated all the "bad" (and "very bad") moves, then broke the "well, all these moves are 'good'" tie by looking at some aggregation of the values of all following positions.  The idea is, you want to pick the "good move" which:

    • In a won position: minimizes the chance that you'll make a "bad" move (or a "very bad" move) later
    • In a drawn position: maximizes the chance that your opponent will make a "bad" move
      • Further ties broken by minimizing the chance that you'll make a "bad" move later
    • In a lost position: maximizes the chance that your opponent will make a "bad" move (or a "very bad" move)

    If there are still ties, I am guessing that Randall invoked a randomizer.  For example, X's first move (in the top left corner) is tied in every possible respect with the other three corners.

    I object to this approach because humans don't always pick moves randomly.  They learn.  They make rules for themselves, some of which are fallible, and can be exploited.  For example, "move in the center if you can" is a typical rule, followed by "if you can't move in the center, move in a corner."  Also, I think information is lost by hiding the other "good" moves.

    So, here's my version.  I don't go into nearly as much depth, but here is the game-theoretic value of all of X's first moves (W = win; D = draw; L = loss:)

    Not very exciting... a nine-way tie.

    And for each first move for X, here is the game-theoretic value of all of O's first moves (I only show three, the others can be inferred by symmetry:)

    For an example of a "very bad" move, consider the position after X moves in the top left corner (a good move) and O moves in the lower left corner (a bad move, after which X has a won position:)

  • Matthew van Eerde's web log

    Perl script to parse MPEG audio header


    I've written a perl script (attached) which will parse MPEG audio headers and display them in a human-readable format.

    For example, if you run it on ding.mpeg (also attached) you get this output:

    >perl ding.mpeg
    Frame header: 11111111 111 (should be all ones)
    MPEG Audio version ID: 11 (MPEG version 1 (ISO/IEC 11172-3))
    Layer description: 10 (layer II)
    Protection bit: 0 (protected by CRC (16-bit CRC follows header))
    Bitrate index: 1011 (224 kbps)
    Sample rate index: 00 (44100 Hz)
    Padding bit: 0 (frame is not padded)
    Private bit: 0 (application specific)
    Channel mode: 00 (stereo)
    Mode extension (if channel mode is joint stereo:) 00 (bands 4 to 31)
    Copyright: 0 (audio is not copyrighted)
    Original: 0 (copy of original media)
    Emphasis: 00 (none)

    Here's the source for the perl script:

    use strict;

    # based on
    # assumes that the file you point it at starts with an MPEG audio header

    unless (1 == @ARGV and $ARGV[0] ne "/?" and $ARGV[0] ne "-?") {
        print "USAGE: perl $0 mpeg-audio-file.mpeg";

    my %version = (
        "00" => "MPEG Version 2.5 (unofficial)",
        "01" => "reserved",
        "10" => "MPEG version 2 (ISO/IEC 13818-3)",
        "11" => "MPEG version 1 (ISO/IEC 11172-3)",

    my %layer = (
        "00" => "reserved",
        "01" => "layer III",
        "10" => "layer II",
        "11" => "layer I",

    my %protection = (
        "0" => "protected by CRC (16-bit CRC follows header)",
        "1" => "not protected",

    my %bitrate = (
        # version 1
        "11" => {
            # layer 1
            "11" => {
                "0000" => "free",
                "0001" => "32 kbps",
                "0010" => "64 kbps",
                "0011" => "96 kbps",
                "0100" => "128 kbps",
                "0101" => "160 kbps",
                "0110" => "192 kbps",
                "0111" => "224 kbps",
                "1000" => "256 kbps",
                "1001" => "288 kbps",
                "1010" => "320 kbps",
                "1011" => "352 kbps",
                "1100" => "384 kbps",
                "1101" => "416 kbps",
                "1110" => "448 kbps",
                "1111" => "bad",

            # layer 2
            "10" => {
                "0000" => "free",
                "0001" => "32 kbps",
                "0010" => "48 kbps",
                "0011" => "56 kbps",
                "0100" => "64 kbps",
                "0101" => "80 kbps",
                "0110" => "96 kbps",
                "0111" => "112 kbps",
                "1000" => "128 kbps",
                "1001" => "160 kbps",
                "1010" => "192 kbps",
                "1011" => "224 kbps",
                "1100" => "256 kbps",
                "1101" => "320 kbps",
                "1110" => "384 kbps",
                "1111" => "bad",

            # layer 3
            "01" => {
                "0000" => "free",
                "0001" => "32 kbps",
                "0010" => "40 kbps",
                "0011" => "48 kbps",
                "0100" => "56 kbps",
                "0101" => "64 kbps",
                "0110" => "80 kbps",
                "0111" => "96 kbps",
                "1000" => "112 kbps",
                "1001" => "128 kbps",
                "1010" => "160 kbps",
                "1011" => "192 kbps",
                "1100" => "224 kbps",
                "1101" => "256 kbps",
                "1110" => "320 kbps",
                "1111" => "bad",

        # version 2
        "10" => {
            # layer 1
            "11" => {
                "0000" => "free",
                "0001" => "32 kbps",
                "0010" => "48 kbps",
                "0011" => "56 kbps",
                "0100" => "64 kbps",
                "0101" => "80 kbps",
                "0110" => "96 kbps",
                "0111" => "112 kbps",
                "1000" => "128 kbps",
                "1001" => "144 kbps",
                "1010" => "160 kbps",
                "1011" => "176 kbps",
                "1100" => "192 kbps",
                "1101" => "224 kbps",
                "1110" => "256 kbps",
                "1111" => "bad",

            # layer 2
            "10" => {
                "0000" => "free",
                "0001" => "8 kbps",
                "0010" => "16 kbps",
                "0011" => "24 kbps",
                "0100" => "32 kbps",
                "0101" => "40 kbps",
                "0110" => "48 kbps",
                "0111" => "56 kbps",
                "1000" => "64 kbps",
                "1001" => "80 kbps",
                "1010" => "96 kbps",
                "1011" => "112 kbps",
                "1100" => "128 kbps",
                "1101" => "144 kbps",
                "1110" => "160 kbps",
                "1111" => "bad",

            # layer 3
            "01" => {
                "0000" => "free",
                "0001" => "8 kbps",
                "0010" => "16 kbps",
                "0011" => "24 kbps",
                "0100" => "32 kbps",
                "0101" => "40 kbps",
                "0110" => "48 kbps",
                "0111" => "56 kbps",
                "1000" => "64 kbps",
                "1001" => "80 kbps",
                "1010" => "96 kbps",
                "1011" => "112 kbps",
                "1100" => "128 kbps",
                "1101" => "144 kbps",
                "1110" => "160 kbps",
                "1111" => "bad",

    my %samplerate = (
        # version 1
        "11" => {
            "00" => "44100 Hz",
            "01" => "48000 Hz",
            "10" => "32000 Hz",
            "11" => "reserved",

        # version 2
        "10" => {
            "00" => "22050 Hz",
            "01" => "24000 Hz",
            "10" => "16000 Hz",
            "11" => "reserved",

        # version 2.5 (unofficial)
        "00" => {
            "00" => "11025 Hz",
            "01" => "12000 Hz",
            "10" => "8000 Hz",
            "11" => "reserved",

    my %padding = (
        "0" => "frame is not padded",
        "1" => "frame is padded with one extra slot",

    my %channelmode = (
        "00" => "stereo",
        "01" => "joint stereo (stereo)",
        "10" => "dual channel (stereo)",
        "11" => "single channel (mono)",

    my %modeextension = (
        # layer I
        "11" => {
            "00" => "bands 4 to 31",
            "01" => "bands 8 to 31",
            "10" => "bands 12 to 31",
            "11" => "bands 16 to 31",

        # layer II
        "10" => {
            "00" => "bands 4 to 31",
            "01" => "bands 8 to 31",
            "10" => "bands 12 to 31",
            "11" => "bands 16 to 31",

        # layer III
        "01" => {
            "00" => "intensity stereo off; m/s stereo off",
            "01" => "intensity stereo on; m/s stereo off",
            "10" => "intensity stereo off; m/s stereo on",
            "11" => "intensity stereo on; m/s stereo on",

    my %copyright = (
        "0" => "audio is not copyrighted",
        "1" => "audio is copyrighted",

    my %original = (
        "0" => "copy of original media",
        "1" => "original media",

    my %emphasis = (
        "00" => "none",
        "01" => "50/15 microseconds", # the source incorrectly says "ms" which is milliseconds
        "10" => "reserved",
        "11" => "CCIT J.17",

    open(MPEG, "<", $ARGV[0]) or die("Could not open $ARGV[0]: $!");
    binmode(MPEG) or die("Could not set file handle to binary mode: $!"); # binary file

    my $header = "";

    my $header_size = 16;
    my $read = read(MPEG, $header, $header_size, 0);


    $header_size == $read or die("Expected $header_size bytes to be read, not $read");

    my @bits = ();

    for my $byte (map { ord( $_ ) } split (//, $header)) {
        for my $bit (1 .. 8) {
            push @bits, (($byte & (1 << (8 - $bit))) ? 1 : 0);

    unless ("1" x 11 eq join("", @bits[0 .. 10])) {
        printf("WARNING: the frame header is not all ones. This is not a valid MPEG audio header.\n");
        # carry on regardless

        "Frame header: %s %s (%s)\n" .
        "MPEG Audio version ID: %s (%s)\n" .
        "Layer description: %s (%s)\n" .
        "Protection bit: %s (%s)\n" .
        "Bitrate index: %s (%s)\n" .
        "Sample rate index: %s (%s)\n" .
        "Padding bit: %s (%s)\n" .
        "Private bit: %s (%s)\n" .
        "Channel mode: %s (%s)\n" .
        "Mode extension (if channel mode is joint stereo:) %s (%s)\n" .
        "Copyright: %s (%s)\n" .
        "Original: %s (%s)\n" .
        "Emphasis: %s (%s)\n" .
        join("", @bits[0 .. 7]), join("", @bits[8 .. 10]), "should be all ones",
        join("", @bits[11 .. 12]), $version{ join("", @bits[11 .. 12]) },
        join("", @bits[13 .. 14]), $layer{ join("", @bits[13 .. 14]) },
        $bits[15], $protection{ $bits[15] },
        join("", @bits[16 .. 19]),
            # bit rate depends on version, layer, and bitrate index
            $bitrate{ join("", @bits[11 .. 12]) }{ join("", @bits[13 .. 14]) }{ join("", @bits[16 .. 19]) },
        join("", @bits[20 .. 21]),
            # sample rate depends on version
            $samplerate{ join("", @bits[11 .. 12]) }{ join("", @bits[20 .. 21]) },
        $bits[22], $padding{ $bits[22] },
        $bits[23], "application specific",
        join("", @bits[24 .. 25]), $channelmode{ join("", @bits[24 .. 25]) },
        join("", @bits[26 .. 27]),
            # mode extension depends on layer
            $modeextension{ join("", @bits[13 .. 14]) }{ join("", @bits[26 .. 27]) },
        $bits[28], $copyright{ $bits[28] },
        $bits[29], $original{ $bits[29] },
        join("", @bits[30 .. 31]), $emphasis{ join("", @bits[30 .. 31]) },

    Note this script assumes that the very first bytes of the file are the MPEG audio header, and makes no effort to dig into the file to find the audio header.

  • Matthew van Eerde's web log

    Interview question: baby boys and baby girls



    In a country in which people only want boys, every family continues to have children until they have a boy. If they have a girl, they have another child. If they have a boy, they stop. What is the proportion of boys to girls in the country?

    Answer next week.

    EDIT 4/26: Answer.

  • Matthew van Eerde's web log

    Solution: baby boys and baby girls


    Last week I posed an interview question about the ratio of baby boys to girls in a hypothetical country.

    In a country in which people only want boys, every family continues to have children until they have a boy. If they have a girl, they have another child. If they have a boy, they stop. What is the proportion of boys to girls in the country?

    The correct conclusion is "probably pretty close to 50-50," but the path to the conclusion is far more interesting and important than the conclusion itself.

    This question interests me (though I would never use it as an interview question) because there are many ways to get the answer.  There are many different kinds of assumptions that are entailed by the problem statement, and the mathematical rigor of the solution can vary tremendously.

    First, check out Business Insider's answer, which is perfectly adequate.  The approach is to solve the more specific problem "... when there are 10 families, and they all start at once."  (This is an excellent approach, recommended [among others] by the famous mathematician George Polya.)

    This one caused quite the debate, but we figured it out following these steps:

    • Imagine you have 10 couples who have 10 babies. 5 will be girls. 5 will be boys. (Total babies made: 10, with 5 boys and 5 girls)
    • The 5 couples who had girls will have 5 babies. Half (2.5) will be girls. Half (2.5) will be boys. Add 2.5 boys to the 5 already born and 2.5 girls to the 5 already born. (Total babies made: 15, with 7.5 boys and 7.5 girls.)
    • The 2.5 couples that had girls will have 2.5 babies. Half (1.25) will be boys and half (1.25) will be girls. Add 1.25 boys to the 7.5 boys already born and 1.25 girls to the 7.5 already born. (Total babies: 17.5 with 8.75 boys and 8.75 girls).
    • And so on, maintianing a 50/50 population.

    This is a good back-of-the-envelope calculation, but it talks about things like "2.5 babies" which is silly and "and so on, maintaining a 50/50 population" which begs the question.  Nevertheless, it's a good answer for a short interview.

    Before I get to my own solutions, a digression on another related problem:

    Two trains are heading toward each other on the same track.  They are 120 miles apart (as the track is laid) at time t = 0.  Each train is moving at 30 mph.

    At time t = 0 a fly takes off from the front of the first train and starts flying toward the second train, following the track, at 60 mph (yes, this fly is faster than the train.)  As soon as it reaches the second train it turns around (losing no time on the turnaround, and without being crushed on the windshield) and starts back toward the first train.  When it reaches the first train again it turns around and heads back toward the second train again, and so on, back and forth, until the trains collide and the poor creature's miserable existence is mercifully ended between them.

    The question is: how many miles did the fly's odometer advance between time t = 0 and the collision?  (I know, flies don't usually have odometers. This fly does.  He's a very peculiar fly.  But you knew that already.)

    There are two canonical ways to answer this question.  One way is to start calculating from the fly's point of view:

    Well, the fly F starts at x = -60 and heads toward train T2 at 60 mph... but the train is heading toward the origin at 30 mph... so I solve the system of two linear equations F(t) = -60 + 60t and T2(t) = 60 - 30t and I get the point of intersection is at x = 20 (t = 1 1/3 but this is less relevant.)  So the fly goes 60 + 20 = 80 miles on the first leg.  Then he turns around and heads back toward T1, which by this time is at x = -20... so I solve the new system of two linear equations and I get the point of intersection is at x = -6 2/3.  So the fly goes 26 2/3 miles on the second leg, or 80 + 26 2/3 so far.  Now I do the third leg... hmm, it looks like the distance he goes on each leg is 1/3 the distance of the leg before, that's interesting... I wonder if I can prove it... [proof elided]... OK, it boils down to:

    ... and I know the geometric series formula...

    for |r| < 1 (Geometric series theorem.)

    ... if I pull out the 80 and substitute r = 1/3 I get...

    which comes out to... 120 miles!

    This is absolutely correct.  But the other way is to back off a step and look at it from the director's point of view.

    OK, the trains start at time t = 0 and they head toward each other at 30 miles per hour each.  So the distance between them is getting smaller at a rate of 60 miles per hour.  There's 120 miles between them at the start, so the crash will happen at time t = 2 hours.  The little fly is going at a constant 60 miles per hour throughout those two hours (poor little fella) so he'll have racked up exactly 120 miles of travel at the time of the collision.  I don't know how far he goes on any given leg (and I don't particularly care.)

    Legend has it that legendary mathematician John von Neumann was asked this question during class by a student eager to show him up.  Von Neumann thought for a few moments and came up with the correct answer.

    Student (somewhat embarrassed:) Darn... I was hoping you wouldn't see the shortcut, and you'd have to sum the series.

    Von Neumann (puzzled:) I did sum the series.

    Student: (awed silence)

    Heh.  Gets me every time.

    Anyway, back to boys and girls.  The "easy" way to solve this is to think of it from the point of view of the hospital maternity ward:

    Every day, women come in and give birth.  The midwife doesn't know (or care) how many siblings the newborn has, and nothing in the problem statement changes the assumption that the midwife is going to see a (roughly) equal number of boys and girls being delivered.  So the ratio of boys to girls is (roughly) 50-50.

    Changing the point of view from the family to the hospital reveals that almost all of the information in the problem statement was extraneous:

    In a country in which people only want boys, every family continues to have children until they have a boy. If they have a girl, they have another child. If they have a boy, they stop. What is the proportion of boys to girls in the country?

    All well and good.  But let's try the summing-the-series way (I'm a glutton for punishment.)  We'll start by figuring out how many boys there are, and then figure out how many girls there are, and then calculate the ratio directly.  This way we'll actually use all the information in the problem.

    -- How many boys are there? --

    Well, the number of families in the country is not given.  Let's call it F, and we'll only count families that actually have children.  About half of the families would have had a boy first... that's F / 2 boys.  The remainder would have tried again.  About half of these would have gotten a boy on the second try... that's F / 4 boys.  The remainder would have tried again.  About half of these would have gotten a boy on the third try... that's F / 8 boys.

    At this point we can use an inductive argument to say that for any n from 1 to infinity, there are F / 2n boys that result from the a family getting a boy on the nth try.  Key to this inductive argument are the perfectly natural assumptions that a family can support arbitrarily many children, and that a woman is capable of generating children arbitrarily quickly.

    The total number of boys B can then be calculated as:

    (Note the use of the geometric series formula again.)

    That is to say, there are precisely as many boys as there are families.  Which makes sense because each family has one boy.  Hmm, perhaps there was an easier way to solve this part...

    -- How many girls are there? --

    The F / 2 families that got a boy first shot out of the box have 0 girls: 0 * F / 2.

    The F / 4 families that got a boy on the second try have 1 girl each: 1 * F / 4.

    The F / 8 families that got a boy on the third try have 2 girls each: 2 * F / 8.


    The total number of girls G is thus:

    ... um...

    It's not immediately clear how to sum the series.  An elegant way is to use a trick worth remembering:

    I am assuming both |x| < 1 (for convergence) and x ≠ 0 (for convenience.)

    Differentiate (this is the trick:)

    Multiply both sides by x:

    Now substitute x = 1/2:

    Back to the calculation:

    Heh.  The total number of girls is also equal to the total number of families... they're just distributed differently (F / 2 families have no girls; F / 4 have one; F / 8 have two; F / 16 have three, etc.)

    -- What is the ratio of boys to girls? --

    Since B = G = F, the ratio B / G = 1.


    All this beautiful analysis to the contrary, the answer is actually wrong.  More boys are born every year than girls (at a ratio of about 106 to 100), and this is countered gradually due to a higher mortality rate among boys than girls (there are fewer girls born every year but they have a higher life expectancy.)  The cutoff age is 36... below this age there are more men than women, above that age there are more women than men.

  • Matthew van Eerde's web log

    Pirates of Penzance puzzle - what year is it?


    The setting of today's blog post is The Pirates of Penzance, Gilbert and Sullivan's musical about making sure you read the fine print.

    The scene opens with a birthday celebration for Frederic, who has just turned 21 and completed his pirate apprenticeship.

    SAM.    For today our pirate 'prentice
            Rises from indenture freed;
        Strong his arm, and keen his scent is
            He's a pirate now indeed!

    ALL.    Here's good luck to Frederic's ventures!
            Frederic's out of his indentures.

    SAM.    Two and twenty, now he's rising,
            And alone he's fit to fly,
        Which we're bent on signalizing
            With unusual revelry.
    KING.  Yes, Frederic, from to-day you rank as a full-blown member of our band.

    Frederic, however, is a fine upstanding young man with no taste for piracy; he served this long only because he was contractually obligated.

    FRED.  To-day I am out of my indentures, and to-day I leave you for ever.
    SAM.  We don't seem to make piracy pay.  I'm sure I don't know why, but we don't.
    FRED.  I know why, but, alas! I mustn't tell you; it wouldn't be right.
    KING.  Why not, my boy?  It's only half-past eleven, and you are one of us until the clock strikes twelve.

    Reinforcement of the fact that (as of today) Fred is now twenty-one:

    FRED.  Ruth, I will be quite candid with you.  You are very dear to me, as you know, but I must be circumspect. You see, you are considerably older than I. A lad of twenty-one usually looks for a wife of seventeen.

    Fred is well on his way to wiping out his former employers when they track him down and point out a technicality in the contract:


    For some ridiculous reason, to which, however, I've no desire to be disloyal,
    Some person in authority, I don't know who, very likely the Astronomer Royal,
    Has decided that, although for such a beastly month as February, twenty-eight days as a rule are plenty,
    One year in every four his days shall be reckoned as nine and twenty.
    Through some singular coincidence -- I shouldn't be surprised if it were owing to the agency of an ill-natured fairy --
    You are the victim of this clumsy arrangement, having been born in leap-year, on the twenty-ninth of February;
    And so, by a simple arithmetical process, you'll easily discover,
    That though you've lived twenty-one years, yet, if we go by birthdays, you're only five and a little bit over!
    FRED.  (more amused than any)  How quaint the ways of Paradox!
        At common sense she gaily mocks!
        Though counting in the usual way,
            Years twenty-one I've been alive,
        Yet, reckoning by my natal day,
            I am a little boy of five!

    RUTH and KING.    He is a little boy of five! Ha! ha! ha!
    FRED.  Upon my word, this is most curious -- most absurdly whimsical. Five-and-a-quarter!  No one would think it to look at me!
    RUTH.  You are glad now, I'll be bound, that you spared us. You would never have forgiven yourself when you discovered that you had killed two of your comrades.
    FRED.  My comrades?
    KING.  (rises)  I'm afraid you don't appreciate the delicacy of your position:   You were apprenticed to us --
    FRED.  Until I reached my twenty-first year.
    KING.  No, until you reached your twenty-first birthday (producing document), and, going by birthdays, you are as yet only five-and-a-quarter.

    Shocked, Fred goes to confess to his sweetheart Mabel, portrayed in this picture by Marion Hood:

    FRED.    No, Mabel, no.  A terrible disclosure
        Has just been made. Mabel, my dearly-loved one,
        I bound myself to serve the pirate captain
        Until I reached my one-and-twentieth birthday --
    MABEL.    But you are twenty-one?
    FRED.        I've just discovered
        That I was born in leap-year, and that birthday
        Will not be reached by me till nineteen forty!

    MABEL.    Oh, horrible!  Catastrophe appalling!

    This brings us to the point.  Calculate the following:

    1. In what year was Frederic born?
    2. With as much accuracy as possible, specify the date and time of the opening scene.
  • Matthew van Eerde's web log

    Newton: combining celestial and terrestrial mechanics


    For as long as there have been humans, we have looked at the skies - and around us on Earth as well.  Things move in the skies; things move on Earth.  The way things move in the skies is called "celestial mechanics"; the way things move on Earth is called "terrestrial mechanics."

    The ancient Greeks thought there were five elements, corresponding to the five regular (Platonic) solids: the four terrestrial elements (air, fire, earth, and water) and the fifth celestial element which the Romans called quintessence.

    All kinds of rules were discovered/proposed for how things move on Earth.  Galileo demonstrated that falling objects accelerate at the same rate regardless of their mass.  Similar rules were discovered/proposed for how things move in the heavens.  Kepler demonstrated that the planets move in ellipses around the sun, that they swept out equal areas in equal times, etc.  But everybody knew that things in the heavens were different than they were on Earth.

    Then Newton, building on Hooke, came up with the crazy idea that perhaps things fall on Earth for the same reason that heavenly objects go in ellipses... that all objects, celestial or terrestrial, act on each other from a distance in a uniform fashion.  (He himself wrote that this was such a crazy idea that no-one should take it seriously.)

    That one body may act upon another at a distance through a vacuum without the mediation of anything else, by and through which their action and force may be conveyed from one another, is to me so great an absurdity that, I believe, no man who has in philosophic matters a competent faculty of thinking could ever fall into it. -- Isaac Newton

    But he had done some calculations and it seemed to work out... specifically, he worked out how much acceleration you would expect the Earth to exert on an object as far away as the Moon, and how much acceleration the Moon would need to stay in its orbit (and not wander off or spiral down into the Earth.)

    I... compared the force requisite to keep the moon in her orb with the force of gravity at the surface of the earth and found them to answer pretty nearly. -- Isaac Newton

    Note Newton's use of the word "orb" to describe the motion of the moon, a direct reference to celestial mechanics.

    Let's see how compelling those calculations were, shall we?

    His first idea was that there was this thing called "force" which was equal to the mass of an object multiplied by the acceleration it was undergoing.  F = m a.  Fine.

    His second idea (which he stole, at least partially, from Hooke) was that any two objects have a force that attracts them, which is proportional to both of their masses, and inversely proportional to the square of the distance between them (that's the part he stole from Hooke.)  F = G m1 m2 / r2.  Fine.

    His third idea was that an object that goes around a circle of radius r at a constant speed v is undergoing a constant centripetal acceleration of a = v2 / rThis follows pretty quickly from the definitions with a little geometry, trigonometry, and a dash of calculus.  Fine.

    He also knew some facts.  I'm going to state them in metric because, hey, this is the twenty-first century.  He knew:

    • The distance from the center of the Earth to the surface (where the apple trees are) is 3,960 miles: 6370 km.
    • Falling objects on the surface of the Earth accelerate at 32.2 ft/s2: 9.81 m/s2.
    • The distance from the center of the Earth to the center of the Moon is 239,000 miles: 384,000 km.
    • The Moon takes 27.3 days to make a single sidereal orbit around the Earth: 236,000 s.

    Coarse estimate time...

    If the Moon is roughly 50 times further away from the center of the Earth than an apple is, and "gravitational" acceleration is inversely proportional to the square of the distance between two objects, then the Moon should be accelerated roughly 1/2500th as much as the apple is: 0.004 m/s2 instead of 10 m/s2,

    More precisely...

    Consider the apple case (mE is the mass of the Earth, mA the mass of the apple:)

    FA = mA aA = G mE mA / rA2
    A rA2 = G mE
    9.81 m/s2 (6.37e6 m)2 = G mE
    mE = 3.98e14 m3/s2

    Now consider the moon case:

    FM = mM aM = G mE mM / rM2
    aM = G mE / rM2
    aM = 3.98e14 m3/s2 / (3.84e5 m)2
    aM = 0.00269 m/s2

    So Newton's proposed formula predicts that the Earth's gravity causes the Moon to accelerate at 0.00269 m/s2.  This is, indeed, roughly 0.004 m/s2 so it jives with our coarse estimate.

    Let's see how that compares to the necessary centripetal acceleration to achieve a closed orbit: a = v2 / r.

    The distance the moon travels in its sidereal orbit is 2π * 3.84e5 m = 2.42e9 m, and it does so in 27.3 days; so its velocity is 2π * 3.84e5 m / (27.3 days * 24 (hours / day) * 60 (minutes / hour) * 60 (s / minute)) = 1020 m/s.  This is roughly Mach 3 in our atmosphere.

    The centripetal acceleration is thus measured to be (1020 m/s)2 / 3.84e8 m = 0.00273 m/s2. Newton's prediction is about 1.5% off, which isn't bad, considering.

    Note that although Newton knew the value of the product G mE, he didn't know either G or mE seperately.  It wasn't until Cavendish took two known masses and measured the gravitational pull between them directly that G was evaluated; we could then calculate the mass of the earth as G mE / G.

    Once G was known we could also evaluate the mass of the Sun based on Earth's orbit.  However, we still could not determine the mass of the Moon... in fact, back in the 1950s, American scientists were unable to determine the mass of Sputnik (which would have been very helpful) even though we could see its orbit.

    Which raises the question... how did we know the mass of the Moon?

  • Matthew van Eerde's web log

    Blown movie lines - Torn Curtain


    In the underrated movie Torn Curtain, American scientists Michael Armstrong (Paul Newman) and Sarah Louise Sherman (Julie Andrews) meet up with East German scientist Gustav Lindt (the excellent actor Ludwig Dorath) to pump him for a secret formula (the MacGuffin.)

    They're at an upscale party and a waltz is playing.  Lindt is a little tipsy and enjoying himself, and he says:

    Lindt: The Vienna Waltz. Did I tell you my sister Emily was knocked down by a tram in Vienna? (sings along to the good bit - about 0:54 in the link below.)

    Why is this a blown line?

    Because there is no such waltz. There are various waltzes with "Vienna" in the title, but the particular waltz in question is the Emperor Waltz.  It would indeed be unfortunate if poor Emily was knocked down by the Emperor...

    Hitchcock directed a film about Johann Strauss, Jr.: Waltzes from Vienna, and Ludwig Donath was himself Austrian, so this mistake is surprising.

    As an aside, it was this movie (in particular the blackboard scene) that stimulated my youthful interest in mathematics.  A couple of people arguing over weird chalk symbols and one of them says "it would blow up!" - that got my attention.
  • Matthew van Eerde's web log

    How to turn on HDCP or SCMS in an audio playback app


    There's an MSDN sample of how to turn on HDCP or SCMS in a playback app.  The sample is loosely based on a test app I wrote, but there are still some rough edges.  For example, the CMFAttributeImpl<T> template is not part of the SDK or the DDK.  Also, there's a leak in the GetDigitalAudioEndpoint implementation.  (Using the private template was my bug.  The leak was someone else's.)

    Exercise: find the leak.

    There are also rough edges in the test app.  For one thing, it doesn't play audio - the way to use the app is to

    1. Start playing audio using your favorite audio playback application, to the digital output on which want to turn on HDCP or SCMS.
    2. Run the trustedaudiodrivers2.exe command line to do what you want it to.

    Source and binaries (x86 and amd64) attached.

    trustedaudiodrivers2 --list-devices

        --device [ communications | console | multimedia | <device-name>]
        --copy-ok [ 0 | 1 ]
        --digital-output-disable [ 0 | 1 ]
        --test-certificate-enable [ 0 | 1 ]
        --drm-level <drm-level>

    --list-devices: displays a list of output devices.

    Sets a Trusted Audio Drivers 2 output policy on a given audio endpoint.
        communications: set policy on default communications render endpoint.
        console: set policy on default console render endpoint.
        multimedia: set policy on default multimedia render endpoint.
        <device-name>: set policy on the endpoint with this long name.
    --copy-ok: 1 or 0. Set to 0 to turn on "copy protection" (SCMS or HDCP)
    --digital-output-disable: 1 or 0. Set to 1 to disable output (or turn on HDCP.) --test-certificate-enable: 1 or 0. Set to 1 to allow the test certificate.
    --drm-level: set to a number. 1100 is a good default.

    Here's a sample command line which should disable the digital out (or enable HDCP, if the driver supports it.)  This is line-wrapped for readability only.

        --device "Acer H213H (High Definition Audio Device)"
        --copy-ok 1
        --digital-output-disable 1
        --test-certificate-enable 1
        --drm-level 1300

    If all goes well the output should look something like this:

    Output Trust Authorities on this endpoint: 2
    Processing Output Trust Authority #1 of 2
    OTA action is PEACTION_PLAY (1)
    GetOriginatorID() called
    GetOriginatorID() called
    GenerateRequiredSchemas() called
    dwAttributes: MFOUTPUTATTRIBUTE_DIGITAL (0x00000001)
    guidOutputSubType: MFCONNECTOR_HDMI ({57CD596D-CE47-11D9-92DB-000BDB28FF98})
    cProtectionSchemasSupported: 1
    MFPROTECTION_TRUSTEDAUDIODRIVERS ({65BDF3D2-0168-4816-A533-55D47B027101})
    IMFOutputSchema::GetSchemaType called
    IMFOutputSchema::GetConfigurationData called
    IMFOutputTrustAuthority::SetPolicy returned 0x00000000
    Processing Output Trust Authority #2 of 2
    OTA action is PEACTION_EXTRACT (4)
    Skipping as the OTA action is not PEACTION_PLAY
    Policy successfully applied.  Press any key to release IMFTrustedOutput...

    At that point I pressed a key, which redacted the policy, and the music started playing again.

    Common causes of failures:

    1. No audio is playing to that device.  Setting protection requires an active audio stream.
    2. The audio device is not S/PDIF or HDMI.
    3. The audio driver is not capable of enforcing the protection.
    4. The audio driver is test-signed only; try --test-certificate-enable 1
    5. Kernel debugging is enabled.
    6. Driver verifier is enabled.

    This source should compile with just publically available headers and no special voodoo on the part of the developer.

    EDIT: 11/13/2009 10:05 AM

    A subtle problem was called to my attention.  This line triggers an ATL ASSERT:

    hr = pMFOutputTrustAuthority->SetPolicy(&pMFOutputPolicy, 1, &pbTicket, &cbTicket);

    The ASSERT is in CComPtr<IMFOutputPolicy>::operator& which ASSERTs if the pointer CComPtr<IMFOutputPolicy>::p is not NULL.

    My first instinct was to bypass the assertion like this:

    hr = pMFOutputTrustAuthority->SetPolicy(&pMFOutputPolicy.p, 1, &pbTicket, &cbTicket);

    But ASSERTs are there for a reason.  The fundamental problem here is that I'm trying to be too clever.

    Despite its name, IMFOutputTrustAuthority::SetPolicy takes, not an IMFOutputPolicy *, but an array of IMFOutputPolicy *s.  A little confusing, yes.  As this is effectively a sample, I should be emphasizing this behavior, not covering it up.

    So here's the fix I decided on.  Updated source and binaries attached.

    // we're only setting a single policy but the API allows setting multiple policies
    // (like WaitForMultipleObjects)
    IMFOutputPolicy *rMFOutputPolicies[1] = { pMFOutputPolicy };
    hr = pMFOutputTrustAuthority->SetPolicy(


  • Matthew van Eerde's web log

    Forcing Windows to install on a single partition


    If you're installing Windows via a boot DVD, and you choose Custom, you have the option to rearrange partitions.  I like use this to have each drive be one big partition.

    Windows 7 wants to set aside a 100 MB partition for something-or-other.  I'm sure there's a very good reason for this but I am too lazy to look up the team that owns this space and ask them what it is.

    So I'm in the "Where do you want to install Windows?" stage, I've gone into "advanced drive setup", and I've deleted all the partitions.  Fine.  I then create a partition that fills the drive, and I get this popup:

    Install Windows
    To ensure that all Windows features work correctly, Windows might create additional partitions for system files.
    OK | Cancel

    After letting Windows finish installing, I jump into diskpart.exe and sure enough, Windows has created an additional partition.  A small one, to be sure... but an additional partition (horrors!)

    Not being one to let Windows push me around, I decided to experiment, and came up with the following dance to allow me to just have one big partition thankyouverymuch:

    1. Boot from the Windows DVD
    2. Choose Custom (advanced) as opposed to Upgrade
    3. Go into Drive options (advanced)
    4. Delete all partitions on the drive
    5. Create a new partition - you will get the prompt above
    6. Click OK
    7. There will now be two partitions - a small (System) one and a large (Primary) one.
    8. Delete the large one.
    9. Extend the new one to fill the drive.
    10. Install Windows.
    11. Open Windows Explorer
    12. Right-click the C: drive | Properties
    13. Delete the "System Reserved" partition name
    Et voilà, Windows installs perfectly happily on the single partition (confirmed with diskpart.exe post-installation.)
Page 3 of 6 (138 items) 12345»