Matthew van Eerde's web log

  • Matthew van Eerde's web log

    Sample - WASAPI loopback capture (record what you hear)

    • 95 Comments

    In a previous post I showed how to play silence to a given audio device and hinted at a possible application.

    Attached to this post is a sample WASAPI loopback capture app - amd64, x86 and source included.  This allows you to record the sound that is coming out of your speakers:

    >loopback-capture -?
    loopback-capture -?
    loopback-capture --list-devices
    loopback-capture [--device "Device long name"] [--file "file name"] [--int-16]

        -? prints this message.
        --list-devices displays the long names of all active playback devices.
        --device captures from the specified device (default if omitted)
        --file saves the output to a file (loopback-capture.wav if omitted))
        --int-16 attempts to coerce data to 16-bit integer format

    There are a couple of oddities for WASAPI loopback capture.  One is that "event mode" doesn't work for loopback capture; you can call pAudioClient->Initialize(... AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK, ... ), you can call pAudioClient->SetEventHandle(...), and everything will succeed... but the "data is ready" event will never fire.  So this app creates its own waitable timer.

    Another oddity is that WASAPI will only push data down to the render endpoint when there are active streams.  When nothing is playing, there is nothing to capture.

    For example, play a song, and then run loopback-capture.  While loopback-capture is running, stop the song, and then start it again.  You'll get this output when you start it back up:

    >loopback-capture
    Press Enter to quit...
    IAudioCaptureClient::GetBuffer set flags to 0x00000001 on pass 5381 after 1088829 frames

    Thread HRESULT is 0x8000ffff

    The flag in question is AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY.  When the song stopped, no more data was available to capture.  Eventually the song started up again, and WASAPI dutifully reported that there was a glitch detected.  This app stops on glitches.

    There are a couple of other possible ways to handle this.  One way is to ignore glitches; then if you stop a song, wait a few seconds, and start it again, then the recorded signal will omit the wait and abut the two "audio is playing" portions.

    But my particular favorite way of handling this is to run silence.exe.  That way there are never any "nothing is playing" glitches, because there's always something playing.

    EDIT 11/23/2009: Updated loopback-capture.exe to ignore the glitch flag on the first packet, since Windows 7 sets it.  Also improved the interaction between the capture thread bailing out and the user pressing Enter to finish.

  • Matthew van Eerde's web log

    Deriving the centripetal acceleration formula

    • 25 Comments

    A body that moves in a circular motion (of radius r) at constant speed (v) is always being accelerated.  The acceleration is at right angles to the direction of motion (towards the center of the circle) and of magnitude v2 / r.

    The direction of acceleration is deduced by symmetry arguments.  If the acceleration pointed out of the plane of the circle, then the body would leave the plane of the circle; it doesn't, so it isn't.  If the acceleration pointed in any direction other than perpendicular (left or right) then the body would speed up or slow down.  It doesn't.

    Now for the magnitude.  Consider the distance traveled by the body over a small time increment Δt:

    http://blogs.msdn.com/photos/matthew_van_eerde/images/9952566/original.aspx

    We can calculate the arc length s as both the distance traveled (distance = rate * time = v Δt) and using the definition of a radian (arc = radius * angle in radians = r Δθ:)

    http://blogs.msdn.com/photos/matthew_van_eerde/images/9952564/original.aspx

    The angular velocity of the object is thus v / r (in radians per unit of time.)

    The right half of the diagram is formed by putting the tails of the two v vectors together.  Note that Δθ is the same in both diagrams.

    http://blogs.msdn.com/photos/matthew_van_eerde/images/9952565/original.aspx

    Note the passing from sin to cos is via l'Hôpital's rule.

    QED.

  • Matthew van Eerde's web log

    Disney princesses: an attempt at a complete list

    • 24 Comments

    Last time we examined the "Disney princess" status of Belle from Beauty and the Beast. This time I attempt to give an exhaustive list of all Disney princesses together with a very short summary of their status.

    Snow White and the Seven Dwarfs (1937)

    Snow White
    Queen's Daughter; Queen? Prince's Girlfriend; Prince's Wife?
    Disney Princess

    Bambi (1942)

    Faline
    Prince's Wife
    Disney Princess
    Bambi's Mother
    Prince's Wife
    Disney Princess

    Cinderella (1950)

    Cinderella
    Prince's Wife
    Disney Princess

    Peter Pan (1953)

    Tiger Lily
    Chief's Daughter
    Disney Princess

    Sleeping Beauty (1959)

    Aurora
    King's Daughter; Prince's Girlfriend; Prince's Wife?
    Disney Princess

    Robin Hood (1973)

    Maid Marian
    Wealthy; Prince(?)'s Girlfriend
    Not a princess

    Nausicaä of the Valley of Wind (1984: Studio Ghibli1)

    Nausicaä
    King's Daughter; Queen?
    Disney(?) Princess
    Kushana
    Royalty's Daughter
    Disney(?) Princess
    Lastelle
    Queen's Daughter
    Disney(?) Princess

    The Black Cauldron (1985)

    Eilonwy
    Queen's Daughter; Prince(?)2's Girlfriend(?)
    Disney Princess

    Castle in the Sky (1986: Studio Ghibli)

    Sheeta
    Queen's Daughter; Queen?
    Disney(?) Princess

    Oliver & Company (1988)

    Jenny
    Wealthy
    Not a princess

    The Little Mermaid (1989)

    Ariel
    King's Daughter; Queen's Daughter?3 Prince's Wife
    Disney Princess
    Aquata, Andrina, Arista, Attina, Adella, Alana
    King's Daughter; Queen's Daughter?3 Queen?4
    Disney Princesses

    Beauty and the Beast (1991)

    Belle
    Prince's Girlfriend; Prince's Wife?5
    Disney Princess?

    Aladdin (1992)

    Jasmine
    Sultan's Daughter; Prince's Girlfriend?6 Sultan's Girlfriend?7
    Disney Princess

    The Lion King (1994)

    Nala
    King's Daughter8; Prince's Girlfriend; King's Girlfriend; King's Wife
    Disney Princess
    Kiara
    King's Daughter9
    Disney Princess?

    Pocahontas (1995)

    Pocahontas
    Chief's Daughter
    Disney Princess

    Princess Mononoke (1997: Studio Ghibli)

    San (also known as Princess Mononoke)
    Wolf God's Adopted Daughter
    Disney(?) Princess
    Kaya
    Prince's Sister
    Disney(?) Princess

    A Bug's Life (1998: Pixar)

    Dot
    Queen's Daughter
    Disney(?) Princess
    Atta
    Queen's Daughter; Queen
    Disney(?) Princess

    Atlantis: The Lost Empire (2001)

    Kida
    King's Daughter; Queen
    Disney Princess

    Tales from Earthsea (2006: Studio Ghibli)

    Therru (true name Tehanu)
    Prince's Girlfriend
    Not a princess

    Ponyo (2008: Studio Ghibli)

    Ponyo
    Queen's Daughter
    Disney(?) Princess

    The Princess and the Frog (2009)

    Tiana
    Prince's Wife
    Disney Princess
    Charlotte
    King's Daughter10
    Disney Princess

    Tangled (2010)

    Rapunzel
    King & Queen's Daughter
    Disney Princess

    Brave (2012)

    Merida
    King & Queen's Daughter
    Disney Princess

    Wreck-it Ralph (2012)

    Vanellope von Schweetz
    Princess
    Disney Princess

    Frozen (2013)

    Anna
    King & Queen's Daughter; Queen?11
    Disney Princess
    Elsa
    King & Queen's Daughter; Queen
    Disney Princess

    Methodology - I started with Disney's own "50 animated features" list as present on the Tangled DVD. To this I added the list of Pixar and (somewhat more controversially) Studio Ghibli movies released under the Disney name.

    1 Nausicaä of the Valley of Wind is technically not a Studio Ghibli film as the studio was not founded until after the film was released.
    2 In The Black Cauldron, Taran is not yet aware that he is a prince.
    3 In The Little Mermaid, Ursula briefly has a claim to being a queen. Her relationship to Triton is never made clear; if she is his ex-wife, Ariel and her sisters are potentially her daughters.
    4 In The Little Mermaid, Triton is briefly incapacitated. His eldest daughter thus has a brief claim to being Queen.
    5 In Beauty and the Beast, Belle's princess status hinges on whether she is married to the Beast.
    6 In Aladdin, Aladdin spends a fair amount of time transformed into a prince, though it is not clear where his principality lies.
    7 In Aladdin, Jafar is briefly Sultan and attempts to woo Jasmine, though there is no reciprocity in their relationship.
    8 In The Lion King it is perhaps best not to inquire too closely into the precise nature of the familial relationships between members of the pride in question.
    9 The Lion King ends with a shot of Simba and Nala's cub being presented, but it is not clear whether the cub is male or female; in sequels it is made clear that the cub is a girl and her name is Kiara.
    10 Charlotte's father is King of Mardi Gras, so Charlotte is princess for only one day. Still, Once a Princess, Always a Princess.
    11 When Elsa abandons Arendelle, there is a case to be made that Anna is de facto Queen. Anna herself would dispute this claim.

  • Matthew van Eerde's web log

    Sample - WASAPI exclusive-mode event-driven playback app, including the HD Audio alignment dance

    • 19 Comments

    Attached to this post is a sample WASAPI exclusive-mode event-driven playback app, including amd64 and x86 binaries, source, and a modification of the ac3.wav Dolby Digital test tone to include a "fact" chunk.

    >play-exclusive.exe -?
    play-exclusive.exe -?
    play-exclusive.exe --list-devices
    play-exclusive.exe [--device "Device long name"] --file "WAV file name"

        -? prints this message.
        --list-devices displays the long names of all active playback devices.

    Plays the given file to the given device in WASAPI exclusive mode.
    If no device is specified, plays to the default console device.

    On the particular system I used to test this, these are the devices I have:

    >play-exclusive.exe --list-devices
    Active render endpoints found: 3
        Digital Audio (S/PDIF) (2- High Definition Audio Device)
        Speakers (2- High Definition Audio Device)
        Sceptre (High Definition Audio Device)

    And this is the output I get when I play the attached ac3.wav test tones to the Sceptre HDMI output:


    >play-exclusive --device "Sceptre (High Definition Audio Device)" --file ac3.wav
    Opening .wav file "ac3.wav"...
    The default period for this device is 30000 hundred-nanoseconds, or 144 frames.
    Buffer size not aligned - doing the alignment dance.
    Trying again with periodicity of 33333 hundred-nanoseconds, or 160 frames.
    We ended up with a period of 33333 hns or 160 frames.

    Successfully played all 460800 frames.

    A word on the "alignment dance" highlighted above... first, this scene from The Pacifier.  (Vin Diesel is so coordinated.)

    The Pacifier: The Peter Panda dance

    Here's the source for the dance (in play.cpp in the attached.)

    // call IAudioClient::Initialize the first time
    // this may very well fail
    // if the device period is unaligned
    hr = pAudioClient->Initialize(
        AUDCLNT_SHAREMODE_EXCLUSIVE,
        AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
        hnsPeriod, hnsPeriod, pWfx, NULL
    );
    // if you get a compilation error on AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED,
    // uncomment the #define below
    //#define AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED AUDCLNT_ERR(0x019)
    if (AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED == hr) {

        // if the buffer size was not aligned, need to do the alignment dance
        printf("Buffer size not aligned - doing the alignment dance.\n");
       
        // get the buffer size, which will be aligned
        hr = pAudioClient->GetBufferSize(&nFramesInBuffer);
        if (FAILED(hr)) {
            printf("IAudioClient::GetBufferSize failed: hr = 0x%08x\n", hr);
            return hr;
        }
       
        // throw away this IAudioClient
        pAudioClient->Release();

        // calculate the new aligned periodicity
        hnsPeriod = // hns =
            (REFERENCE_TIME)(
                10000.0 * // (hns / ms) *
                1000 * // (ms / s) *
                nFramesInBuffer / // frames /
                pWfx->nSamplesPerSec  // (frames / s)
                + 0.5 // rounding
            );

        // activate a new IAudioClient
        hr = pMMDevice->Activate(
            __uuidof(IAudioClient),
            CLSCTX_ALL, NULL,
            (void**)&pAudioClient
        );
        if (FAILED(hr)) {
            printf("IMMDevice::Activate(IAudioClient) failed: hr = 0x%08x\n", hr);
            return hr;
        }

        // try initialize again
        printf("Trying again with periodicity of %I64u hundred-nanoseconds, or %u frames.\n", hnsPeriod, nFramesInBuffer);
        hr = pAudioClient->Initialize(
            AUDCLNT_SHAREMODE_EXCLUSIVE,
            AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
            hnsPeriod, hnsPeriod, pWfx, NULL
        );

        if (FAILED(hr)) {
            printf("IAudioClient::Initialize failed, even with an aligned buffer: hr = 0x%08x\n", hr);
            pAudioClient->Release();
            return hr;
        }
    } else if (FAILED(hr)) {
        printf("IAudioClient::Initialize failed: hr = 0x%08x\n", hr);
        pAudioClient->Release();
        return hr;
    }

    // OK, IAudioClient::Initialize succeeded
    // let's see what buffer size we actually ended up with
    hr = pAudioClient->GetBufferSize(&nFramesInBuffer);
    if (FAILED(hr)) {
        printf("IAudioClient::GetBufferSize failed: hr = 0x%08x\n", hr);
        pAudioClient->Release();
        return hr;
    }

    // calculate the new period
    hnsPeriod = // hns =
        (REFERENCE_TIME)(
            10000.0 * // (hns / ms) *
            1000 * // (ms / s) *
            nFramesInBuffer / // frames /
            pWfx->nSamplesPerSec  // (frames / s)
            + 0.5 // rounding
        );

     

     

    Note the new HRESULT. 

    HD Audio works on a 128-byte aligned buffer size.  This dance ensures that the HD Audio driver is being fed data in chunks of 128 bytes.  It is somewhat complicated by the fact that IAudioClient::Initialize takes a parameter of hundred-nano-seconds, but IAudioClient::GetBufferSize sets a parameter of frames.

  • Matthew van Eerde's web log

    How to enumerate audio endpoint (IMMDevice) properties on your system

    • 12 Comments

    Source and binaries (amd64 and x86) attached.

    Pseudocode:

    CoCreateInstance(..., &pMMDeviceEnumerator);
    pMMDeviceEnumerator->EnumAudioEndpoints(..., &pMMDeviceCollection);
    for (each device in the collection) {
        pMMDevice->OpenPropertyStore(...,  &pPropertyStore);
        for (each property in the store) {
            log the property
        }
    }

    Output on my system:

    >audioendpoints.exe
    ID: {0.0.0.00000000}.{6d531641-b3e3-43c5-ba56-ba165b4a9bb6}
    State: 4 (DEVICE_STATE_NOTPRESENT)
    -- Properties (18) --
    {b3f8fa53-0004-438e-9003-51a46e139bfc},15:
        VT_BLOB of size 16
        db 07 06 00 03 00 01 00
        17 00 3a 00 2d 00 26 03
    DEVPKEY_Device_DeviceDesc:
        VT_LPWSTR Internal AUX Jack
    {b3f8fa53-0004-438e-9003-51a46e139bfc},6:
        VT_LPWSTR High Definition Audio Device
    {b3f8fa53-0004-438e-9003-51a46e139bfc},2:
        VT_LPWSTR {1}.HDAUDIO\FUNC_01&VEN_8384&DEV_7690&SUBSYS_102801BD&REV_1022\4&33D044FE&0&0001
    {83da6326-97a6-4088-9453-a1923f573b29},3:
        VT_LPWSTR hdaudio.inf:Microsoft.ntamd64:HdAudModel:6.1.7600.16385::hdaudio\func_01
    DEVPKEY_Device_BaseContainerId:
        VT_CLSID {00000000-0000-0000-ffff-ffffffffffff}
    DEVPKEY_Device_ContainerId:
        VT_CLSID {00000000-0000-0000-ffff-ffffffffffff}
    DEVPKEY_Device_EnumeratorName:
        VT_LPWSTR HDAUDIO
    {b3f8fa53-0004-438e-9003-51a46e139bfc},1:
        VT_BLOB of size 72
        a8 7f a4 d5 98 6d d1 11
        a2 1a 00 a0 c9 22 31 96
        9c ac 97 dc ec dd 59 4d
        b6 50 3b 8b a6 7b c2 a1
        00 00 00 00 00 00 00 00
        00 00 00 00 00 00 00 00
        e0 cc 13 de 04 83 e9 4e
        ba ce 48 24 21 4e 3e a5
        00 00 02 00 01 00 00 00
    PKEY_AudioEndpoint_FormFactor:
        VT_UI4 10
    PKEY_AudioEndpoint_JackSubType:
        VT_LPWSTR {6994AD04-93EF-11D0-A3CC-00A0C9223196}
    DEVPKEY_DeviceClass_IconPath:
        VT_LPWSTR %windir%\system32\mmres.dll,-3018
    {840b8171-b0ad-410f-8581-cccc0382cfef},0:
        VT_BLOB of size 300
        01 00 00 00 28 01 00 00
        00 00 01 00 7b 00 32 00
        7d 00 2e 00 5c 00 5c 00
        3f 00 5c 00 68 00 64 00
        61 00 75 00 64 00 69 00
        6f 00 23 00 66 00 75 00
        6e 00 63 00 5f 00 30 00
        31 00 26 00 76 00 65 00
        6e 00 5f 00 38 00 33 00
        38 00 34 00 26 00 64 00
        65 00 76 00 5f 00 37 00
        36 00 39 00 30 00 26 00
        73 00 75 00 62 00 73 00
        79 00 73 00 5f 00 31 00
        30 00 32 00 38 00 30 00
        31 00 62 00 64 00 26 00
        72 00 65 00 76 00 5f 00
        31 00 30 00 32 00 32 00
        23 00 34 00 26 00 33 00
        33 00 64 00 30 00 34 00
        34 00 66 00 65 00 26 00
        30 00 26 00 30 00 30 00
        30 00 31 00 23 00 7b 00
        36 00 39 00 39 00 34 00
        61 00 64 00 30 00 34 00
        2d 00 39 00 33 00 65 00
        66 00 2d 00 31 00 31 00
        64 00 30 00 2d 00 61 00
        33 00 63 00 63 00 2d 00
        30 00 30 00 61 00 30 00
        63 00 39 00 32 00 32 00
        33 00 31 00 39 00 36 00
        7d 00 5c 00 65 00 6d 00
        69 00 63 00 69 00 6e 00
        74 00 6f 00 70 00 6f 00
        2f 00 30 00 30 00 30 00
        31 00 30 00 30 00 30 00
        30 00 00 00
    PKEY_AudioEndpoint_Association:
        VT_LPWSTR {00000000-0000-0000-0000-000000000000}
    PKEY_AudioEndpoint_Supports_EventDriven_Mode:
        VT_UI4 1
    DEVPKEY_Device_FriendlyName:
        VT_LPWSTR Internal AUX Jack (High Definition Audio Device)
    DEVPKEY_DeviceInterface_FriendlyName:
        VT_LPWSTR High Definition Audio Device
    PKEY_AudioEndpoint_GUID:
        VT_LPWSTR {6D531641-B3E3-43C5-BA56-BA165B4A9BB6}

    ID: {0.0.0.00000000}.{ca9fa848-1e60-401c-81e6-323546335d0a}
    State: 1 (DEVICE_STATE_ACTIVE)
    -- Properties (26) --
    {b3f8fa53-0004-438e-9003-51a46e139bfc},15:
        VT_BLOB of size 16
        da 07 0a 00 04 00 1c 00
        16 00 3a 00 3a 00 d1 00
    DEVPKEY_Device_DeviceDesc:
        VT_LPWSTR Speakers
    {b3f8fa53-0004-438e-9003-51a46e139bfc},6:
        VT_LPWSTR High Definition Audio Device
    {b3f8fa53-0004-438e-9003-51a46e139bfc},2:
        VT_LPWSTR {1}.HDAUDIO\FUNC_01&VEN_8384&DEV_7690&SUBSYS_102801BD&REV_1022\4&33D044FE&0&0001
    {83da6326-97a6-4088-9453-a1923f573b29},3:
        VT_LPWSTR hdaudio.inf:Microsoft.ntamd64:HdAudModel:6.1.7600.16385::hdaudio\func_01
    DEVPKEY_Device_BaseContainerId:
        VT_CLSID {00000000-0000-0000-ffff-ffffffffffff}
    DEVPKEY_Device_ContainerId:
        VT_CLSID {00000000-0000-0000-ffff-ffffffffffff}
    DEVPKEY_Device_EnumeratorName:
        VT_LPWSTR HDAUDIO
    {b3f8fa53-0004-438e-9003-51a46e139bfc},1:
        VT_BLOB of size 72
        a8 7f a4 d5 98 6d d1 11
        a2 1a 00 a0 c9 22 31 96
        9c ac 97 dc ec dd 59 4d
        b6 50 3b 8b a6 7b c2 a1
        00 00 00 00 00 00 00 00
        00 00 00 00 00 00 00 00
        e0 cc 13 de 04 83 e9 4e
        ba ce 48 24 21 4e 3e a5
        00 00 02 00 01 00 00 00
    PKEY_AudioEndpoint_FormFactor:
        VT_UI4 1
    PKEY_AudioEndpoint_JackSubType:
        VT_LPWSTR {DFF21CE1-F70F-11D0-B917-00A0C9223196}
    DEVPKEY_DeviceClass_IconPath:
        VT_LPWSTR %windir%\system32\mmres.dll,-3010
    {840b8171-b0ad-410f-8581-cccc0382cfef},0:
        VT_BLOB of size 324
        01 00 00 00 40 01 00 00
        00 00 01 00 7b 00 32 00
        7d 00 2e 00 5c 00 5c 00
        3f 00 5c 00 68 00 64 00
        61 00 75 00 64 00 69 00
        6f 00 23 00 66 00 75 00
        6e 00 63 00 5f 00 30 00
        31 00 26 00 76 00 65 00
        6e 00 5f 00 38 00 33 00
        38 00 34 00 26 00 64 00
        65 00 76 00 5f 00 37 00
        36 00 39 00 30 00 26 00
        73 00 75 00 62 00 73 00
        79 00 73 00 5f 00 31 00
        30 00 32 00 38 00 30 00
        31 00 62 00 64 00 26 00
        72 00 65 00 76 00 5f 00
        31 00 30 00 32 00 32 00
        23 00 34 00 26 00 33 00
        33 00 64 00 30 00 34 00
        34 00 66 00 65 00 26 00
        30 00 26 00 30 00 30 00
        30 00 31 00 23 00 7b 00
        36 00 39 00 39 00 34 00
        61 00 64 00 30 00 34 00
        2d 00 39 00 33 00 65 00
        66 00 2d 00 31 00 31 00
        64 00 30 00 2d 00 61 00
        33 00 63 00 63 00 2d 00
        30 00 30 00 61 00 30 00
        63 00 39 00 32 00 32 00
        33 00 31 00 39 00 36 00
        7d 00 5c 00 65 00 73 00
        6c 00 61 00 76 00 65 00
        64 00 68 00 70 00 73 00
        70 00 65 00 61 00 6b 00
        65 00 72 00 74 00 6f 00
        70 00 6f 00 2f 00 30 00
        30 00 30 00 31 00 30 00
        30 00 30 00 31 00 00 00
        00 00 00 00
    PKEY_AudioEndpoint_Association:
        VT_LPWSTR {00000000-0000-0000-0000-000000000000}
    PKEY_AudioEndpoint_Supports_EventDriven_Mode:
        VT_UI4 1
    {9a82a7db-3ebb-41b4-83ba-18b7311718fc},1:
        VT_UI4 65536
    {233164c8-1b2c-4c7d-bc68-b671687a2567},1:
        VT_LPWSTR {2}.\\?\hdaudio#func_01&ven_8384&dev_7690&subsys_102801bd&rev_1022#4&33d044fe&0&0001#{6994ad04-93ef-11d0-a3cc-00a0c9223196}\eslavedhpspeakerwave
    {5a9125b7-f367-4924-ace2-0803a4a3a471},0:
        VT_UI4 1610652916
    {5a9125b7-f367-4924-ace2-0803a4a3a471},2:
        VT_UI4 1610644836
    {b3f8fa53-0004-438e-9003-51a46e139bfc},0:
        VT_UI4 1
    PKEY_AudioEngine_DeviceFormat:
        VT_BLOB of size 40
        fe ff 02 00 44 ac 00 00
        10 b1 02 00 04 00 10 00
        16 00 10 00 03 00 00 00
        01 00 00 00 00 00 10 00
        80 00 00 aa 00 38 9b 71
    {e4870e26-3cc5-4cd2-ba46-ca0a9a70ed04},0:
        VT_BLOB of size 40
        fe ff 02 00 44 ac 00 00
        20 62 05 00 08 00 20 00
        16 00 20 00 03 00 00 00
        03 00 00 00 00 00 10 00
        80 00 00 aa 00 38 9b 71
    {e4870e26-3cc5-4cd2-ba46-ca0a9a70ed04},1:
        VT_BLOB of size 8
        d3 8c 01 00 00 00 00 00
    DEVPKEY_Device_FriendlyName:
        VT_LPWSTR Speakers (High Definition Audio Device)
    DEVPKEY_DeviceInterface_FriendlyName:
        VT_LPWSTR High Definition Audio Device
    PKEY_AudioEndpoint_GUID:
        VT_LPWSTR {CA9FA848-1E60-401C-81E6-323546335D0A}

    ID: {0.0.1.00000000}.{4f5e42d9-227f-43ff-bf5b-a1ce5d0324cf}
    State: 8 (DEVICE_STATE_UNPLUGGED)
    -- Properties (28) --
    {b3f8fa53-0004-438e-9003-51a46e139bfc},15:
        VT_BLOB of size 16
        da 07 0a 00 04 00 1c 00
        16 00 3a 00 3a 00 0f 01
    DEVPKEY_Device_DeviceDesc:
        VT_LPWSTR Microphone
    {b3f8fa53-0004-438e-9003-51a46e139bfc},6:
        VT_LPWSTR High Definition Audio Device
    {b3f8fa53-0004-438e-9003-51a46e139bfc},2:
        VT_LPWSTR {1}.HDAUDIO\FUNC_01&VEN_8384&DEV_7690&SUBSYS_102801BD&REV_1022\4&33D044FE&0&0001
    {83da6326-97a6-4088-9453-a1923f573b29},3:
        VT_LPWSTR hdaudio.inf:Microsoft.ntamd64:HdAudModel:6.1.7600.16385::hdaudio\func_01
    DEVPKEY_Device_BaseContainerId:
        VT_CLSID {00000000-0000-0000-ffff-ffffffffffff}
    DEVPKEY_Device_ContainerId:
        VT_CLSID {00000000-0000-0000-ffff-ffffffffffff}
    DEVPKEY_Device_EnumeratorName:
        VT_LPWSTR HDAUDIO
    {b3f8fa53-0004-438e-9003-51a46e139bfc},1:
        VT_BLOB of size 72
        a8 7f a4 d5 98 6d d1 11
        a2 1a 00 a0 c9 22 31 96
        9c ac 97 dc ec dd 59 4d
        b6 50 3b 8b a6 7b c2 a1
        00 00 00 00 00 00 00 00
        00 00 00 00 00 00 00 00
        e0 cc 13 de 04 83 e9 4e
        ba ce 48 24 21 4e 3e a5
        00 00 02 00 01 00 00 00
    PKEY_AudioEndpoint_FormFactor:
        VT_UI4 4
    PKEY_AudioEndpoint_JackSubType:
        VT_LPWSTR {DFF21BE1-F70F-11D0-B917-00A0C9223196}
    DEVPKEY_DeviceClass_IconPath:
        VT_LPWSTR %windir%\system32\mmres.dll,-3014
    {840b8171-b0ad-410f-8581-cccc0382cfef},0:
        VT_BLOB of size 300
        01 00 00 00 28 01 00 00
        00 00 01 00 7b 00 32 00
        7d 00 2e 00 5c 00 5c 00
        3f 00 5c 00 68 00 64 00
        61 00 75 00 64 00 69 00
        6f 00 23 00 66 00 75 00
        6e 00 63 00 5f 00 30 00
        31 00 26 00 76 00 65 00
        6e 00 5f 00 38 00 33 00
        38 00 34 00 26 00 64 00
        65 00 76 00 5f 00 37 00
        36 00 39 00 30 00 26 00
        73 00 75 00 62 00 73 00
        79 00 73 00 5f 00 31 00
        30 00 32 00 38 00 30 00
        31 00 62 00 64 00 26 00
        72 00 65 00 76 00 5f 00
        31 00 30 00 32 00 32 00
        23 00 34 00 26 00 33 00
        33 00 64 00 30 00 34 00
        34 00 66 00 65 00 26 00
        30 00 26 00 30 00 30 00
        30 00 31 00 23 00 7b 00
        36 00 39 00 39 00 34 00
        61 00 64 00 30 00 34 00
        2d 00 39 00 33 00 65 00
        66 00 2d 00 31 00 31 00
        64 00 30 00 2d 00 61 00
        33 00 63 00 63 00 2d 00
        30 00 30 00 61 00 30 00
        63 00 39 00 32 00 32 00
        33 00 31 00 39 00 36 00
        7d 00 5c 00 65 00 6d 00
        69 00 63 00 69 00 6e 00
        74 00 6f 00 70 00 6f 00
        2f 00 30 00 30 00 30 00
        31 00 30 00 30 00 30 00
        31 00 00 00
    PKEY_AudioEndpoint_Association:
        VT_LPWSTR {00000000-0000-0000-0000-000000000000}
    PKEY_AudioEndpoint_Supports_EventDriven_Mode:
        VT_UI4 1
    {24dbb0fc-9311-4b3d-9cf0-18ff155639d4},3:
        VT_BOOL 0xffffffff
    {24dbb0fc-9311-4b3d-9cf0-18ff155639d4},4:
        VT_BOOL 0xffffffff
    {9a82a7db-3ebb-41b4-83ba-18b7311718fc},1:
        VT_UI4 65536
    {233164c8-1b2c-4c7d-bc68-b671687a2567},1:
        VT_LPWSTR {2}.\\?\hdaudio#func_01&ven_8384&dev_7690&subsys_102801bd&rev_1022#4&33d044fe&0&0001#{6994ad04-93ef-11d0-a3cc-00a0c9223196}\emicinwave
    {5a9125b7-f367-4924-ace2-0803a4a3a471},0:
        VT_UI4 1610712932
    {5a9125b7-f367-4924-ace2-0803a4a3a471},2:
        VT_UI4 1610708836
    {b3f8fa53-0004-438e-9003-51a46e139bfc},0:
        VT_UI4 3
    PKEY_AudioEngine_DeviceFormat:
        VT_BLOB of size 40
        fe ff 02 00 44 ac 00 00
        10 b1 02 00 04 00 10 00
        16 00 10 00 03 00 00 00
        01 00 00 00 00 00 10 00
        80 00 00 aa 00 38 9b 71
    {e4870e26-3cc5-4cd2-ba46-ca0a9a70ed04},0:
        VT_BLOB of size 40
        fe ff 02 00 44 ac 00 00
        20 62 05 00 08 00 20 00
        16 00 20 00 03 00 00 00
        03 00 00 00 00 00 10 00
        80 00 00 aa 00 38 9b 71
    {e4870e26-3cc5-4cd2-ba46-ca0a9a70ed04},1:
        VT_BLOB of size 8
        d3 8c 01 00 00 00 00 00
    DEVPKEY_Device_FriendlyName:
        VT_LPWSTR Microphone (High Definition Audio Device)
    DEVPKEY_DeviceInterface_FriendlyName:
        VT_LPWSTR High Definition Audio Device
    PKEY_AudioEndpoint_GUID:
        VT_LPWSTR {4F5E42D9-227F-43FF-BF5B-A1CE5D0324CF}

    ID: {0.0.1.00000000}.{6712184b-f8f0-474d-ad36-808cc90a7cfd}
    State: 4 (DEVICE_STATE_NOTPRESENT)
    -- Properties (29) --
    {b3f8fa53-0004-438e-9003-51a46e139bfc},15:
        VT_BLOB of size 16
        db 07 03 00 01 00 15 00
        10 00 3b 00 21 00 f4 00
    DEVPKEY_Device_DeviceDesc:
        VT_LPWSTR Microphone Array
    {b3f8fa53-0004-438e-9003-51a46e139bfc},6:
        VT_LPWSTR Microphone Array
    {b3f8fa53-0004-438e-9003-51a46e139bfc},2:
        VT_LPWSTR {1}.USB\VID_045E&PID_FFF0&MI_01\6&1474EDB6&0&0001
    {83da6326-97a6-4088-9453-a1923f573b29},3:
        VT_LPWSTR wdma_usb.inf:Microsoft.ntamd64:USBAudio:6.1.7600.16385:usb\class_01
    DEVPKEY_Device_BaseContainerId:
        VT_CLSID {9132f6ea-a152-5472-8685-61c0d297a30b}
    DEVPKEY_Device_ContainerId:
        VT_CLSID {9132f6ea-a152-5472-8685-61c0d297a30b}
    DEVPKEY_Device_EnumeratorName:
        VT_LPWSTR USB
    {b3f8fa53-0004-438e-9003-51a46e139bfc},1:
        VT_BLOB of size 72
        30 f1 1c 4e 79 16 3b 46
        a7 2f a5 bf 64 c8 6e ba
        4e 5a cd ab 63 c2 3b 46
        a7 2f a5 bf 64 c8 6e ba
        f0 75 12 8f e9 26 64 42
        ba 4d 39 ff f0 1d 94 aa
        a6 54 57 fc f8 2d 3b 46
        a7 2f a5 bf 64 c8 6e ba
        02 00 00 00 09 00 00 00
    PKEY_AudioEndpoint_FormFactor:
        VT_UI4 4
    PKEY_AudioEndpoint_JackSubType:
        VT_LPWSTR {DFF21BE5-F70F-11D0-B917-00A0C9223196}
    DEVPKEY_DeviceClass_IconPath:
        VT_LPWSTR %windir%\system32\mmres.dll,-3020
    {840b8171-b0ad-410f-8581-cccc0382cfef},0:
        VT_BLOB of size 236
        01 00 00 00 e8 00 00 00
        00 00 01 00 7b 00 32 00
        7d 00 2e 00 5c 00 5c 00
        3f 00 5c 00 75 00 73 00
        62 00 23 00 76 00 69 00
        64 00 5f 00 30 00 34 00
        35 00 65 00 26 00 70 00
        69 00 64 00 5f 00 66 00
        66 00 66 00 30 00 26 00
        6d 00 69 00 5f 00 30 00
        31 00 23 00 36 00 26 00
        31 00 34 00 37 00 34 00
        65 00 64 00 62 00 36 00
        26 00 30 00 26 00 30 00
        30 00 30 00 31 00 23 00
        7b 00 36 00 39 00 39 00
        34 00 61 00 64 00 30 00
        34 00 2d 00 39 00 33 00
        65 00 66 00 2d 00 31 00
        31 00 64 00 30 00 2d 00
        61 00 33 00 63 00 63 00
        2d 00 30 00 30 00 61 00
        30 00 63 00 39 00 32 00
        32 00 33 00 31 00 39 00
        36 00 7d 00 5c 00 67 00
        6c 00 6f 00 62 00 61 00
        6c 00 2f 00 30 00 30 00
        30 00 31 00 30 00 30 00
        30 00 31 00 00 00 00 00
        00 00 00 00
    PKEY_AudioEndpoint_Association:
        VT_LPWSTR {00000000-0000-0000-0000-000000000000}
    PKEY_AudioEndpoint_Supports_EventDriven_Mode:
        VT_UI4 1
    {24dbb0fc-9311-4b3d-9cf0-18ff155639d4},3:
        VT_BOOL 0x0
    {24dbb0fc-9311-4b3d-9cf0-18ff155639d4},4:
        VT_BOOL 0xffffffff
    {9a82a7db-3ebb-41b4-83ba-18b7311718fc},1:
        VT_UI4 65536
    {233164c8-1b2c-4c7d-bc68-b671687a2567},1:
        VT_LPWSTR {2}.\\?\usb#vid_045e&pid_fff0&mi_01#6&1474edb6&0&0001#{6994ad04-93ef-11d0-a3cc-00a0c9223196}\global
    {5a9125b7-f367-4924-ace2-0803a4a3a471},0:
        VT_UI4 1610713736
    {5a9125b7-f367-4924-ace2-0803a4a3a471},2:
        VT_UI4 1610709736
    {b3f8fa53-0004-438e-9003-51a46e139bfc},0:
        VT_UI4 3
    PKEY_AudioEngine_DeviceFormat:
        VT_BLOB of size 40
        fe ff 04 00 80 3e 00 00
        00 f4 01 00 08 00 10 00
        16 00 10 00 00 00 00 00
        01 00 00 00 00 00 10 00
        80 00 00 aa 00 38 9b 71
    {e4870e26-3cc5-4cd2-ba46-ca0a9a70ed04},0:
        VT_BLOB of size 40
        fe ff 04 00 80 3e 00 00
        00 e8 03 00 10 00 20 00
        16 00 20 00 00 00 00 00
        03 00 00 00 00 00 10 00
        80 00 00 aa 00 38 9b 71
    {e4870e26-3cc5-4cd2-ba46-ca0a9a70ed04},1:
        VT_BLOB of size 8
        a0 86 01 00 00 00 00 00
    {9855c4cd-df8c-449c-a181-8191b68bd06c},0:
        VT_BLOB of size 16
        00 00 f0 41 00 00 f0 41
        00 00 f0 41 00 00 f0 41
    DEVPKEY_Device_FriendlyName:
        VT_LPWSTR Microphone Array (Microphone Array)
    DEVPKEY_DeviceInterface_FriendlyName:
        VT_LPWSTR Microphone Array
    PKEY_AudioEndpoint_GUID:
        VT_LPWSTR {6712184B-F8F0-474D-AD36-808CC90A7CFD}

    The unrecognized properties could be private properties used by the OS, or by the audio driver, or they might be defined in some public header that I didn't bother to scour.

    UPDATE 2011-09-09: added WAVEFORMATEX logging for properties known to be audio formats.

  • Matthew van Eerde's web log

    What is a perfect score in Yahtzee?

    • 11 Comments

    The dice game Yahtzee takes thirteen turns. Each turn involves rolling a set of five dice (with two possible rerolls, the player having the option to reroll only a subset of the dice), then marking one of thirteen boxes and scoring points according to whether the numbers on the dice meet the rules of the box. After each box is marked, the game is over.

    The thirteen boxes and their rules are:

    1. ONES: score is the sum of those dice showing a one (could be zero.)
    2. TWOS: score is the sum of those dice showing a two (could be zero.)
    3. THREES: score is the sum of those dice showing a three (could be zero.)
    4. FOURS: score is the sum of those dice showing a four (could be zero.)
    5. FIVES: score is the sum of those dice showing a five (could be zero.)
    6. SIXES: score is the sum of those dice showing a six (could be zero.)
    7. THREE OF A KIND: if three of the dice show the same number, the score is the sum of all five dice; otherwise, zero.
    8. FOUR OF A KIND: if four of the dice show the same number, the score is the sum of all five dice; otherwise, zero.
    9. FULL HOUSE: if three of the dice show the same number as each other, and the other two show the same number as each other, score 25; otherwise, zero.1
    10. SMALL STRAIGHT: if there are four dice which show (1 2 3 4), or (2 3 4 5), or (3 4 5 6), score 30; otherwise, zero. (There is no defined order for the rolled dice, so order is not important.)
    11. LARGE STRAIGHT: if the five dice show (1 2 3 4 5), or (2 3 4 5 6), score 40; otherwise, zero. (Order is not important.)
    12. CHANCE: score the total of the five dice (minimum possible score is 5.)2
    13. YAHTZEE (FIVE OF A KIND:) if all five dice show the same number, score 50; otherwise, zero.

    There are two bonuses possible:

    1. At the end of the game, if the point total for boxes 1-6 (ONES through SIXES) is at least 63, the player gets a bonus of 35. (63 corresponds to getting three ones + three twos + three threes + ... + three sixes, but there are other possibilities. I don't know where 35 came from.)
    2. At any time the player checks a box, if...
      • the five dice all have the same number as each other
      • AND the Yahtzee box is already checked
      • AND this was not a result of "taking a zero in Yahtzee"
      ... then the player gets a bonus 100 points. This bonus can take effect multiple times per game.

    So a perfect Yahtzee game looks like this. On the first turn...

    Roll Box Score
    (x x x x x) any Yahtzee YAHTZEE 50
    Subtotal 50

    ... then on the following turns, in no particular order (but see below:)

    Roll Box Score
    (1 1 1 1 1) ONES 5
    (2 2 2 2 2) TWOS 10
    (3 3 3 3 3) THREES 15
    (4 4 4 4 4) FOURS 20
    (5 5 5 5 5) FIVES 25
    (6 6 6 6 6) SIXES 30
    (6 6 6 6 6) THREE OF A KIND 30
    (6 6 6 6 6) FOUR OF A KIND 30
    (x x x x x) any Yahtzee FULL HOUSE 0 or 25 (see below)1
    (x x x x x) any Yahtzee SMALL STRAIGHT 0 (but see below)
    (x x x x x) any Yahtzee LARGE STRAIGHT 0 (but see below)
    (6 6 6 6 6) CHANCE 30
    Subtotal 195 or 220

    Now we look at the bonuses and add it all up:

    Category Score
    Base score 245 or 270
    Bonus for scoring >= 63 in ONES through SIXES 35
    Bonus for scoring twelve additional YAHTZEEs after scoring 50 in YAHTZEE 1200
    Total 1480 or 1505

    So a perfect score in Yahtzee is 1480 or 1505.

    Well...

    ... not quite. There's another rule about Yahtzees (the "Joker" rule) which I didn't tell you about yet, because it's kind of complicated. If...

    • You roll a Yahtzee
    • AND the YAHTZEE box is already checked (a zero score is OK; you don't get the 100 point bonus, but this rule still applies)
    • ... then you MUST score this in the ONES-through-SIXES box for the number you rolled (say, 3)...
    • ... UNLESS that box has also already been checked (a zero score is OK), in which case...

    ... you can score this roll in any unchecked box and it automatically meets the scoring criterion (wow!) So you would get 30 points in SMALL STRAIGHT or 40 points in LARGE STRAIGHT. (Or 25 points in FULL HOUSE.)1

    This gives an additional 70 or 95 points, bring the total to 1575. (It also creates additional order dependencies; the boxes for FULL HOUSE, SMALL STRAIGHT, and LARGE STRAIGHT must be checked after the ONES through SIXES box for the corresponding number is checked.)

    1 Does five-of-a-kind count as a full house? I'm not sure. Mathematically it would make sense. It turns out not to matter for this calculation because of the Joker rule, but it may matter in an actual game. The official rules say "Score in this box only if the dice show three of one number and two of another", so I guess not, but I would be comfortable adopting a "house rule" which allowed a Yahtzee to count as a "native" full house. Normally a house rule should be agreed upon by all players before the start of the game, but I'm pretty sure it would be possible to convince the other players to let you score your (3 3 3 3 3) in FULL HOUSE while YAHTZEE was still open. ("You want to throw away 25 points?  Really?  Um, go right ahead...")

    2 The lowest possible Yahtzee score is 5: get (1 1 1 1 1) and score it in Chance, then get zeros in all the other boxes.

  • Matthew van Eerde's web log

    Sample - playing silence via WASAPI event-driven (pull) mode

    • 10 Comments

    Be vewy vewy quiet - we'we hunting wabbits.
        -- Elmer Fudd

    Attached is a mini-app I've written to play silence to any given playback device using WASAPI event-driven (pull) mode.  Source, x86 binary, and amd64 binary are attached.

    Usage statement:

    >silence -?
    silence
    silence -?
    silence --list-devices
    silence --device "Device long name"

        With no arguments, plays silence to the default audio device.
        -? prints this message.
        --list-devices displays the long names of all active playback devices.
        --device plays silence to the specified device.

    >silence --list-devices
    Active render endpoints found: 2
        Speakers (USB Audio Device)
        Headphones (High Definition Audio Device)

    >silence --device "Headphones (High Definition Audio Device)"
    Press Enter to quit...
    Received stop event after 488 passes

    While it's playing it shows up in the Volume Mixer:

     http://blogs.msdn.com/photos/matthew_van_eerde/images/9191979/original.aspx

    Why would I write such a thing?

    Well, there is the pedagogical exercise of writing a WASAPI event-driven render loop.

    But there is also a practical application of an active silence stream, having to do with loopback capture.  More on this in a future post...

    EDIT: 7/30/2009 - fixed bug where I was treating the GetCurrentPadding value as the amount of free space in the buffer when in fact it's the amount of used space.

    While I was at it, added an icon and exited immediately on errors rather than waiting for the caller to hit Enter.

  • Matthew van Eerde's web log

    Tweaks I make every time I install Windows

    • 9 Comments

    As preparation for moving one of my machines from Vista to Windows 7, I'm compiling a list of all the little tweaks I like to make to machines that I use a lot:

    Boot from the Windows DVD.  Delete all partitions; make each hard drive one big partition.  (Hmm... apparently Windows 7 really wants a second 100 MB partition.  Do the partition dance to force it into installing on a single partition.)

    In the "password hint" box, type a misleading hint.

    In "Help protect your computer and improve Windows automatically", choose "Ask me later."

    Once I'm in, create a new limited user (not a member of the Administrators group) and use that as my primary account.

    Control Panel | Hardware and Sound | Mouse
        Pointers | Enable pointer shadow (uncheck)
        Pointer Options
            Enhance pointer precision (uncheck)
            Hide pointer while typing (uncheck)

    Right-click taskbar | Properties | Taskbar
        Use small icons (check)
        Taskbar location on screen (change to "Right")
        Taskbar buttons (change to "Never combine")
        Notification area | Customize
            Turn system icons on or off
                Clock | Off (select)
                Volume | Off (select)
                Network | Off (select)
                ... turn everything off, except sometimes.
                (for example, I might leave Power on for a laptop.)
            Always show all icons and notifications on the taskbar (check)
        Use Aero Peek to preview the desktop (uncheck)

    Windows Explorer | Organize
        Layout | Details pane (uncheck)
        Folder and search options | View | Advanced settings
            Always show icons, never thumbnails (check)
            Always show menus (check)
            Display file icon on thumbnails (uncheck)
            Display file size information in folder tips (uncheck)
            Display the full path in the title bar (Classic theme only) (check)
            Hidden files and folders | Show hidden files, folders, and drives (select)
            Hide empty drives in the Computer folder (uncheck)
            Hide extensions for known file types (uncheck)
            Hide protected operating system files (Recommended) (uncheck, Yes I'm sure)
            Show pop-up description for folder and desktop items (uncheck)

    Control Panel | System and Security | Windows Update | Change settings
        Download updates but let me choose whether to install them (select)
        Allow all users to install updates on this computer (check)

    Elevated command prompt | gpedit.msc | Local Computer Policy
        User Configuration | Administrative Templates | Windows Components
            Windows Explorer | Turn off numerical sorting in Windows Explorer (enable)
        Computer Configuration | Administrative Templates
            System
                Power Management | Video and Display Settings
                    Turn Off Adaptive Display Timeout (Plugged In) (enable)
                    Turn Off Adaptive Display Timeout (On Battery) (enable)
            Windows Components | Windows Update
                Do not display 'Install Updates and Shut Down' ... (enable)
                Do not adjust default option to 'Install Updates and Shut Down' ... (enable)

    Control Panel | View by: Small icons (select)
        AutoPlay | Use AutoPlay for all media and devices (uncheck)
        Indexing Options | Modify | Show all locations
            Offline Files (uncheck)
            C:\Users (uncheck)
            C:\ProgramData\Microsoft\Windows\Start Menu (uncheck)
        Troubleshooting | Change settings | Computer Maintenance | Off (select)
        Windows Defender | Tools
            Automatic scanning | Automatically scan my computer (uncheck)
            Real-time protection | Use real-time protection (recommended) (uncheck)
            Administrator | Use this program (uncheck)

    Right-click Start Menu | Properties
        Customize
            Computer | Don't display this item (select)
            Connect To (uncheck)
            Control Panel | Don't display this item (select)
            Default Programs (uncheck)
            Devices and Printers (uncheck)
            Documents | Don't display this item (select)
            Enable context menus and dragging and dropping (uncheck)
            Games | Don't display this item (select)
            Help (uncheck)
            Highlight newly installed programs (uncheck)
            Music | Don't display this item (select)
            Open submenus when I pause on them with the mouse pointer (uncheck)
            Personal folder | Don't display this item (select)
            Pictures | Don't display this item (select)
            Search other files and libraries | Don't search (select)
            Search programs and Control Panel (uncheck)
            Use large icons (uncheck)
            Number of recent programs to display (set to 20 to make the menu bigger)
            Number of recent items to display in Jump Lists (set to 20 to make the menu bigger)
        Store and display recently opened programs in the Start menu (uncheck)
        Store and display recently opened items in the Start menu and the taskbar (uncheck)

    Right-click everything that is pinned to the taskbar
        Unpin this program from taskbar

    Right-click Recycle Bin | Properties
        For each hard drive in turn (select)
            Don't move files to the Recycle Bin. Remove files immediately... (select)

    Control Panel | Appearance and Personalization
        Personalization | Change desktop icons | Recycle Bin (uncheck)
        Change desktop background
            Picture Location: Solid Colors (select, choose black)

    Control Panel | User Accounts | Change your account picture
        Browse for more pictures
            http://blogs.msdn.com/photos/matthew_van_eerde/images/6831850/thumb.aspx

    Control Panel | Ease of access
        Change how your mouse works
            Mouse pointers | Regular Black (select)
            Prevent windows from being automatically arranged when moved to the edge of the screen (check)
        Change how your keyboard works
            Set up Sticky Keys | Turn on Sticky Keys when SHIFT is pressed five times (uncheck)
        Optimize visual display
            Turn off all unnecessary animations (when possible) (check)

    Make a folder on the desktop named "_"
        Open the following folders simultaneously
            _
            C:\ProgramData\Microsoft\Windows\Start Menu
            C:\Users\%username%\AppData\Roaming\Microsoft\Windows\Start Menu
        Copy various shortcuts from Start Menu over to _
            Notepad
            Paint
            Command Prompt
            Calculator
            ... whatever else strikes my fancy
        ... as I install programs, consider adding them here if I use them a lot

    Right-click the taskbar | Toolbars | New toolbar... | Desktop | _ (Select Folder)
        Right-click the taskbar | Lock the taskbar (uncheck)
        Drag the thumb of the _ toolbar to the top of the taskbar
        Right-click _
            Show Text (uncheck)
            Show title (uncheck)
            View | Small Icons (check)
        Drag the taskbar to be a tiny bit wider so three small icons fit side-by-side
        Drag the view-active-tasks part of the taskbar to be big
        Right-click the taskbar | Lock the taskbar (check)

    Make a 1-pixel-by-1-pixel black .jpg and set it as the LogonUI background

    I'm sure I'm forgetting some other things.  I'll add them later when I run into them.

    I could probably make a series out of this.  Possible candidates for future posts: "Tweaks I make every time I install Office", "Tweaks I make every time I install Firefox"...

    Command Prompt | Alt-Space | Defaults | QuickEdit Mode (check)

  • Matthew van Eerde's web log

    Unsolving the Rubik's Cube

    • 8 Comments

    Recently I was musing on Order vs. Chaos and toying with my Rubik's Cube.  I wondered, as a simple exercise, whether it would be possible to strongly unsolve the cube.  Obviously there is a single way to put the cube into a state of greatest possible order... and a multitude of ways to put it into a state of moderate to severe chaos... but is there a "most chaotic" state?

    Well, I mused, the "greatest possible order" state is achieved by bringing all the orange squares together... and all the blue squares together... and, in general, all the squares of any given color together.  How homogenous... or xenophobic.  Ew.

    Might it not be interesting to intermingle the colors to as great an extent as possible?  Can I put the cube in a state where no two orange squares are adjacent... and no two blue squares are adjacent... and, in general, no two adjacent squares are the same color?

    Of course, I responded.  In fact, I already know how to do that... I learned that trick before I even learned the Restore Order solution.

    Twelve turns later, I had a solution to the Adjacent Squares Are Different problem.

    Exercise: what are the twelve turns?

    Well, that was quick.

    But I wanted more.

    Yeah, OK, no two adjacent squares are the same color, by the usual definition of adjacency (the squares share a common border.)  But this is still a fairly homogenous solution... each face (of nine squares) still consists of only two colors, and there's a very high incidence of diagonally-touching squares of the same color.  Can't we diversify this even more?

    That took me a couple of days.  But here's the solution I came up with:

    Unsolved Rubik's cube

    Note that, as desired, no two adjacent squares are the same color... even if you consider squares that touch only at a corner to be adjacent... even if that corner lies on an edge, and the two squares in question lie on different faces of the cube.

    The method to achieving the solution was simple in the sense that it only requires two moves (starting from a solved cube) but is probably far from optimal in the "total number of turns" sense.

    There are two independent steps which can be done in either order:

    • Flip the orientation of all twelve of the "edge pieces" (the cubelets with two visible faces) 
    • Migrate all eight "corner pieces" (the cubelets with three visible faces) to the diametrically opposite position on the cube

    Each requires knowledge of a single move and a fair amount of courage.

    First, some syntax.

    Hold the cube facing you.  I will name the six faces of the cube:

    • Fore (the face closest to you - in the picture above, the face with the green center)
    • Hind (in the picture above, the face with the blue center)
    • Top (in the picture above, the face with the white center)
    • Bottom (in the picture above, the face with the yellow center)
    • Left (in the picture above, the face with the orange center)
    • Right (in the picture above, the face with the red center)

    Each face has a local definition of "clockwise"... this is the direction a clock painted on the face would turn.

    Cubelet syntax

    • A combination of two letters (FR) means the edge cubelet common to both faces - in this case the Fore and Right faces.  In the picture above, this is the green and white cubelet.
    • A combination of three letters (BHL) means the corner cubelet common to all three faces - in this case the Bottom, Hind, and Left faces.  In the picture above, this is the red-green-and-white cubelet.

    Move syntax: a letter means turn that face clockwise by 90 degrees.  A letter with a subscripted -1 means turn it counterclockwise by 90 degrees.  So a move "turn the Fore face clockwise, then turn the Left face counterclockwise, then turn the Top face counterclockwise" would be written:

    F L-1 T-1

    Start from a solved cube.

    Flip the orientation of all twelve of the "edge pieces"

    Pick your favorite color - say, red - and have that be the top face.

    The following move flips the orientation of the LT and TH edge pieces, and also disturbs the orientation of some corner pieces:

    L-1 T-1 L-1 L-1
    F-1 L-1 F-1 F-1
    T-1 F-1 T-1 T-1

    • Run the move once with red as the top face.
    • Position the cube so that red is still as the top face but the two remaining unflipped edge pieces with red on them are now in the LT and TH position.  That is, turn the whole cube 180 degrees with an axis of rotation that goes through the center of the top and bottom faces.
    • Run the move again.  All four edge cubelets with red on them should now be flipped.
    • Position the cube so that red is now the front face.
    • Run the move again four more times, keeping red as the front face but rotating the cube 90 degrees each time, so each execution of the move acts on two unflipped edge pieces.

    All twelve of the edge pieces are now flipped.  The corner pieces are still in their original positions, though they may be oriented incorrectly. That's OK.

    Reposition all eight corner pieces to the opposite corner

    The following move repositions FLT to TLH, TLH to TRH, and TRH to FLT:

    L-1 T R T-1
    L T R-1 T-1

    As a convenience, the following mirror-image move repositions FRT to TRH, TRH to TLH, and TLH to FRT:

    R T-1 L-1 T
    R-1 T-1 L T

    Strictly speaking, you can get away with only memorizing one of these moves - each move is equivalent to holding the cube in a different position and executing the other move twice.

    Pick three faces that share a common corner - say, the faces whose center cubes are red, white, and blue.  Position the cube so it is balancing on that corner.  Note that the corner cubelets now occupy four distinct strata:

    1. The "top" corner.
    2. The three corners that share a common edge with the "top" corner.
    3. The three corners that share a common edge with the "bottom" corner.
    4. The "bottom" corner. 

    This part of unsolving the cube is completed in four distinct phases:

    1. Unsolve the "top" corner - that is, find the red/white/blue cubelet (it's on the bottom corner) and move it to the top.  This will require two moves, which will to a certain degree randomize the positions of all of the rest of the corners.
    2. Unsolve the three corners that share a common edge with the "top" corner, without disturbing the cubelet in the "top" corner, or any already-unsolved cubelets in this stratum.
    3. Unsolve one of the three corners that share a common edge with the "bottom" corner, without disturbing any of the cubelets in the two strata above.
    4. Finally, there are three remaining un-unsolved cubelets.  Unsolve all three of these simultaneously - this will take precisely one execution of one of the two moves above.

    EDIT January 16 2012: Thanks to Dustin for pointing out that the diagram above shows some reds touching diagonally (near the topmost corner.)  After some analysis I believe this is just due to an error on my part in making the image; specifically, the top corner is green-yellow-red, but so is the corner in the top left.  Also, the green-yellow-orange corner is missing.

    Both of these can be explained by changing the red in the top corner to orange.  Updated image:

  • Matthew van Eerde's web log

    Blown movie lines - The Matrix

    • 7 Comments

    In the first Matrix movie there's a very interesting character called Cypher.  If you go along with the theory that the Matrix series is a rough retelling of the story of Christ, Cypher is the closest analog to Judas Iscariot, who is one of the earliest very-interesting-characters.

    Unfortunately I personally found that the actor kind of got in the way of the character sometimes.

    For example, in his scene with Neo, Cypher has this line

    The image translators work for the construct program - but there's way too much information to decode the Matrix.

    The actor delivers this line in such a way (the image translators work for the construct program) as to imply that there are these thingies called "image translators", and this other thingy called a "construct program".  Furthermore, these "image translator" thingies are currently in the employ of the "construct program".  The "construct program" is apparently a very nasty beast with close ties to the machine Gestapo.  Therefore, if we were so foolish as to attempt to recruit the talents of the "image translators", the "construct program" would find out, and then we'd be in big trouble.

    It follows then, as the night the day, that we are thrown on our own, limited, resources... and what with piloting the ship and arranging the menu and what-not, decoding the matrix (being a substantial task) is rather low on the priority list.

    Sigh.

    This would be an interesting reading, filled with wonderful foreshadowings of the inevitable discovery of Zion by the machines, except for one thing.

    It is already firmly established that the "construct program" is the Holodeck-like-thing that the crew of the Nebuchadnezzar use to equip themselves with simulated equipment (and simulated skills) for use in the simulated reality of the Matrix. So the above reading is, of course, nonsense.  The correct implication of the line is:

    There are these thingies called "image translators."  We throw bits of code at them and they translate them into electrical stimuluses that our brain interprets as images.  They can handle such-and-such amount of code.

    The construct program is under our control, and we scale back the amount of code it generates to what our image translators can handle.  That is why when you enter the construct program you can "see" your residual self-image and whatnot.

    However, the Matrix is not under our control, so we can't scale the amount of code it generates.  It so happens that the Matrix generates an awful lot of code, which overpowers our image translators... which is why these monitors just show a bunch of greenish symbols instead of pretty girls with hair of various colors.

    A delivery that gets this across would be more like

    The image translators work - for the construct program - but there's way too much information to decode the Matrix.
  • Matthew van Eerde's web log

    Triangular primes: or, the dangers of non-rigorous proofs

    • 7 Comments
    Beware of bugs in the above code; I have only proved it correct, not tried it.
    -- Donald Knuth

    If you've bowled, you know the arrangement of the bowling pins forms a triangle.

    http://blogs.msdn.com/photos/matthew_van_eerde/images/8601570/original.aspx

    (Image courtesy of the International Pumpkin Federation.)

    If you've played eight-ball, you know the arrangement of the fifteen billiard balls forms a triangle.

    http://blogs.msdn.com/photos/matthew_van_eerde/images/8601581/original.aspx

    Ten, fifteen... what other numbers form a triangle? The common arrangement of nine-ball and ninepins doesn't count because it's a diamond, not a triangle.

    You can start with ten and add five balls to make a triangle of fifteen... then add six more to make a triangle of 21... then seven more to make a triangle of 28... and so on, with this sequence:

    ..., 10, 15, 21, 28, 36, 45, 55, 66, 78, 91, 105, 120, 136, 153, 171, 190, 210, 231...

    Start looking for patterns.  What do you see?  Nothing jumps out right away.  Are there any primes?  Ones that end in 0, 2, 4, 6, or 8 are obviously even and therefore not primes... ones that end in 5 are divisible by 5 and therefore not primes... the remaining ones fall due to specific cases (21 = 3 x 7; 91 = 7 x 13; 153 = 3 x 3 x 17; 171 = 3 x 3 x 19; 231 = 3 x 7 x 11...)

    In fact, gosh darn it, it seems like none of these numbers are prime, no matter how far we extend that "..." on the right!  Can this be a coincidence?

    A few mental coruscations later I have an idea that it is not a coincidence... that triangular numbers, by there very nature, cannot be prime.  In fact I'm even willing to call it a "proof."  Here it is.

    "Theorem": there are no triangular primes.

    First we need to generate a formula for the triangular numbers.  Note that if you take an n x (n + 1) rectangle and draw a zig-zag line like so, you get two triangles.

     http://blogs.msdn.com/photos/matthew_van_eerde/images/8601535/original.aspx

    Each of these triangles is composed of n diagonals, the shortest of which is 1, and the longest of which is n.  That is to say, each of the triangles is composed of tn squares.  So we know that the total number of squares in the rectangle is 2tn.

    But we also know that the total number of squares is the base times the height, or n(n + 1).  This gives us 2tn = n(n + 1), or tn = n(n + 1)/2.

    The next part of the "proof" breaks down into case analysis.  n can be odd (as in the diagram, where n is 5) or even.

    Case where n is even:

    n is an even positive number.  Therefore n/2 is a positive number (maybe odd, maybe even; doesn't matter.)  tn can be written as (n/2)(n + 1), and is therefore not prime, since it has at least four factors: 1, n/2, n + 1, and tn.

    Case where n is odd:

    n is an odd positive number.  Therefore n + 1 is an even positive number.  Therefore (n + 1)/2 is a positive number (maybe odd, maybe even; doesn't matter.)  tn can be written as (n)([n + 1]/2), and is therefore not prime, since it has at least four factors: 1, n, (n + 1)/2, and tn.

    Note that the "proof" that tn is not prime inevitably concludes in a stronger result - that tn has at least four factors... not only is tn not prime, it can't even have as few as three factors.  (Some numbers with three factors: 4, 9, 25, 49...)

    Exercise: what kind of numbers have exactly three factors?

    A beautiful proof.  Perhaps the two cases can be elegantly folded together to normalize it a bit better.  That is not a serious problem.

    There is a serious problem.

    The proof of our result is doomed.

    Why?

    Because the result does not hold! There is a triangular prime.

    Exercise: find a triangular prime.

    After having recovered from the shocking revelation that, our beautiful proof to the contrary, a triangular prime is so rude as to exist, a little self-examination is in order.  What is wrong with the proof?  This... and not the existence of triangular primes... is the lesson to be learned: that beauty is not always truth.

    The great tragedy of Science -- the slaying of a beautiful hypothesis by an ugly fact.
    -- Thomas Huxley
  • Matthew van Eerde's web log

    Interview question: find solution to x! + y! + z! = x * y * z

    • 7 Comments

    I stumbled on hmlee's algorithm chart quite by chance.  A lot of good questions in there.

    Question 53 caught my eye as a mathematician.

    Q53: Say you have three integers between 0 - 9. You have the equation: A! + B! + C! = ABC (where ABS is a three digit numbers, not A * B * C). Find A, B, and C that satisfies this equation.

    Interesting. But I'd like to modify the question a little.

    Q53': Say you have three integers between 0 - 9. You have the equation: A! + B! + C! = A * B * C.
    Find A, B, and C that satisfies this equation.

    Astute readers will notice that A, B, and C are interchangeable.  Nonetheless there is a unique solution (modulo swapping A, B, and C.)

    I'm much less interested in the answer to this question (I know the answer) than I am in the quality of the program used to find the answer.  (I tried finding the answer by hunt-and-peck, then gave up and wrote a program - I find the program to be more interesting than the answer.)  Try to find the solution as efficiently as possible (without cheating.)

    I'll post my program later.

    If you see a fact, try to see it as intuitively as possible.

    I suspect that the following even more general problem has the same unique solution, which would be very interesting indeed.  This is the kind of thing where programs fail, and the mathematical mind becomes necessary again:

    Q53'': Say you have three integers that are >= 0. You have the equation: A! + B! + C! = A * B * C.
    Find A, B, and C that satisfies this equation.

  • Matthew van Eerde's web log

    Getting audio peak meter values for all active audio sessions

    • 7 Comments

    The Windows Vista volume mixer shows a peak meter for the device.  In Windows 7 we added a peak meter for each application.

    The audio interface for both is IAudioMeterInformation; I've used this before in my post about the linearity of Windows volume APIs.  This post showed how an application can get the peak meter reading for the device meter.

    In Windows 7 we also added APIs to allow applications to get a list of audio sessions.  I wrote up a quick app which shows how to get a list of all audio sessions, filter it down to the active ones (sessions which have an active audio client), and then get a peak meter reading.

    >meters.exe
    {0.0.0.00000000}.{c05f2f54-7294-422a-bb0d-8d690c365b73}|#%b{917F8618-9C57-4720-9B7C-88CA45FC983B}: 0.215705
    Active sessions: 1

    {0.0.0.00000000}.{c05f2f54-7294-422a-bb0d-8d690c365b73}|#%b{917F8618-9C57-4720-9B7C-88CA45FC983B} is the session identifier, which should be considered opaque.  0.215705 is the value of the peak meter, ranging from 0 to 1 linearly in amplitude.  If you are populating a visual peak meter with this information, you will need to apply a curve.

    Source and binaries attached.  Pseudocode follows:

    CoCreate(IMMDeviceEnumerator);
    IMMDeviceEnumerator::GetDefaultAudioEndpoint;
    IMMDevice::Activate(IAudioSessionManager2);
    IAudioSessionManager2::GetSessionEnumerator;
    for (each session) {
        IAudioSessionEnumerator::GetSession
        IAudioSessionControl::GetState
        if the state is anything but "active", skip to the next session
        QI IAudioSessionControl to IAudioSessionControl2
        IAudioSessionControl2::GetSessionIdentifier
        QI IAudioSessionControl to IAudioMeterInformation
        IAudioMeterInformation::GetPeakValue
        Log the session identifier and the peak value
    }

  • Matthew van Eerde's web log

    Linearity of Windows volume APIs - IAudioMeterInformation and full-scale signals

    • 6 Comments

    We have talked about some of the volume APIs Windows exposes. We have also talked about what it means for a volume control to be linear in magnitude, linear in power, or linear in dB.

    The attachment to this blog post contains:

    • An app I wrote to exercise the IAudioStreamVolume, ISimpleAudioVolume, IChannelAudioVolume, and IAudioEndpointVolume APIs
    • An Excel spreadsheet with the analysis of the output of the app

    Let's start by looking at what the app does.

    >volume-linearity.exe
    volume-linearity.exe --signal | --stream | --session | --channel |
        --endpoint-db | --capture
    --signal varies the amplitude of the generated signal from 0 to 1
    --stream varies the IAudioStreamVolume from 0 to 1
    --session varies the ISimpleAudioVolume from 0 to 1
    --channel varies the IChannelAudioVolume from 0 to 1
    --endpoint-db varies the IAudioEndpointVolume from X dB to Y dB
        where X and Y are the min and max values supported
    --capture varies session, channel, and endpoint-db volumes
        on the default capture device

    Let's look at --signal mode first. The app plays a square wave at amplitudes from 0 (silence) to 1 (full-scale), varying in a linearly in magnitude. It takes periodic readings using IAudioMeterInformation to see what the peak values on the other side of the audio engine are. The IAudioMeterInformation API returns readings that are also linear in magnitude, so the response graph looks nice and linear:

    Well... wait a second. What happened at full scale? Let's get a closer look:

    When we attempt to play a square wave of amplitude greater than about 0.985, the meter reveals that what we get out of the audio engine is not quite what we put in. The volume has been capped. What's going on?

    If we look at the list of WASAPI Audio Processing objects, notice that one of them is called CAudioLimiter. The job of this APO is to take its input and produce output that is limited to the range (-1, 1). When it sees that signals are getting too close to the edge, it steps in and pulls the signal closer to the center.

    For this reason, the other modes of volume-linearity.exe use a half-scale (-1/2, 1/2) square wave rather than a full-scale square wave.

    UPDATE June 1 2011: added --capture mode

  • Matthew van Eerde's web log

    How it works: xkcd sucks at math

    • 6 Comments

     

    I'm a casual reader of Randall Munroe's xkcd web comic.  I'm partial especially to the artistic episodes.

    I was reading the "How it Works" episode, reproduced here:

     How it Works

    Fine, a noble sentiment (women in the hard sciences are unfairly targeted) and well executed.

    As is my wont, I then went for the extra bit of juiciness to the episode by hovering over the image, and what do I find?

    It's pi plus C, of course.

    ... OK, fine.  A good reference to an in-joke in the mathematical community.  Well done.

    ... except I can't stomach the idea of "π plus a constant."  It just doesn't sit right.  And there's something wrong about the equation on the board.  It's missing something... else.

    The equation as it appears on the board:

     S(x^2) = pi

    ... and the suggested improvement:

     S(x^2) = pi + C

    Yeah.  I still don't buy it.

    Under the premise that the best way to spoil a good joke is to analyze it, let's see if we can figure out what our characters are trying to do with this integral, shall we?

    Well, there are integrals and integrals.  There are at least three common uses of integrals:

    1. Find the antiderivative of a given function for later use.
    2. Evaluate the antiderivative at upper and lower limits and subtract to find the area under the curve.
    3. Do a contour integration to evaluate a function over a known region.

    The "plus a constant" comment applies exclusively to the first use.  The function y = x2 is analytic everywhere and so is not likely to be interesting for a contour integral.  But the second use seems to be very appropriate to this "integral equals a constant" equation:

    The first thing to do is find the antiderivative.  This can occasionally be very difficult, but in this case we have a textbook function which is the first recipe anyone memorizes:

     S(x^p)dx = x^(p + 1) / (p + 1) + C

    The above formula holds for all p except for p = -1.   That one's a little tricky because the denominator above would be 0.  For completeness, here's the case with p = -1:

     S(1/x)dx = ln x + C

    A little faith is required if you haven't seen the derivation, but it works out.  (This is as good a definition as any of ln x, FWIW.)

    Indeed, the antiderivative is a whole family of functions which differ only by a constant.  But if we're going to evaluate it at certain limits, we need to (eventually) know what the limits are.  Let's call them a and b for now:

    S(x^p)dx:a->b = (b^(p+1) - a^(p+1)) / (p+1)

    (Note in particular that the evaluation doesn't depend on which of the family of functions we chose... C cancels out.   This is my problem with the comment.)

    Setting p = 2 we still have a single equation in two unknowns (a and b).  Still need more information.

    Well, let's think about it for a minute.  Many functions have "natural" points of evaluation.  For x2 this is 0.  For 1/x it's 1.   What if we set a = 0?  The natural-ness of 0 as a base point of evaluation is made clear from a graph of y = x2:

    Graph

    If we set p = 2 and a = 0, then the equation above reduces to the solvable problem:

     (1/3)b^3 = pi

    The solution is now trivial:

    b = (3 pi)^(1/3) = 2.112307-ish

    So we can complete the original blackboard equation as follows:

    S(x^2)dx:0->(3 pi)^(1/3) = pi

    or in fuller generality as

     S(x^2)dx:a->(3 pi + a^3)^(1/3) = pi

    for any a.

    (I'm sticking with the a = 0 solution, myself.)

  • Matthew van Eerde's web log

    Sample: find out if your default audio playback and audio capture devices are on the same hardware

    • 5 Comments

    vbBretty on the audio forums asked how to tell if a given audio output and a given audio input were on the same physical audio card.

    I found the exercise interesting enough to share here:

    // main.cpp

    #define INITGUID

    #include <windows.h>
    #include <tchar.h>

    const GUID GUID_NULL = { 0 };

    #include <atlstr.h>
    #include <mmdeviceapi.h>
    #include <devicetopology.h>
    #include <functiondiscoverykeys.h>

    #define LOG(formatstring, ...) _tprintf(formatstring _T("\n"), __VA_ARGS__)

    // helper class to CoInitialize/CoUninitialize
    class CCoInitialize {
    private:
        HRESULT m_hr;
    public:
        CCoInitialize(PVOID pReserved, HRESULT &hr)
            : m_hr(E_UNEXPECTED) { hr = m_hr = CoInitialize(pReserved); }
        ~CCoInitialize() { if (SUCCEEDED(m_hr)) { CoUninitialize(); } }
    };

    // helper class to CoTaskMemFree
    class CCoTaskMemFreeOnExit {
    private:
        PVOID m_p;
    public:
        CCoTaskMemFreeOnExit(PVOID p) : m_p(p) {}
        ~CCoTaskMemFreeOnExit() { CoTaskMemFree(m_p); }
    };

    // helper class to PropVariantClear
    class CPropVariantClearOnExit {
    private:
        PROPVARIANT *m_p;
    public:
        CPropVariantClearOnExit(PROPVARIANT *p) : m_p(p) {}
        ~CPropVariantClearOnExit() { PropVariantClear(m_p); }
    };

    // find the default capture and render audio devices
    // determine whether they are on the same audio hardware
    int _tmain() {
        HRESULT hr = S_OK;

        // initialize COM
        CCoInitialize ci(NULL, hr);
        if (FAILED(hr)) {
            LOG(_T("CoInitialize failed: hr = 0x%08x"), hr);
            return __LINE__;
        }

        // get enumerator
        CComPtr<IMMDeviceEnumerator> pMMDeviceEnumerator;
        hr = pMMDeviceEnumerator.CoCreateInstance(__uuidof(MMDeviceEnumerator));
        if (FAILED(hr)) {
            LOG(_T("CoCreateInstance(IMMDeviceEnumerator) failed: hr = 0x%08x"), hr);
            return __LINE__;
        }

        // get default render/capture endpoints
        CComPtr<IMMDevice> pRenderEndpoint;
        hr = pMMDeviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &pRenderEndpoint);
        if (FAILED(hr)) {
            LOG(_T("GetDefaultAudioEndpoint(eRender, eConsole) failed: hr = 0x%08x"), hr);
            return __LINE__;
        }
        
        CComPtr<IMMDevice> pCaptureEndpoint;
        hr = pMMDeviceEnumerator->GetDefaultAudioEndpoint(eCapture, eConsole, &pCaptureEndpoint);
        if (FAILED(hr)) {
            LOG(_T("GetDefaultAudioEndpoint(eCapture, eConsole) failed: hr = 0x%08x"), hr);
            return __LINE__;
        }

        // get endpoint device topologies
        CComPtr<IDeviceTopology> pRenderEndpointTopology;
        hr = pRenderEndpoint->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL, (void**)&pRenderEndpointTopology);
        if (FAILED(hr)) {
            LOG(_T("Render endpoint Activate(IDeviceTopology) failed: hr = 0x%08x"), hr);
            return __LINE__;
        }

        CComPtr<IDeviceTopology> pCaptureEndpointTopology;
        hr = pCaptureEndpoint->Activate(__uuidof(IDeviceTopology), CLSCTX_ALL, NULL, (void**)&pCaptureEndpointTopology);
        if (FAILED(hr)) {
            LOG(_T("Capture endpoint Activate(IDeviceTopology) failed: hr = 0x%08x"), hr);
            return __LINE__;
        }

        // get KS filter device IDs
        LPWSTR szRenderFilterId;
        CComPtr<IConnector> pConnector;
        hr = pRenderEndpointTopology->GetConnector(0, &pConnector);
        if (FAILED(hr)) {
            LOG(_T("Render endpoint topology GetConnector(0) failed: hr = 0x%08x"), hr);
            return __LINE__;
        }

        hr = pConnector->GetDeviceIdConnectedTo(&szRenderFilterId);
        if (FAILED(hr)) {
            LOG(_T("Render connector GetDeviceIdConnectedTo() failed: hr = 0x%08x"), hr);
            return __LINE__;
        }
        CCoTaskMemFreeOnExit freeRender(szRenderFilterId);
        LOG(_T("KS filter ID for render endpoint:\n\t%ls"), szRenderFilterId);

        LPWSTR szCaptureFilterId;
        pConnector = NULL;
        hr = pCaptureEndpointTopology->GetConnector(0, &pConnector);
        if (FAILED(hr)) {
            LOG(_T("Capture endpoint topology GetConnector(0) failed: hr = 0x%08x"), hr);
            return __LINE__;
        }

        hr = pConnector->GetDeviceIdConnectedTo(&szCaptureFilterId);
        if (FAILED(hr)) {
            LOG(_T("Capture connector GetDeviceIdConnectedTo() failed: hr = 0x%08x"), hr);
            return __LINE__;
        }
        CCoTaskMemFreeOnExit freeCapture(szCaptureFilterId);
        LOG(_T("KS filter ID for capture endpoint:\n\t%ls"), szCaptureFilterId);

        // get IMMDevices for each associated devnode
        CComPtr<IMMDevice> pRenderDevnode;
        hr = pMMDeviceEnumerator->GetDevice(szRenderFilterId, &pRenderDevnode);
        if (FAILED(hr)) {
            LOG(_T("Getting render devnode via IMMDeviceEnumerator::GetDevice(\"%ls\") failed: hr = 0x%08x"), szRenderFilterId, hr);
            return __LINE__;
        }
        
        CComPtr<IMMDevice> pCaptureDevnode;
        hr = pMMDeviceEnumerator->GetDevice(szCaptureFilterId, &pCaptureDevnode);
        if (FAILED(hr)) {
            LOG(_T("Getting capture devnode via IMMDeviceEnumerator::GetDevice(\"%ls\") failed: hr = 0x%08x"), szCaptureFilterId, hr);
            return __LINE__;
        }
        LOG(_T(""));

        // open property set on each devnode
        CComPtr<IPropertyStore> pRenderDevnodePropertyStore;
        hr = pRenderDevnode->OpenPropertyStore(STGM_READ, &pRenderDevnodePropertyStore);
        if (FAILED(hr)) {
            LOG(_T("Getting render devnode property store failed: hr = 0x%08x"), hr);
            return __LINE__;
        }

        CComPtr<IPropertyStore> pCaptureDevnodePropertyStore;
        hr = pCaptureDevnode->OpenPropertyStore(STGM_READ, &pCaptureDevnodePropertyStore);
        if (FAILED(hr)) {
            LOG(_T("Getting capture devnode property store failed: hr = 0x%08x"), hr);
            return __LINE__;
        }

        // get PKEY_Device_InstanceId property
        PROPVARIANT varRenderInstanceId; PropVariantInit(&varRenderInstanceId);
        CPropVariantClearOnExit clearRender(&varRenderInstanceId);
        hr = pRenderDevnodePropertyStore->GetValue(PKEY_Device_InstanceId, &varRenderInstanceId);
        if (FAILED(hr)) {
            LOG(_T("Render devnode property store GetValue(PKEY_Device_InstanceId) failed: hr = 0x%08x"), hr);
            return __LINE__;
        }
        if (VT_LPWSTR != varRenderInstanceId.vt) {
            LOG(_T("Render instance id variant type is %u - expected VT_LPWSTR"), varRenderInstanceId.vt);
            return __LINE__;
        }
        LOG(_T("Instance Id of default render device: %ls"), varRenderInstanceId.pwszVal);

        PROPVARIANT varCaptureInstanceId; PropVariantInit(&varCaptureInstanceId);
        CPropVariantClearOnExit clearCapture(&varCaptureInstanceId);
        hr = pCaptureDevnodePropertyStore->GetValue(PKEY_Device_InstanceId, &varCaptureInstanceId);
        if (FAILED(hr)) {
            LOG(_T("Capture devnode property store GetValue(PKEY_Device_InstanceId) failed: hr = 0x%08x"), hr);
            return __LINE__;
        }
        if (VT_LPWSTR != varCaptureInstanceId.vt) {
            LOG(_T("Capture instance id variant type is %u - expected VT_LPWSTR"), varCaptureInstanceId.vt);
            return __LINE__;
        }
        LOG(_T("Instance Id of default capture device: %ls"), varCaptureInstanceId.pwszVal);

        LOG(_T(""));

        // paydirt
        if (0 == _wcsicmp(varRenderInstanceId.pwszVal, varCaptureInstanceId.pwszVal)) {
            LOG(_T("Default render and capture audio endpoints ARE on the same hardware."));
        } else {
            LOG(_T("Default render and capture audio endpoints ARE NOT on the same hardware."));
        }

        return 0;
    }

  • Matthew van Eerde's web log

    Bug - jack detection on Windows 7 hdaudio.sys mixed capture

    • 5 Comments

    There's a bug that got away from me in Windows 7's HD Audio class driver (hdaudio.sys.)

    Before I explain the bug, a few caveats:

    If you're using Vista, there's no problem.  This only affects Windows 7.

    If you use the third-party audio driver, there's no problem.  This only affects the Microsoft HD Audio class driver (hdaudio.sys.)

    If your recording devices (mic and line in) are independent (can capture from both at the same time) or muxed (can capture from one or the other but not from their sum) then you're fine; this only affects mixed capture (can capture from one, from the other, or from their sum, but not from both sides of the mix independently.)

    If either of your recording devices doesn't support jack presence detection then you're fine; this only affects mixed capture where both sides of the mix support jack presence detection.

    Here's the bug.

    1. There are various ways to "reset" the system and bring the OS recording device state into synchronization with the hardware state
      • reboot
      • reset the HD Audio controller
      • reinstall the audio driver
      • restart the AudioEndpointBuilder service
      • unplug all recording devices in the mix
    2. The first state change (plug or unplug) after a reset works
    3. The second and further state changes are not recognized by the OS until a reset.

    Here's a sample state diagram assuming a mixed Mic and Line In (click to view full-size:)

     

    Workarounds:

    Install the third-party audio driver instead of the HD Audio class driver.

    Use audio extension cables to fool the jack presence detection hardware into thinking something is always plugged in.

  • Matthew van Eerde's web log

    My unattend.xml file

    • 5 Comments

    As a tester on Windows 8 I install Windows on my dev machine very frequently.

    I use F12 to boot from the network into WinPE, then I run a .bat file which looks something like this:

    @echo off
    setlocal enabledelayedexpansion

    set PASSWORD_ADMIN=(redacted)
    set PASSWORD_MATEER=(redacted)
    set LICENSE_KEY=(redacted)
    set FANCYLANG=qps-plocm

    del unattend-processed.xml
    for /f "usebackq delims=" %%l in (`type unattend-template.xml`) do (
        set line=%%l
        set line=!line:#PASSWORD_ADMIN#=%PASSWORD_ADMIN%!
        set line=!line:#PASSWORD_MATEER#=%PASSWORD_MATEER%!
        set line=!line:#LICENSE_KEY#=%LICENSE_KEY%!
        set line=!line:#FANCY_LANG#=%FANCY_LANG%!
        echo !line! >> unattend-processed.xml
    )

    setup.exe /unattend:unattend-processed.xml

    This takes my template unattend.xml file, injects various parameters, and calls setup.exe.

    The template unattend.xml follows in all of its glory.  We'll take a closer look at some of the things in future posts.

    <?xml version="1.0" encoding="utf-8"?>
    <unattend xmlns="urn:schemas-microsoft-com:unattend">
        <settings pass="windowsPE">
            <component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
                <UserData>
                    <ProductKey>
                        <WillShowUI>OnError</WillShowUI>
                        <Key>#LICENSE_KEY#</Key>
                    </ProductKey>
                    <AcceptEula>true</AcceptEula>
                    <FullName>Matthew van Eerde</FullName>
                    <Organization>Microsoft</Organization>
                </UserData>
                <DiskConfiguration>
                    <Disk wcm:action="add">
                        <CreatePartitions>
                            <CreatePartition wcm:action="add">
                                <Order>1</Order>
                                <Extend>true</Extend>
                                <Type>Primary</Type>
                            </CreatePartition>
                        </CreatePartitions>
                        <WillWipeDisk>true</WillWipeDisk>
                        <DiskID>0</DiskID>
                    </Disk>
                </DiskConfiguration>
                <ImageInstall>
                    <OSImage>
                        <WillShowUI>OnError</WillShowUI>
                        <InstallToAvailablePartition>true</InstallToAvailablePartition>
                    </OSImage>
                </ImageInstall>
            </component>
            <component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
                <SetupUILanguage>
                    <UILanguage>#FANCY_LANG#</UILanguage>
                </SetupUILanguage>
                <InputLocale>0409:00000409</InputLocale>
                <UserLocale>en-US</UserLocale>
                <SystemLocale>en-US</SystemLocale>
                <UILanguage>#FANCY_LANG#</UILanguage>
            </component>
        </settings>
        <settings pass="specialize">
            <component name="Microsoft-Windows-UnattendedJoin" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
                <Identification>
                    <Credentials>
                        <Domain>redmond.corp.microsoft.com</Domain>
                        <Password>#PASSWORD_MATEER#</Password>
                        <Username>MatEer</Username>
                    </Credentials>
                    <JoinDomain>ntdev.corp.microsoft.com</JoinDomain>
                </Identification>
            </component>
            <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
                <ComputerName>MATEER-Q</ComputerName>
                <RegisteredOwner>Matthew van Eerde</RegisteredOwner>
                <RegisteredOrganization>Microsoft</RegisteredOrganization>
            </component>
            <component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
                <fDenyTSConnections>false</fDenyTSConnections>
            </component>
            <component name="Networking-MPSSVC-Svc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
                <FirewallGroups>
                    <FirewallGroup wcm:action="add" wcm:keyValue="RemoteDesktop">
                        <Active>true</Active>
                        <Profile>all</Profile>
                        <Group>@FirewallAPI.dll,-28752</Group>
                    </FirewallGroup>
                </FirewallGroups>
            </component>
        </settings>
        <settings pass="oobeSystem">
            <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
                <OOBE>
                    <HideEULAPage>true</HideEULAPage>
                    <NetworkLocation>Work</NetworkLocation>
                    <ProtectYourPC>3</ProtectYourPC>
                    <HideOnlineAccountScreens>true</HideOnlineAccountScreens>
                    <HideLocalAccountScreen>true</HideLocalAccountScreen>
                    <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
                </OOBE>
                <UserAccounts>
                    <LocalAccounts>
                        <LocalAccount wcm:action="add">
                            <Password>
                                <Value>#PASSWORD_ADMIN#</Value>
                                <PlainText>true</PlainText>
                            </Password>
                            <Name>Admin</Name>
                            <Group>Administrators</Group>
                        </LocalAccount>
                    </LocalAccounts>
                    <DomainAccounts>
                        <DomainAccountList wcm:action="add">
                            <Domain>redmond.corp.microsoft.com</Domain>
                            <DomainAccount wcm:action="add">
                                <Group>RemoteDesktopUsers</Group>
                                <Name>MatEer</Name>
                            </DomainAccount>
                        </DomainAccountList>
                    </DomainAccounts>
                </UserAccounts>
                <TimeZone>Pacific Standard Time</TimeZone>
                <AutoLogon>
                    <Password>
                        <Value>#PASSWORD_MATEER#</Value>
                        <PlainText>true</PlainText>
                    </Password>
                    <Domain>redmond.corp.microsoft.com</Domain>
                    <Enabled>true</Enabled>
                    <LogonCount>1</LogonCount>
                    <Username>MatEer</Username>
                </AutoLogon>
            </component>
        </settings>
    </unattend>

     

  • Matthew van Eerde's web log

    Sample: how to enumerate waveIn and waveOut devices on your system

    • 4 Comments

    This shows how to call waveInGetNumDevs, waveInGetDevCaps, waveOutGetNumDevs, and waveOutGetDevCaps.

    // main.cpp

    #include <windows.h>
    #include <mmsystem.h>
    #include <stdio.h>

    #define LOG(format, ...) wprintf(format L"\n", __VA_ARGS__)

    int _cdecl wmain() {

        UINT devs = waveInGetNumDevs();
        LOG(L"waveIn devices: %u", devs);
        for (UINT dev = 0; dev < devs; dev++) {
            WAVEINCAPS caps = {};
            MMRESULT mmr = waveInGetDevCaps(dev, &caps, sizeof(caps));
           
            if (MMSYSERR_NOERROR != mmr) {
                 LOG(L"waveInGetDevCaps failed: mmr = 0x%08x", mmr);
                 return mmr;
            }
           
            LOG(
                L"-- waveIn device #%u --\n"
                L"Manufacturer ID: %u\n"
                L"Product ID: %u\n"
                L"Version: %u.%u\n"
                L"Product Name: %s\n"
                L"Formats: 0x%x\n"
                L"Channels: %u\n"
                L"Reserved: %u\n"
                ,
                dev,
                caps.wMid,
                caps.wPid,
                caps.vDriverVersion / 256, caps.vDriverVersion % 256,
                caps.szPname,
                caps.dwFormats,
                caps.wChannels,
                caps.wReserved1
            );
        }

        devs = waveOutGetNumDevs();
        LOG(L"waveOut devices: %u", devs);
        for (UINT dev = 0; dev < devs; dev++) {
            WAVEOUTCAPS caps = {};
            MMRESULT mmr = waveOutGetDevCaps(dev, &caps, sizeof(caps));
           
            if (MMSYSERR_NOERROR != mmr) {
                 LOG(L"waveOutGetDevCaps failed: mmr = 0x%08x", mmr);
                 return mmr;
            }
           
            LOG(
                L"-- waveOut device #%u --\n"
                L"Manufacturer ID: %u\n"
                L"Product ID: %u\n"
                L"Version: %u.%u\n"
                L"Product Name: %s\n"
                L"Formats: 0x%x\n"
                L"Channels: %u\n"
                L"Reserved: %u\n"
                L"Support: 0x%x\n"
                L"%s%s%s%s%s"
                ,
                dev,
                caps.wMid,
                caps.wPid,
                caps.vDriverVersion / 256, caps.vDriverVersion % 256,
                caps.szPname,
                caps.dwFormats,
                caps.wChannels,
                caps.wReserved1,
                caps.dwSupport,
                    ((caps.dwSupport & WAVECAPS_LRVOLUME) ?       L"\tWAVECAPS_LRVOLUME\n" :       L""),
                    ((caps.dwSupport & WAVECAPS_PITCH) ?          L"\tWAVECAPS_PITCH\n" :          L""),
                    ((caps.dwSupport & WAVECAPS_PLAYBACKRATE) ?   L"\tWAVECAPS_PLAYBACKRATE\n" :   L""),
                    ((caps.dwSupport & WAVECAPS_VOLUME) ?         L"\tWAVECAPS_VOLUME\n" :         L""),
                    ((caps.dwSupport & WAVECAPS_SAMPLEACCURATE) ? L"\tWAVECAPS_SAMPLEACCURATE\n" : L"")
            );
        }

        return 0;
    }

    On my system this outputs:

    waveIn devices: 3
    -- waveIn device #0 --
    Manufacturer ID: 1
    Product ID: 65535
    Version: 0.0
    Product Name: Microphone (High Definition Aud
    Formats: 0xfffff
    Channels: 2
    Reserved: 0

    -- waveIn device #1 --
    Manufacturer ID: 1
    Product ID: 65535
    Version: 0.0
    Product Name: Digital Audio (S/PDIF) (High De
    Formats: 0xfffff
    Channels: 2
    Reserved: 0

    -- waveIn device #2 --
    Manufacturer ID: 1
    Product ID: 65535
    Version: 0.0
    Product Name: CD Audio (High Definition Audio
    Formats: 0xfffff
    Channels: 2
    Reserved: 0

    waveOut devices: 2
    -- waveOut device #0 --
    Manufacturer ID: 1
    Product ID: 65535
    Version: 0.0
    Product Name: Headphones (High Definition Aud
    Formats: 0xfffff
    Channels: 2
    Reserved: 0
    Support: 0x2e
     WAVECAPS_LRVOLUME
     WAVECAPS_PLAYBACKRATE
     WAVECAPS_VOLUME
     WAVECAPS_SAMPLEACCURATE

    -- waveOut device #1 --
    Manufacturer ID: 1
    Product ID: 65535
    Version: 0.0
    Product Name: Digital Audio (S/PDIF) (High De
    Formats: 0xfffff
    Channels: 2
    Reserved: 0
    Support: 0x2e
     WAVECAPS_LRVOLUME
     WAVECAPS_PLAYBACKRATE
     WAVECAPS_VOLUME
     WAVECAPS_SAMPLEACCURATE

     

    Source and binaries attached.

  • Matthew van Eerde's web log

    How to validate and log a WAVEFORMATEX

    • 4 Comments

    Last time I wrote about how to query audio endpoint properties.

    This time, a few words about WAVEFORMATEX structures.  WAVEFORMATEX is a variable-sized structure - it can be as small as a PCMWAVEFORMAT or larger than a WAVEFORMATEXTENSIBLE.

    If the wFormatTag member (the first field) is WAVE_FORMAT_PCM, then you should assume that it is a PCMWAVEFORMAT.  In particular you should not touch the cbSize field since that may not be memory you own!

    If wFormatTag is anything else, you can look at the cbSize to see how much bigger than a WAVEFORMATEX the structure is.  For example, if the wFormatTag is WAVE_FORMAT_EXTENSIBLE, you have the right to expect that cbSize be at least sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX).

    I updated the attachment to the post linked above.  Here's the new output:

    PKEY_AudioEngine_DeviceFormat VT_BLOB of size 40
        WAVEFORMATEXTENSIBLE
        Format (
            wFormatTag: 65534 (WAVE_FORMAT_EXTENSIBLE)
            nChannels: 4
            nSamplesPerSec: 16000
            nAvgBytesPerSec: 128000
            nBlockAlign: 8
            wBitsPerSample: 16
            cbSize: 22
        )
        Samples (
            wValidBitsPerSample / wSamplesPerBlock / wReserved: 16
        )
        dwChannelMask: 0x0
        SubFormat: {00000001-0000-0010-8000-00AA00389B71} (KSDATAFORMAT_SUBTYPE_PCM)

    And here's the code that produces it:

    HRESULT LogWaveFormat(LPCWAVEFORMATEX pWfx, UINT nBytes) {
        if (NULL == pWfx) {
            ERR(L"LogWaveFormat called with a NULL pointer");
            return E_POINTER;
        }
        
        if (nBytes < sizeof(PCMWAVEFORMAT)) {
            ERR(L"Wave format is only %u bytes, smaller even than a PCMWAVEFORMAT (%Iu bytes)", nBytes, sizeof(PCMWAVEFORMAT));
            return E_INVALIDARG;
        } else if (nBytes == sizeof(PCMWAVEFORMAT)) {
            const PCMWAVEFORMAT *pPcm = reinterpret_cast<const PCMWAVEFORMAT *>(pWfx);
            
            // PCMWAVEFORMAT must have a wFormatTag of WAVE_FORMAT_PCM
            if (WAVE_FORMAT_PCM != pPcm->wf.wFormatTag) {
                ERR(L"PCMWAVEFORMAT has invalid format tag %u (expected %u)", pPcm->wf.wFormatTag, WAVE_FORMAT_PCM);
                return E_INVALIDARG;
            }
            
            LOG(
                L"    PCMWAVEFORMAT\n"
                L"        wf.wFormatTag: %u (%s)\n"
                L"        wf.nChannels: %u\n"
                L"        wf.nSamplesPerSec: %u\n"
                L"        wf.nAvgBytesPerSec: %u\n"
                L"        wf.nBlockAlign: %u\n"
                L"        wBitsPerSample: %u",
                pPcm->wf.wFormatTag, StringFromWaveFormatTag(pPcm->wf.wFormatTag),
                pPcm->wf.nChannels,
                pPcm->wf.nSamplesPerSec,
                pPcm->wf.nAvgBytesPerSec,
                pPcm->wf.nBlockAlign,
                pPcm->wBitsPerSample
            );
            return S_OK;
        } else if (nBytes < sizeof(WAVEFORMATEX)) {
            ERR(
                L"Wave format is %u bytes, "
                L"bigger than a PCMWAVEFORMAT (%Iu bytes) "
                L"but smaller than a WAVEFORMATEX (%Iu bytes)",
                nBytes,
                sizeof(PCMWAVEFORMAT),
                sizeof(WAVEFORMATEX)
            );
            return E_INVALIDARG;
        } else if (nBytes != sizeof(WAVEFORMATEX) + pWfx->cbSize) {
            ERR(
                L"Wave format takes up %u bytes "
                L"but sizeof(WAVEFORMATEX) + pWfx->cbSize is %Iu bytes",
                nBytes,
                sizeof(WAVEFORMATEX) + pWfx->cbSize
            );
            return E_INVALIDARG;
        } else {
            // nBytes matches cbSize
            switch (pWfx->wFormatTag) {
               
                case WAVE_FORMAT_EXTENSIBLE:
                    {
                        if (pWfx->cbSize < sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)) {
                            ERR(
                                L"cbSize for a WAVE_FORMAT_EXTENSIBLE format must be at least "
                                L"sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX) (%Iu), "
                                L"not %u",
                                sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX),
                                pWfx->cbSize
                            );
                            return E_INVALIDARG;
                        }
                    
                        const WAVEFORMATEXTENSIBLE *pWfxExt = reinterpret_cast<const WAVEFORMATEXTENSIBLE *>(pWfx);
                        
                        LOG(
                            L"    WAVEFORMATEXTENSIBLE\n"
                            L"    Format (\n"
                            L"        wFormatTag: %u (%s)\n"
                            L"        nChannels: %u\n"
                            L"        nSamplesPerSec: %u\n"
                            L"        nAvgBytesPerSec: %u\n"
                            L"        nBlockAlign: %u\n"
                            L"        wBitsPerSample: %u\n"
                            L"        cbSize: %u\n"
                            L"    )\n"
                            L"    Samples (\n"
                            L"        wValidBitsPerSample / wSamplesPerBlock / wReserved: %u\n"
                            L"    )\n"
                            L"    dwChannelMask: 0x%x\n"
                            L"    SubFormat: {%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X} (%s)",
                            pWfxExt->Format.wFormatTag, StringFromWaveFormatTag(pWfxExt->Format.wFormatTag),
                            pWfxExt->Format.nChannels,
                            pWfxExt->Format.nSamplesPerSec,
                            pWfxExt->Format.nAvgBytesPerSec,
                            pWfxExt->Format.nBlockAlign,
                            pWfxExt->Format.wBitsPerSample,
                            pWfxExt->Format.cbSize,
                            pWfxExt->Samples.wValidBitsPerSample,
                            pWfxExt->dwChannelMask,
                            pWfxExt->SubFormat.Data1,
                            pWfxExt->SubFormat.Data2,
                            pWfxExt->SubFormat.Data3,
                            pWfxExt->SubFormat.Data4[0],
                            pWfxExt->SubFormat.Data4[1],
                            pWfxExt->SubFormat.Data4[2],
                            pWfxExt->SubFormat.Data4[3],
                            pWfxExt->SubFormat.Data4[4],
                            pWfxExt->SubFormat.Data4[5],
                            pWfxExt->SubFormat.Data4[6],
                            pWfxExt->SubFormat.Data4[7],
                            StringFromSubFormat(pWfxExt->SubFormat)
                        );

                        if (pWfx->cbSize > sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)) {
                            LOG(L"    Trailing bytes:\n");
                            return LogBytes(
                                reinterpret_cast<const BYTE *>(pWfx) + sizeof(WAVEFORMATEXTENSIBLE),
                                pWfx->cbSize - (sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX))
                            );
                        }
                        
                        return S_OK;
                    }
                    
                case WAVE_FORMAT_PCM:
                    if (pWfx->cbSize > 0) {
                        ERR(L"cbSize for a WAVE_FORMAT_PCM format must be 0, not %u", pWfx->cbSize);
                        return E_INVALIDARG;
                    }
                    // intentionally fall through
                default:
                    LOG(
                        L"    WAVEFORMATEX\n"
                        L"        wFormatTag: %u (%s)\n"
                        L"        nChannels: %u\n"
                        L"        nSamplesPerSec: %u\n"
                        L"        nAvgBytesPerSec: %u\n"
                        L"        nBlockAlign: %u\n"
                        L"        wBitsPerSample: %u\n"
                        L"        cbSize: %u",
                        pWfx->wFormatTag, StringFromWaveFormatTag(pWfx->wFormatTag),
                        pWfx->nChannels,
                        pWfx->nSamplesPerSec,
                        pWfx->nAvgBytesPerSec,
                        pWfx->nBlockAlign,
                        pWfx->wBitsPerSample,
                        pWfx->cbSize
                    );
                    
                    if (pWfx->cbSize > 0) {
                        LOG(L"    Trailing bytes:");
                        return LogBytes(reinterpret_cast<const BYTE *>(pWfx) + sizeof(WAVEFORMATEX), pWfx->cbSize);
                    }

                    return S_OK;
            }
        }
    }


  • Matthew van Eerde's web log

    How to install unsigned drivers

    • 4 Comments

    Most device drivers these days have signatures which allow the operating system to validate that the driver files have not been tampered with since the author sent them out into the world.

    It is still possible to run into an unsigned driver. If you try to install an unsigned driver, Windows will warn you at install time:

    IMPORTANT: if you see this warning, ask yourself this question - DO I TRUST THE SOURCE OF THIS DRIVER? If the answer is anything but a resounding YES, you should click "Don't install this software."

    Windows XP was, by and large, a 32-bit operating system. A 64-bit version of Windows XP was developed but it was not broadly released. At that time it was still fairly common to have unsigned drivers.

    Windows Vista was the first client release of Windows to have a widely deployed 64-bit release. This gave an opportunity to be more strict about enforcing driver signatures. For backwards compatibility, it was still possible to run unsigned drivers on the 32-bit version of Windows Vista (so the XP drivers would still work) but there was no such burden on Windows Vista 64-bit; so Windows Vista 64-bit was the first time that there was really a strong incentive to run with only signed drivers.

    Even so, there are still workarounds provided:

    • If you want to run an unsigned driver for one particular boot, you can hit F8 during boot1 and choose "Disable Driver Signature Enforcement". This allows unsigned drivers to load for this boot only.
    • If you're a device driver developer, it is expected that you will want to iterate on your particular driver; it is also expected that you will probably also have a kernel debugger attached. So driver signature enforcement is disabled if you have a kernel debugger attached.

    The most reliable method, however, and the one with the fewest side effects, is to... well... sign the driver!

    To sign a driver:

    1. Download the Windows Driver Kit.
    2. Make sure the driver .inf has a CatalogFile=MyCatalogFile.cat line (specify your own value for MyCatalogFile.cat). If one is missing you can add it to the [Version] section.
    3. Point inf2cat to the driver .inf file and it will make a .cat file for you. This .cat file will have an entry for every file pulled in by the .inf.
    4. Use SignTool to sign the .cat file.

    At this point you can see who says this driver is OK. Double-click the .cat file | View Signature | View Certificate | Certification Path and note the highest certificate in the chain:

    If you're a professional driver developer and you want your driver to ship on Windows systems, your next step would very likely be to use the Windows Logo Kit to run the Microsoft logo tests, and get an official "Microsoft says this driver has passed the logo tests" signature for your .cat file. (The difference is that the top certificate in the chain will be "Microsoft Root Authority" rather than "Microsoft Test Root Authority.") A full description of this process lies outside the scope of this blog post.

    But if you're just developing a driver for your personal use, you would probably skip this step. Instead of getting a "Microsoft says this driver is OK" signature, you can add the root certificate for your signing certificate to the "trusted root certificates" certificate store on the machine you want to load the driver on.

    If the root certificate in question is the "Microsoft Test Root Authority", you can run "bcdedit -set testsigning on" and reboot.

    If it's some other certificate (for example, if you generated a self-signed certificate) then you need to add it to the trusted certificate store by using certmgr.exe or by going to Internet Explorer | Internet Options | Content | Certificates.

    If you have everything right, then installing the driver should not pop up the "driver is unsigned" warning. If you still get the warning, looking at C:\windows\inf \setupapi.dev.log can give some clues as to what is wrong... whether there is a driver file that is not listed in the catalog file, or the catalog file's signature does not lead back to a trusted root certificate, or something else.

    1 In Windows 8 Developer Preview, the F8 experience has been reworked. Hit Shift-F8 instead.2
    2 Some BIOSes have an issue where Shift is not sent to Windows this early. If you have Windows 8 Developer Preview installed one of these machines there's another way - hit F10 during boot to get to the "Edit Boot Options" screen. Add /DISABLE_INTEGRITY_CHECKS to the boot flags.

  • Matthew van Eerde's web log

    Spot the bug - Outlook "block sender" icon

    • 4 Comments

    Back in Windows 7 I did a fair amount of UI testing for the Sound team.

    One of the problems with testing UI is it gives you an unconscious attention to detail that's difficult to unlearn.

    For example, this Outlook bug didn't bother me before... but now it does.

    http://blogs.msdn.com/photos/matthew_van_eerde/images/9995171/original.aspx

    Exercise: what is the bug?

    Answer next week.

    EDIT 4/19/201: Answer.

  • Matthew van Eerde's web log

    Jeopardy conundrum

    • 4 Comments

    What happens if Double Jeopardy ends and all contestants are in the red (or have no money?)

    Do they skip Final Jeopardy and pick three new people for the next show?

  • Matthew van Eerde's web log

    How to write a leading apostrophe in Word

    • 4 Comments

    Disclaimer: I don't work on the Office team.

    Word has a smart quotes feature where it will automagically transform

    straight "double quotes," 'single quotes,' and greengrocer's apostrophes

    into

    curly “double quotes,” ‘single quotes,’ and greengrocer’s apostrophes

    as you type.  You send a Unicode Character 'APOSTROPHE' (U+0027) to Word, and Word turns it into a Unicode Character 'LEFT SINGLE QUOTATION MARK' (U+2018) or a Unicode Character 'RIGHT SINGLE QUOTATION MARK' (U+2019) as appropriate.  If you type it at the beginning of a word, it's an opening-single-quote; if you type it in the middle of a word, it's an apostrophe; if you type it at the end, it's either an apostrophe or a closing-single-quote, but both are the same character, so it doesn't matter.

    Right?

    Usually.

    The apostrophe is occasionally used at the beginning of a word, to mark elided letters.  Word has trouble with this.

    Twas brillig, and the slithy toves did gyre and gimbol in the wabe.

        -- Jabberwocky

    I want to give the Word folks credit here.  A common use of an apostrophe at the beginning of a word is to abbreviate a year.  Word gets this right (I'm using Word 2010 with default settings:)

    Check it out: a 57 Chevy!

    But Word also gets it wrong when you want to single-quote a clause that begins with a number:

    “They were singing 99 bottles of beer on the wall’.”

    Also, if you pluralize the date, Word gets suckered:

    10 models on sale! Check out the new 11s!

    Little nifties, from the 50s, innocent and sweet;
    Sexy ladies from the 80s, who are indiscreet
        -- 42nd Street

    For those times when Word gets it wrong, here's how to fix it.

    If you want to type a word that starts with an apostrophe:

    1. Type the apostrophe.  Word assumes you want an opening quote.  Fine
    2. Type the apostrophe again.  Word shrugs its shoulders and gives you a closing quote in a little soixante-neuf of punctuation.
    3. Type the magic sequence:
      1. Left arrow
      2. Backspace
      3. Right arrow
    4. Et voilà.  Continue typing your word.
      Twas 

    If you want to use an opening single quote with a sentence that starts with a number:

    1. Type the opening quote and the number in its entirety.  So far, so good.
      99
    2. Type the word breaker (usually a space.)  Word "helpfully" turns the opening single quote into an apostrophe.
      99 
    3. Invoke "Undo" via your favorite mechanism (I prefer Ctrl-Z since I'm already on the keyboard.)
      99 
    4. Et voilà.  Continue typing your quoted clause.
      99 bottles of beer on the wall

    That "Undo" tip is actually a fairly powerful curative against all sorts of Word voodoo.

  • Matthew van Eerde's web log

    Spot the bug - control flow macro

    • 4 Comments

    Found this in a code review.

    Well, in all candor, I didn't find it; our automated code review tool found it.  But I'm taking credit for it because I read the output of the automated code review tool. :-)

    Code rewritten because I'm too lazy to look it up.

    // macros.h
    #define ENSURE_EQUAL(x, y) if (x != y) { bail(); }

    // implementation.cpp
    ...
    hr = foo();
    ENSURE_EQUAL( foo_supported() ? S_OK : E_FOO_NOT_SUPPORTED, hr );
    ...

Page 1 of 6 (138 items) 12345»