Matthew van Eerde's web log

  • Matthew van Eerde's web log

    How to enumerate WASAPI Audio Processing Objects (APOs) on your system

    • 0 Comments

    Source and binaries (amd64 and x86) attached.

    Pseudocode:

    main() {
        UINT32 nCount;
        EnumerateAPOs(myCallback, &nCount);
        print nCount;
    }

    ...

    // called once for each APO
    myCallback(PAPO_REG_PROPERTIES props, PVOID pnCount) {
        print props;
        (*pnCount)++;
    }

    Gotcha: the APO_REG_PROPERTIES structure is variable-size.  If you want to store the structure for later use, you need to make the copy before the callback function exits, and make sure add on the size of any additional interface IDs beyond the first (the first is included in the structure) if you want to store them too.

    Output on my system:

    >apoenum.exe
    -- APO properties for CAudioVolume --
        clsid: {06587E71-F043-403A-BF49-CB591BA6E103}
        Flags: 0x0000000f
            APO_FLAG_INPLACE
            APO_FLAG_SAMPLESPERFRAME_MUST_MATCH
            APO_FLAG_FRAMESPERSECOND_MUST_MATCH
            APO_FLAG_BITSPERSAMPLE_MUST_MATCH
        szFriendlyName: "CAudioVolume"
        szCopyrightInfo: "copyright (c) 2003 microsoft corporation"
        u32MajorVersion: 1
        u32MinorVersion: 0
        u32MinInputConnections: 1
        u32MaxInputConnections: 1
        u32MinOutputConnections: 1
        u32MaxOutputConnections: 1
        u32MaxInstances: 4294967295
        u32NumAPOInterfaces: 1
        #1: {D81229B1-5A43-480C-92F7-BE0F7F4EAB60}

    -- APO properties for CAudioConstrictor --
        clsid: {07252659-BB6B-4B79-B78B-623F6699A579}
        Flags: 0x0000000f
            APO_FLAG_INPLACE
            APO_FLAG_SAMPLESPERFRAME_MUST_MATCH
            APO_FLAG_FRAMESPERSECOND_MUST_MATCH
            APO_FLAG_BITSPERSAMPLE_MUST_MATCH
        szFriendlyName: "CAudioConstrictor"
        szCopyrightInfo: "Copyright (c) 2003 Microsoft Corporation"
        u32MajorVersion: 1
        u32MinorVersion: 0
        u32MinInputConnections: 1
        u32MaxInputConnections: 1
        u32MinOutputConnections: 1
        u32MaxOutputConnections: 1
        u32MaxInstances: 4294967295
        u32NumAPOInterfaces: 1
        #1: {6B54B2B5-45EE-4F11-9935-9EC3183AD534}

    -- APO properties for caudiomixer --
        clsid: {12DD4DBB-532B-4FCE-8653-74CDB9C8FE5A}
        Flags: 0x0000000e
            APO_FLAG_SAMPLESPERFRAME_MUST_MATCH
            APO_FLAG_FRAMESPERSECOND_MUST_MATCH
            APO_FLAG_BITSPERSAMPLE_MUST_MATCH
        szFriendlyName: "caudiomixer"
        szCopyrightInfo: "Copyright (C) 2003 Microsoft Corporation"
        u32MajorVersion: 1
        u32MinorVersion: 0
        u32MinInputConnections: 1
        u32MaxInputConnections: 1
        u32MinOutputConnections: 1
        u32MaxOutputConnections: 1
        u32MaxInstances: 4294967295
        u32NumAPOInterfaces: 1
        #1: {C23BDC7A-47F8-49A1-B750-692C35B532C3}

    -- APO properties for CAudioRateConvertCMPT --
        clsid: {27C98999-2895-4829-B080-5A8B65BD3DB0}
        Flags: 0x0000000a
            APO_FLAG_SAMPLESPERFRAME_MUST_MATCH
            APO_FLAG_BITSPERSAMPLE_MUST_MATCH
        szFriendlyName: "CAudioRateConvertCMPT"
        szCopyrightInfo: "Copyright (c) 2005 Microsoft Corporation"
        u32MajorVersion: 1
        u32MinorVersion: 0
        u32MinInputConnections: 1
        u32MaxInputConnections: 1
        u32MinOutputConnections: 1
        u32MaxOutputConnections: 1
        u32MaxInstances: 4294967295
        u32NumAPOInterfaces: 3
        #1: {788F7BE2-9C40-41C0-AF05-4393FBF409F9}
        #2: {C78841EF-516F-4516-B591-F04FA93783A9}
        #3: {7BA1DB8F-78AD-49CD-9591-F79D80A17C81}

    -- APO properties for caudiometer --
        clsid: {3DC09436-7D83-4BA0-ADDC-CD47F996C5BA}
        Flags: 0x0000000f
            APO_FLAG_INPLACE
            APO_FLAG_SAMPLESPERFRAME_MUST_MATCH
            APO_FLAG_FRAMESPERSECOND_MUST_MATCH
            APO_FLAG_BITSPERSAMPLE_MUST_MATCH
        szFriendlyName: "caudiometer"
        szCopyrightInfo: "copyright (c) 2003 microsoft corporation"
        u32MajorVersion: 1
        u32MinorVersion: 0
        u32MinInputConnections: 1
        u32MaxInputConnections: 1
        u32MinOutputConnections: 0
        u32MaxOutputConnections: 1
        u32MaxInstances: 4294967295
        u32NumAPOInterfaces: 1
        #1: {419B26E3-FA99-4408-83DE-CC1276EFA489}

    -- APO properties for caudioformatconvert --
        clsid: {3FD7F233-A716-472E-8F2F-C25954F34E96}
        Flags: 0x00000006
            APO_FLAG_SAMPLESPERFRAME_MUST_MATCH
            APO_FLAG_FRAMESPERSECOND_MUST_MATCH
        szFriendlyName: "caudioformatconvert"
        szCopyrightInfo: "copyright (c) 2003 microsoft corporation"
        u32MajorVersion: 1
        u32MinorVersion: 0
        u32MinInputConnections: 1
        u32MaxInputConnections: 1
        u32MinOutputConnections: 1
        u32MaxOutputConnections: 1
        u32MaxInstances: 4294967295
        u32NumAPOInterfaces: 2
        #1: {FC7DFF56-6B8D-45A9-B4CA-266F9AC21693}
        #2: {6BB6A944-7352-4327-AB91-D92607B25656}

    -- APO properties for caudiomatrix --
        clsid: {541987EE-0E02-411E-9A85-1FC6156E7F4B}
        Flags: 0x0000000c
            APO_FLAG_FRAMESPERSECOND_MUST_MATCH
            APO_FLAG_BITSPERSAMPLE_MUST_MATCH
        szFriendlyName: "caudiomatrix"
        szCopyrightInfo: "copyright (c) 2003 microsoft corporation"
        u32MajorVersion: 1
        u32MinorVersion: 0
        u32MinInputConnections: 1
        u32MaxInputConnections: 1
        u32MinOutputConnections: 1
        u32MaxOutputConnections: 1
        u32MaxInstances: 4294967295
        u32NumAPOInterfaces: 1
        #1: {BB8B2F5D-2AF0-473F-BD94-F55A77587D3F}

    -- APO properties for WM audio LFX APO --
        clsid: {62DC1A93-AE24-464C-A43E-452F824C4250}
        Flags: 0x0000000d
            APO_FLAG_INPLACE
            APO_FLAG_FRAMESPERSECOND_MUST_MATCH
            APO_FLAG_BITSPERSAMPLE_MUST_MATCH
        szFriendlyName: "WM audio LFX APO"
        szCopyrightInfo: "Copyright Microsoft"
        u32MajorVersion: 1
        u32MinorVersion: 1
        u32MinInputConnections: 1
        u32MaxInputConnections: 1
        u32MinOutputConnections: 1
        u32MaxOutputConnections: 1
        u32MaxInstances: 4294967295
        u32NumAPOInterfaces: 1
        #1: {FD7F2B29-24D0-4B5C-B177-592C39F9CA10}

    -- APO properties for WM audio GFX APO --
        clsid: {637C490D-EEE3-4C0A-973F-371958802DA2}
        Flags: 0x0000000d
            APO_FLAG_INPLACE
            APO_FLAG_FRAMESPERSECOND_MUST_MATCH
            APO_FLAG_BITSPERSAMPLE_MUST_MATCH
        szFriendlyName: "WM audio GFX APO"
        szCopyrightInfo: "Copyright Microsoft"
        u32MajorVersion: 1
        u32MinorVersion: 1
        u32MinInputConnections: 1
        u32MaxInputConnections: 1
        u32MinOutputConnections: 1
        u32MaxOutputConnections: 1
        u32MaxInstances: 4294967295
        u32NumAPOInterfaces: 1
        #1: {FD7F2B29-24D0-4B5C-B177-592C39F9CA10}

    -- APO properties for caudiorateconvert --
        clsid: {C58BD103-E87F-4B78-A0FA-7A5C95970EE2}
        Flags: 0x0000000a
            APO_FLAG_SAMPLESPERFRAME_MUST_MATCH
            APO_FLAG_BITSPERSAMPLE_MUST_MATCH
        szFriendlyName: "caudiorateconvert"
        szCopyrightInfo: "copyright (c) 2003 microsoft corporation"
        u32MajorVersion: 1
        u32MinorVersion: 0
        u32MinInputConnections: 1
        u32MaxInputConnections: 1
        u32MinOutputConnections: 1
        u32MaxOutputConnections: 1
        u32MaxInstances: 4294967295
        u32NumAPOInterfaces: 3
        #1: {788F7BE2-9C40-41C0-AF05-4393FBF409F9}
        #2: {C78841EF-516F-4516-B591-F04FA93783A9}
        #3: {7BA1DB8F-78AD-49CD-9591-F79D80A17C81}

    -- APO properties for CAudioLimiter --
        clsid: {D69E0717-DD4B-4B25-997A-DA813833B8AC}
        Flags: 0x0000000e
            APO_FLAG_SAMPLESPERFRAME_MUST_MATCH
            APO_FLAG_FRAMESPERSECOND_MUST_MATCH
            APO_FLAG_BITSPERSAMPLE_MUST_MATCH
        szFriendlyName: "CAudioLimiter"
        szCopyrightInfo: "Copyright (c) 2003 Microsoft Corporation"
        u32MajorVersion: 1
        u32MinorVersion: 0
        u32MinInputConnections: 1
        u32MaxInputConnections: 1
        u32MinOutputConnections: 1
        u32MaxOutputConnections: 1
        u32MaxInstances: 4294967295
        u32NumAPOInterfaces: 1
        #1: {7DF8824C-AEC7-4119-9425-E6979172A8AE}

    -- APO properties for caudiocopy --
        clsid: {E916B6B2-22BD-4AFC-B337-D3D9FB27670E}
        Flags: 0x0000000e
            APO_FLAG_SAMPLESPERFRAME_MUST_MATCH
            APO_FLAG_FRAMESPERSECOND_MUST_MATCH
            APO_FLAG_BITSPERSAMPLE_MUST_MATCH
        szFriendlyName: "caudiocopy"
        szCopyrightInfo: "copyright (c) 2003 microsoft corporation"
        u32MajorVersion: 1
        u32MinorVersion: 0
        u32MinInputConnections: 1
        u32MaxInputConnections: 1
        u32MinOutputConnections: 1
        u32MaxOutputConnections: 1
        u32MaxInstances: 4294967295
        u32NumAPOInterfaces: 1
        #1: {ADF7583D-F947-4311-83FB-066AD4E5E7C2}

    APOs found: 12
  • Matthew van Eerde's web log

    How to enumerate Media Foundation transforms on your system

    • 0 Comments

    This mini-application (source and binaries attached) uses MFTEnumEx to get a list of all the registered IMFTransform classes on the system.  If you're writing a Media Foundation app, you're likely going to use different flags than I used... see the documentation for sets of flags that match three different scenarios.

    In my case I wanted as exhaustive a list of transforms as possible - so I include asynchronous MFTs, disabled MFTs, etc:

    DWORD dwFlags = 0
        // enumerate all three kinds of data flow
        | MFT_ENUM_FLAG_SYNCMFT
        | MFT_ENUM_FLAG_ASYNCMFT
        | MFT_ENUM_FLAG_HARDWARE
           
        // include not-usually-included kinds of MFTs
        | MFT_ENUM_FLAG_FIELDOFUSE
        | MFT_ENUM_FLAG_LOCALMFT
        | MFT_ENUM_FLAG_TRANSCODE_ONLY
       
        // DO NOT filter or sort the results
        // we want to include even blocked MFTs in this list
        // EXPLICITLY NOT SETTING MFT_ENUM_FLAG_SORTANDFILTER
    ;

    Opportunities for later improvement - I only print out the friendly name and the CLSID for each found transform.  I could add things like the MF_TRANSFORM_FLAGS_Attribute, the MF_SUPPORT_DYNAMIC_FORMAT_CHANGE attribute, the supported input types and output types, etc.  But this is good enough for now.

    Output of the tool on my machine:

    > MFTEnum.exe

    -- Audio decoders ({9EA73FB4-EF7A-4559-8D5D-719D8F0426C7}) --
    WMAudio Decoder MFT ({2EEB4ADF-4578-4D10-BCA7-BB955F56320A})
    Microsoft AAC Audio Decoder MFT ({32D186A7-218F-4C75-8876-DD77273A8999})
    GSM ACM Wrapper MFT ({4A76B469-7B66-4DD4-BA2D-DDF244C766DC})
    WMAPro over S/PDIF MFT ({5210F8E4-B0BB-47C3-A8D9-7B2282CC79ED})
    WMSpeech Decoder DMO ({874131CB-4ECC-443B-8948-746B89595D20})
    G711 Wrapper MFT ({92B66080-5E2D-449E-90C4-C41F268E5514})
    IMA ADPCM ACM Wrapper MFT ({A16E1BFF-A80D-48AD-AECD-A35C005685FE})
    MP3 Decoder MFT ({BBEEA841-0A63-4F52-A7AB-A9B3A84ED38A})
    ADPCM ACM Wrapper MFT ({CA34FE0A-5722-43AD-AF23-05F7650257DD})

    -- Audio effects ({11064C48-3648-4ED0-932E-05CE8AC811B7}) --
    AEC ({745057C7-F353-4F2D-A7EE-58434477730E})
    Resampler MFT ({F447B69E-1884-4A7E-8055-346F74D6EDB3})

    -- Audio encoders ({91C64BD0-F91E-4D8C-9276-DB248279D975}) --
    WM Speech Encoder DMO ({1F1F4E1A-2252-4063-84BB-EEE75F8856D5})
    WMAudio Encoder MFT ({70F598E9-F4AB-495A-99E2-A7C4D3D89ABF})
    Microsoft AAC Audio Encoder MFT ({93AF0C51-2275-45D2-A35B-F2BA21CAED00})

    -- Demultiplexers ({A8700A7A-939B-44C5-99D7-76226B23B3F1}) --
    No IMFTransforms found.

    -- Multiplexers ({059C561E-05AE-4B61-B69D-55B61EE54A7B}) --
    No IMFTransforms found.

    -- Miscellaneous ({90175D57-B7EA-4901-AEB3-933A8747756F}) --
    No IMFTransforms found.

    -- Video decoders ({D6C02D4B-6833-45B4-971A-05A4B04BAB91}) --
    DV Decoder MFT ({404A6DE5-D4D6-4260-9BC7-5A6CBD882432})
    Mpeg4s Decoder MFT ({5686A0D9-FE39-409F-9DFF-3FDBC849F9F5})
    Microsoft H264 Video Decoder MFT ({62CE7E72-4C71-4D20-B15D-452831A87D9D})
    WMV Screen decoder MFT ({7BAFB3B1-D8F4-4279-9253-27DA423108DE})
    WMVideo Decoder MFT ({82D353DF-90BD-4382-8BC2-3F6192B76E34})
    NVIDIA Video Decoder MFT ({ACAE6A3C-3DB0-4F71-B783-CDBD2AC6FDE8})
    MJPEG Decoder MFT ({CB17E772-E1CC-4633-8450-5617AF577905})
    Mpeg43 Decoder MFT ({CBA9E78B-49A3-49EA-93D4-6BCBA8C4DE07})
    Mpeg4 Decoder MFT ({F371728A-6052-4D47-827C-D039335DFE0A})

    -- Video effects ({12E17C21-532C-4A6E-8A1C-40825A736397}) --
    Frame Rate Converter ({01F36CE2-0907-4D8B-979D-F151BE91C883})
    Resizer MFT ({1EA1EA14-48F4-4054-AD1A-E8AEE10AC805})
    Color Control ({798059F0-89CA-4160-B325-AEB48EFE4F9A})
    Color Converter MFT ({98230571-0087-4204-B020-3282538E57D3})

    -- Video encoders ({F79EAC7D-E545-4387-BDEE-D647D7BDE42A}) --
    NVIDIA Video Encoder MFT ({305AFD76-ADD0-417E-AA99-3AC4FDB22B21})
    H264 Encoder MFT ({6CA50344-051A-4DED-9779-A43305165E35})
    WMVideo8 Encoder MFT ({7E320092-596A-41B2-BBEB-175D10504EB6})
    WMVideo9 Encoder MFT ({D23B90D0-144F-46BD-841D-59E4EB19DC59})

    -- Video processors ({302EA3FC-AA5F-47F9-9F7A-C2188BB16302}) --
    NVIDIA Video Processor MFT ({6A8C0482-4A25-48A1-AFD1-0A7C1629352A})

    -- KS data compressors ({1E84C900-7E70-11D0-A5D6-28DB04C10000}) --
    No IMFTransforms found.

    -- KS data decompressors ({2721AE20-7E70-11D0-A5D6-28DB04C10000}) --
    No IMFTransforms found.

    EDIT: the IMFAttributes::GetAllocatedString call in this app is incorrect.  I pass a NULL for the "length" output parameter.  As it turns out, this must not be NULL because the call may be marshaled out-of-proc.

  • Matthew van Eerde's web log

    Perl script to parse ADTS audio header

    • 0 Comments

    Last time I posted a script to parse MPEG-1 audio headers.  This time, a script to parse MPEG-2 AAC audio headers:

    Here's the output for a typical file (attached:)

    >perl adtsaudioheader.pl ding.adts
    -- adts_fixed_header --
    syncword: 11111111 1111 (should be all ones)
    ID: 1 (MPEG identifier, always 1)
    layer: 00 (layer, always 00)
    protection_absent: 1 (CRC error detection data IS NOT present)
    profile: 01 (Low Complexity profile (LC))
    sampling_frequency_index: 0100 (44100 Hz)
    private_bit: 0
    channel_configuration: 010 (2.0: L R)
    original_copy: 0 (not copyright)
    home: 0 (this is a copy)
    -- adts_variable_header --
    copyright_identification_bit: 0 (copyright identification is transferred one bit per frame)
    copyright_identification_start: 0 (continuation of previous copyright identification, or no copyright)
    aac_frame_length: 00001111 11011 (507 bytes)
    adts_buffer_fullness: 11111111 111 (all ones means variable bit rate)
    number_of_raw_data_blocks_in_frame: 00 (1 blocks)

    Here's the script:

    use strict;

    # based on ISO IEC 13818-7-2006
    # assumes that the file you point it at starts with an ADTS audio header

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

    my %protection_absent = (
        "0" => "CRC error detection data IS present",
        "1" => "CRC error detection data IS NOT present",
    );

    my %profiles = (
        "00" => "Main profile",
        "01" => "Low Complexity profile (LC)",
        "10" => "Scalable Sampling Rate profile (SSR)",
        "11" => "Reserved"
    );

    my %sampling_frequencies = (
        "0000" => "96000 Hz",
        "0001" => "88200 Hz",
        "0010" => "64000 Hz",
        "0011" => "48000 Hz",
        "0100" => "44100 Hz",
        "0101" => "32000 Hz",
        "0110" => "24000 Hz",
        "0111" => "22050 Hz",
        "1000" => "16000 Hz",
        "1001" => "12000 Hz",
        "1010" => "11025 Hz",
        "1011" => "8000 Hz",
        "1100" => "Reserved",
        "1101" => "Reserved",
        "1110" => "Reserved",
        "1111" => "Reserved",
    );

    my %channel_configurations = (
        "000" => "see program_config_element or implicit",
        "001" => "1.0: C",
        "010" => "2.0: L R",
        "011" => "3.0: C L R",
        "100" => "4.0: C L R rear-surround",
        "101" => "5.0: C L R Ls Rs",
        "110" => "5.1: C L R Ls Rs LFE",
        "111" => "7.1: C L R Lo Ro Ls Rs LFE",
    );

    my %original_copy = (
        "0" => "not copyright",
        "1" => "copyright",
    );

    my %home = (
        "0" => "this is a copy",
        "1" => "this is an original",
    );

    my %copyright_identification_start = (
        "0" => "continuation of previous copyright identification, or no copyright",
        "1" => "start of copyright identification,"
    );

    sub bits_to_num(@);

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

    my $header = "";

    my $header_size = 7;
    my $read = read(ADTS, $header, $header_size, 0);

    close(ADTS);

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

    my @bits = ();

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

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

    printf(
        "-- adts_fixed_header --\n" .
        "syncword: %s %s (%s)\n" .
        "ID: %s (%s)\n" .
        "layer: %s (%s)\n" .
        "protection_absent: %s (%s)\n" .
        "profile: %s (%s)\n" .
        "sampling_frequency_index: %s (%s)\n" .
        "private_bit: %s\n" .
        "channel_configuration: %s (%s)\n" .
        "original_copy: %s (%s)\n" .
        "home: %s (%s)\n" .
        "-- adts_variable_header --\n" .
        "copyright_identification_bit: %s (%s)\n" .
        "copyright_identification_start: %s (%s)\n" .
        "aac_frame_length: %s %s (%s)\n" .
        "adts_buffer_fullness: %s %s (%s)\n" .
        "number_of_raw_data_blocks_in_frame: %s (%s)\n" .
        ""
        ,
        join("", @bits[0 .. 7]), join("", @bits[8 .. 11]), "should be all ones",
        join("", $bits[12]), "MPEG identifier, always 1",
        join("", @bits[13 .. 14]), "layer, always 00",
        join("", $bits[15]), $protection_absent{ join("", $bits[15]) },
        join("", @bits[16 .. 17]), $profiles{ join("", @bits[16 .. 17]) },
        join("", @bits[18 .. 21]), $sampling_frequencies{ join("", @bits[18 .. 21]) },
        join("", $bits[22]), # private_bit
        join("", @bits[23 .. 25]), $channel_configurations{ join("", @bits[23 .. 25]) },
        join("", $bits[26]), $original_copy{ join("", $bits[26]) },
        join("", $bits[27]), $home{ join("", $bits[27]) },
        join("", $bits[28]), "copyright identification is transferred one bit per frame",
        join("", $bits[29]), $copyright_identification_start{ join("", $bits[29]) },
        join("", @bits[30 .. 37]), join("", @bits[38 .. 42]),
            bits_to_num( @bits[30 .. 42] ) . " bytes", # aac_frame_length
        join("", @bits[43 .. 50]), join("", @bits[51 .. 53]),
            "all ones means variable bit rate", # adts_buffer_fullness
        join("", @bits[54 .. 55]),
            (bits_to_num( @bits[54 .. 55] ) + 1) . " blocks", # number_of_raw_data_blocks_in_frame

    );

    # pass an array of bits, little-endian
    sub bits_to_num(@) {
        my $bit = pop;

        return $bit + (@_ ? 2 * bits_to_num(@_) : 0);
    }

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

  • Matthew van Eerde's web log

    A mental model for the Windows Phone AudioRoutingManager API

    • 0 Comments

    The Windows Phone SDK includes a Windows.Phone.Media.Devices.AudioRoutingManager API which I had occasion to use.

    The API allows apps that have communication audio streams (e.g., Voice over IP calls) to control whether the audio goes out over the earpiece, over the speakerphone, or over the Bluetooth headset. This might be done automatically, or might be used to power "Speakerphone" and "Bluetooth" buttons in the app UI.

    The starting point is a GetDefault() method which gives you the singleton AudioRoutingManager object.

    There are three ways to get information out of this object:

    1. A read-only AvailableAudioEndpoints property tells you the list of currently available audio outputs.
    2. A GetAudioEndpoint method tells you what the current audio output is.
    3. An AudioEndpointChanged callback tells you when either of the previous two things change.

    You can also tell the object to change something:

    1. SetAudioEndpoint(…) lets you tell Windows Phone where audio should come out, subject to some restrictions.

    There are two enumerated types used by these methods:

    1. AvailableAudioRoutingEndpoints, which is the type of the AvailableAudioEndpoints property. This is a "flags"-style (multi-valued) enum with the following values:
      • None
      • Earpiece
      • Speakerphone
      • Bluetooth
    2. AudioRoutingEndpoint, which is returned by GetAudioEndpoint and is the sole argument for SetAudioEndpoint. This is a single-valued enum with the following values:
      • Default
      • Earpiece
      • Speakerphone
      • Bluetooth
      • WiredHeadset
      • WiredHeadsetSpeakerOnly
      • BluetoothWithNoiseAndEchoCancellation

    At first I found this very confusing. SetAudioEndpoint takes an AudioRoutingEndpoint type, but what do I pass to it? And why does GetAudioEndpoint always tell me "Speakerphone?"

    After experimenting and chatting with the folks who own the API I was able to construct an internal mental model which made more sense to me.

    1. While communications audio is playing, the Phone has an audio routing policy. Imagine an AudioRoutingPolicy write-only property with the following values:
      • I'm flexible: play to the first available of { wired headset, Bluetooth device, earpiece }
      • Gimme Bluetooth: play to the first available of { Bluetooth device, wired headset, earpiece }
      • No Bluetooth: play to the first available of { wired headset, earpiece }
      • Speakerphone: play to the built-in speaker
    2. If you want to change this policy, the app needs to have either the ID_CAP_VOIP or ID_CAP_VOICEMAIL capability. (The documentation refers to an ID_CAP_AUDIOROUTING capability, but this does not exist.)
      Do:
          var audioRoutingManager = Windows.Phone.Media.Devices.AudioRoutingManager.GetDefault();
          audioRoutingManager.SetAudioEndpoint(x);

      where xis as follows:
      • x = AudioRoutingEndpoint.Bluetooth sets the policy to Gimme Bluetooth
      • x = AudioRoutingEndpoint.Earpiece sets the policy to No Bluetooth
      • x = AudioRoutingEndpoint.Speakerphone sets the policy to Speakerphone
    3. There is no direct way to set the policy to I'm flexible.
    4. There is no direct way to tell what the current value of the AudioRoutingPolicy is. You can sometimes guess, though, based on the value of GetAudioEndpoint and/or AvailableAudioEndpoints.
      • If GetAudioEndpoint is AudioRoutingEndpoint.Speakerphone, then the current policy must be Speakerphone.
      • If GetAudioEndpoint is AudioRoutingEndpoint.Earpiece and AvailableAudioEndpoints & AvailableAudioRoutingEndpoints.Bluetooth is set, then the current policy must be No Bluetooth.
      • If GetAudioEndpoint is AudioRoutingEndpoint.WiredHeadset and AvailableAudioEndpoints & AvailableAudioRoutingEndpoints.Bluetooth is set, then the current policy must be either I'm flexible or No Bluetooth.
      • If GetAudioEndpoint is AudioRoutingEndpoint.Bluetooth or AudioRoutingEndpoint.BluetoothWithNoiseAndEchoCancelation, then AvailableAudioEndpoints & AvailableAudioRoutingEndpoints.Bluetooth must be set, and the current policy must be either I'm flexible or Gimme Bluetooth.
    5. When there are no audio communications streams, the policy is undefined.
    6. When the number of audio communications streams goes from zero to one (perhaps as the result of a phone call or VoIP call), the policy is reset/defaulted to I'm flexible or No Bluetooth depending on the details. This means you shouldn't bother setting a policy until after your phone call starts playing audio.
    7. When a device is connected (a Bluetooth device is connected, or a wired headset is plugged in) the policy is reset to I'm flexible. This (usually) results in audio switching to the new device, which is usually what the user wants.
      (If a device is removed, on the other hand, the policy is not reset.)

    Here's a chart I put together on how the different states and policies interact:

    If a Bluetooth Hands-Free HF device is: Connected
    AvailableAudioEndpoints =
      Speakerphone | Earpiece | Bluetooth
    Not connected
    AvailableAudioEndpoints =
      Speakerphone | Earpiece
    I'm flexible audio routing policy is:
    This policy may be automatically invoked when:
      a call starts, or
      a device connects
    You can manually invoke it with:
      SetAudioEndpoint(Bluetooth)
    WiredHeadset or
    WiredHeadsetSpeakerOnly or
    Bluetooth or
    BluetoothWith...
    Depending on what is plugged in, and the capabilities of the device
    WiredHeadset or
    WiredHeadsetSpeakerOnly or
    Earpiece
    Depending on what is plugged in
    Gimme Bluetooth audio routing policy is:
    This policy may be automatically invoked when a call starts
    You can manually invoke it with:
      SetAudioEndpoint(Bluetooth)
    Bluetooth or
    BluetoothWith...
    Depending on the capabilities of the device
    WiredHeadset or
    WiredHeadsetSpeakerOnly or
    Earpiece
    Depending on what is plugged in
    No Bluetooth audio routing policy is:
    You can manually invoke this policy with:
      SetAudioEndpoint(Earpiece) or
      SetAudioEndpoint(Default)
    WiredHeadset or
    WiredHeadsetSpeakerOnly or
    Earpiece
    Depending on what is plugged in
    Speakerphone audio routing policy is:
    You can manually invoke this policy with:
      SetAudioEndpoint(Speakerphone)
    Speakerphone
    Invalid audio routing policies:
    The following calls are all errors:
      SetAudioEndpoint(WiredHeadset)
      SetAudioEndpoint(WiredHeadsetSpeakerOnly)
      SetAudioEndpoint(BluetoothWith...)
    N/A
    SetAudioEndpoint throws an exception

    Note that if a wired headset is plugged in, the app has no way to make audio come out of the earpiece. This is true regardless of whether Bluetooth is connected.

    It seems like much of my confusion resulted from a single enumerated type (AudioRoutingEndpoint) serving three purposes:

    1. Tell the app where audio is coming out (WiredHeadset vs. Earpiece)
    2. Tell the app what the capabilities of the current output are (Bluetooth vs. BluetoothWithNoiseAndEchoCancellation)
    3. Allow the app to control the audio routing policy (Default vs. Speakerphone)

    I think it would have been clearer to make the audio routing policy a different enumerated type from the current audio output or the available audio outputs. But with the "audio routing policy" mental model, it's not too bad.

  • Matthew van Eerde's web log

    Efficient multiplication and division in GF(2⁸)

    • 0 Comments

    I talked about Rijndael in a couple of previous posts: Sieving irreducible monic polynomials over a finite field, Addition and multiplication table for GF(2²)

    I'm going to talk some more about it today.

    Rijndael does a lot of arithmetic operations (addition and multiplication) on elements of GF(28)4.

    These are represented as polynomials b0 + b1 x + b2 x2 + b3 x3.

    The individual coefficients bi are elements of GF(28).

    These are themselves polynomials a0 + a1 x + ... + a7 x7 where the individual coefficients ai are bits (0 or 1).

    Multiplication bi bj is made into a closed operation by taking the modulus of the product under the reduction polynomial m = x8 + x4 + x3 + x + 1.

    The reduction polynomial is prime, which is important in establishing that this representation of the Galois field really is a field.

    For convenience we will represent the elements of GF(28) as hexadecimal numbers 0x00, 0x01, ... 0xfe, 0xff.

    By contrast, we will represent the elements of GF(28)4 as vectors (b0 b1 b2 b3); for example, (0x00 0x01 0x02 0x03).

    Multiplying vectors in GF(28)4 induces a bunch of multiplications in GF(28). It would be good to come up with a way to make this fast. Doing a polynomial reduction every time is not fast.

    Of course, one way to do this is to create a multiplication table with 256 rows and 256 columns, do each multiplication the slow way once, and compile in the results. This would require on the order of 64 kB. But there's a better way.

    The trick that is used in Daemen and Rijmen's implementation is to find a primitive element e in GF(28) so that the orbit of e (that is to say, the set {e0, e1, ..., e254}) spans all of GF(28) - {0x00}.

    If we find such an e, then we can save a cheat sheet which is almost as fast as the multiplication table, but requires storing only on the order of 256 bytes.

    Well, let's calculate the orbits of all the elements in order until we find a primitive element.

    Orbit(0x00) = { 0x000, 0x001, 0x002, ... } = { 0x01, 0x00, 0x00, ... } = { 0x00, 0x01 }. Nope.
    Orbit(0x01) = { 0x010, 0x011, 0x012, ... } = { 0x01, 0x01, 0x01, ... } = { 0x01 }. Even worse.
    Orbit(0x02) = { 0x020, 0x021, 0x022, ... } = { 0x01, 0x02, 0x04, ..., 0x8d }. This looks promising at first but we end up with an orbit of only 51 out of the necessary 255 elements (note that 51 divides 255:)

    0x020 = 0x01
    0x021 = 0x02
    0x022 = 0x04
    0x023 = 0x08
    0x024 = 0x10
    0x025 = 0x20
    0x026 = 0x40
    0x027 = 0x80
    0x028 = 0x1b
    0x029 = 0x36
    0x0210 = 0x6c
    0x0211 = 0xd8
    0x0212 = 0xab
    0x0213 = 0x4d
    0x0214 = 0x9a
    0x0215 = 0x2f
    0x0216 = 0x5e
    0x0217 = 0xbc
    0x0218 = 0x63
    0x0219 = 0xc6
    0x0220 = 0x97
    0x0221 = 0x35
    0x0222 = 0x6a
    0x0223 = 0xd4
    0x0224 = 0xb3
    0x0225 = 0x7d
    0x0226 = 0xfa
    0x0227 = 0xef
    0x0228 = 0xc5
    0x0229 = 0x91
    0x0230 = 0x39
    0x0231 = 0x72
    0x0232 = 0xe4
    0x0233 = 0xd3
    0x0234 = 0xbd
    0x0235 = 0x61
    0x0236 = 0xc2
    0x0237 = 0x9f
    0x0238 = 0x25
    0x0239 = 0x4a
    0x0240 = 0x94
    0x0241 = 0x33
    0x0242 = 0x66
    0x0243 = 0xcc
    0x0244 = 0x83
    0x0245 = 0x1d
    0x0246 = 0x3a
    0x0247 = 0x74
    0x0248 = 0xe8
    0x0249 = 0xcb
    0x0250 = 0x8d
    And then we circle back around to...
    0x0251 = 0x01

    Well, let's keep looking. What about 0x03?

    Orbit(0x03) = { 0x030, 0x031, 0x032, ... } = { 0x01, 0x03, 0x05, ..., 0xf6 }. Bingo, we hit all 255 non-0x00 elements!

    0x030 = 0x01
    0x031 = 0x03
    0x032 = 0x05
    0x033 = 0x0f
    0x034 = 0x11
    0x035 = 0x33
    0x036 = 0x55
    0x037 = 0xff
    0x038 = 0x1a
    0x039 = 0x2e
    0x0310 = 0x72
    0x0311 = 0x96
    0x0312 = 0xa1
    0x0313 = 0xf8
    0x0314 = 0x13
    0x0315 = 0x35
    0x0316 = 0x5f
    0x0317 = 0xe1
    0x0318 = 0x38
    0x0319 = 0x48
    0x0320 = 0xd8
    0x0321 = 0x73
    0x0322 = 0x95
    0x0323 = 0xa4
    0x0324 = 0xf7
    0x0325 = 0x02
    0x0326 = 0x06
    0x0327 = 0x0a
    0x0328 = 0x1e
    0x0329 = 0x22
    0x0330 = 0x66
    0x0331 = 0xaa
    0x0332 = 0xe5
    0x0333 = 0x34
    0x0334 = 0x5c
    0x0335 = 0xe4
    0x0336 = 0x37
    0x0337 = 0x59
    0x0338 = 0xeb
    0x0339 = 0x26
    0x0340 = 0x6a
    0x0341 = 0xbe
    0x0342 = 0xd9
    0x0343 = 0x70
    0x0344 = 0x90
    0x0345 = 0xab
    0x0346 = 0xe6
    0x0347 = 0x31
    0x0348 = 0x53
    0x0349 = 0xf5
    0x0350 = 0x04
    0x0351 = 0x0c
    0x0352 = 0x14
    0x0353 = 0x3c
    0x0354 = 0x44
    0x0355 = 0xcc
    0x0356 = 0x4f
    0x0357 = 0xd1
    0x0358 = 0x68
    0x0359 = 0xb8
    0x0360 = 0xd3
    0x0361 = 0x6e
    0x0362 = 0xb2
    0x0363 = 0xcd
    0x0364 = 0x4c
    0x0365 = 0xd4
    0x0366 = 0x67
    0x0367 = 0xa9
    0x0368 = 0xe0
    0x0369 = 0x3b
    0x0370 = 0x4d
    0x0371 = 0xd7
    0x0372 = 0x62
    0x0373 = 0xa6
    0x0374 = 0xf1
    0x0375 = 0x08
    0x0376 = 0x18
    0x0377 = 0x28
    0x0378 = 0x78
    0x0379 = 0x88
    0x0380 = 0x83
    0x0381 = 0x9e
    0x0382 = 0xb9
    0x0383 = 0xd0
    0x0384 = 0x6b
    0x0385 = 0xbd
    0x0386 = 0xdc
    0x0387 = 0x7f
    0x0388 = 0x81
    0x0389 = 0x98
    0x0390 = 0xb3
    0x0391 = 0xce
    0x0392 = 0x49
    0x0393 = 0xdb
    0x0394 = 0x76
    0x0395 = 0x9a
    0x0396 = 0xb5
    0x0397 = 0xc4
    0x0398 = 0x57
    0x0399 = 0xf9
    0x03100 = 0x10
    0x03101 = 0x30
    0x03102 = 0x50
    0x03103 = 0xf0
    0x03104 = 0x0b
    0x03105 = 0x1d
    0x03106 = 0x27
    0x03107 = 0x69
    0x03108 = 0xbb
    0x03109 = 0xd6
    0x03110 = 0x61
    0x03111 = 0xa3
    0x03112 = 0xfe
    0x03113 = 0x19
    0x03114 = 0x2b
    0x03115 = 0x7d
    0x03116 = 0x87
    0x03117 = 0x92
    0x03118 = 0xad
    0x03119 = 0xec
    0x03120 = 0x2f
    0x03121 = 0x71
    0x03122 = 0x93
    0x03123 = 0xae
    0x03124 = 0xe9
    0x03125 = 0x20
    0x03126 = 0x60
    0x03127 = 0xa0
    0x03128 = 0xfb
    0x03129 = 0x16
    0x03130 = 0x3a
    0x03131 = 0x4e
    0x03132 = 0xd2
    0x03133 = 0x6d
    0x03134 = 0xb7
    0x03135 = 0xc2
    0x03136 = 0x5d
    0x03137 = 0xe7
    0x03138 = 0x32
    0x03139 = 0x56
    0x03140 = 0xfa
    0x03141 = 0x15
    0x03142 = 0x3f
    0x03143 = 0x41
    0x03144 = 0xc3
    0x03145 = 0x5e
    0x03146 = 0xe2
    0x03147 = 0x3d
    0x03148 = 0x47
    0x03149 = 0xc9
    0x03150 = 0x40
    0x03151 = 0xc0
    0x03152 = 0x5b
    0x03153 = 0xed
    0x03154 = 0x2c
    0x03155 = 0x74
    0x03156 = 0x9c
    0x03157 = 0xbf
    0x03158 = 0xda
    0x03159 = 0x75
    0x03160 = 0x9f
    0x03161 = 0xba
    0x03162 = 0xd5
    0x03163 = 0x64
    0x03164 = 0xac
    0x03165 = 0xef
    0x03166 = 0x2a
    0x03167 = 0x7e
    0x03168 = 0x82
    0x03169 = 0x9d
    0x03170 = 0xbc
    0x03171 = 0xdf
    0x03172 = 0x7a
    0x03173 = 0x8e
    0x03174 = 0x89
    0x03175 = 0x80
    0x03176 = 0x9b
    0x03177 = 0xb6
    0x03178 = 0xc1
    0x03179 = 0x58
    0x03180 = 0xe8
    0x03181 = 0x23
    0x03182 = 0x65
    0x03183 = 0xaf
    0x03184 = 0xea
    0x03185 = 0x25
    0x03186 = 0x6f
    0x03187 = 0xb1
    0x03188 = 0xc8
    0x03189 = 0x43
    0x03190 = 0xc5
    0x03191 = 0x54
    0x03192 = 0xfc
    0x03193 = 0x1f
    0x03194 = 0x21
    0x03195 = 0x63
    0x03196 = 0xa5
    0x03197 = 0xf4
    0x03198 = 0x07
    0x03199 = 0x09
    0x03200 = 0x1b
    0x03201 = 0x2d
    0x03202 = 0x77
    0x03203 = 0x99
    0x03204 = 0xb0
    0x03205 = 0xcb
    0x03206 = 0x46
    0x03207 = 0xca
    0x03208 = 0x45
    0x03209 = 0xcf
    0x03210 = 0x4a
    0x03211 = 0xde
    0x03212 = 0x79
    0x03213 = 0x8b
    0x03214 = 0x86
    0x03215 = 0x91
    0x03216 = 0xa8
    0x03217 = 0xe3
    0x03218 = 0x3e
    0x03219 = 0x42
    0x03220 = 0xc6
    0x03221 = 0x51
    0x03222 = 0xf3
    0x03223 = 0x0e
    0x03224 = 0x12
    0x03225 = 0x36
    0x03226 = 0x5a
    0x03227 = 0xee
    0x03228 = 0x29
    0x03229 = 0x7b
    0x03230 = 0x8d
    0x03231 = 0x8c
    0x03232 = 0x8f
    0x03233 = 0x8a
    0x03234 = 0x85
    0x03235 = 0x94
    0x03236 = 0xa7
    0x03237 = 0xf2
    0x03238 = 0x0d
    0x03239 = 0x17
    0x03240 = 0x39
    0x03241 = 0x4b
    0x03242 = 0xdd
    0x03243 = 0x7c
    0x03244 = 0x84
    0x03245 = 0x97
    0x03246 = 0xa2
    0x03247 = 0xfd
    0x03248 = 0x1c
    0x03249 = 0x24
    0x03250 = 0x6c
    0x03251 = 0xb4
    0x03252 = 0xc7
    0x03253 = 0x52
    0x03254 = 0xf6
    ... and only now do we circle around to...
    0x03255 = 0x01

    This gives us a one-to-one mapping between the 255 powers 0 through 254 and the 255 non-zero bytes 0x01 through 0xff.

    Here are the perl scripts I used to generate these:

    use strict;

    my $m = 0x11b;

    my $a = 1;
    for (my $i = 0; ; $i++) {
        printf("0x02^%2d = 0x%02x\n", $i, $a);
        if ($i > 0 and $a == 1) { last; }
        $a <<= 1; # * 0x02
        if ($a > 0xff) { $a ^= $m; }
    }

    print "\n";

    $a = 1;
    for (my $i = 0; ; $i++) {
        printf("0x03^%3d = 0x%02x\n", $i, $a);
        if ($i > 0 and $a == 1) { last; }
        $a = ($a << 1) ^ $a; # * 0x03
        if ($a > 0xff) { $a ^= $m; }
    }

    Now that we have this orbit, we use it to construct two lookup tables:

    // 255 entries xPlusOne_ToThe[0] = 0x01 to xPlusOne_ToThe[254] = 0xf6
    xPlusOne_ToThe[] = { 0x01, 0x03, 0x05, ..., 0x52, 0xf6 };

    And the inverse mapping:

    // 255 entries logXPlusOne_Of[0x01] = 0 to logXPlusOne_Of[0xff] = 7
    logXPlusOne_Of[] = { UNDEFINED, 0, 25, 1, 50, ..., 112, 7 };

    Now that we have paid for these 512-ish bytes, we can do multiplication very cheaply:

    multiply(b1, b2) {
        if (0x00 == b1 || 0x00 == b2) { return 0x00; }

        logAns = logXPlusOne_Of[b1] + logXPlusOne_Of[b2];
        if (logAns >= 255) { logAns -= 255; }
        return xPlusOne_ToThe[logAns];
    }

  • Matthew van Eerde's web log

    Generating the Rijndael S-box

    • 0 Comments

    I talked about Rijndael in a couple of previous posts: Efficient multiplication and division in GF(28), Sieving irreducible monic polynomials over a finite field, Addition and multiplication table for GF(22).

    I'm going to talk some more about it today.

    One of the more interesting steps used in the Rijndael transformation is the non-linear S-box. This is a function S(a) that takes an element of GF(28) (which could be represented as a byte) and returns another one. It is invertible but non-linear.

    The spec defines S(a) in terms of two other invertible functions g(a) and f(a). In particular S(a) = f(g(a)). It follows that S-1(a) = g-1(f-1(a)).

    g(a) is the non-linear piece, and is quite simple:
    g(00) is defined as 00.
    g(a) = a-1 for all other a in GF(28). In particular, if a = 03b, then g(a) = 03255 - b.
    This is clearly invertible; in fact, it is its own inverse.

    Daemen and Rijmen seem to have been almost embarrassed by the simplicity of g so they introduced f. To quote from section 3.4.1:

    By definition, g has a very simple algebraic expression. This could allow algebraic manipulations that can be used to mount attacks such as interpolation attacks. Therefore, we built the S-box as the sequence of g and an invertible affine transformation f.

    I don't know if I buy the "simplicity" argument. It seems to me that if Rijndael, without f, is robust, then it's robust. And if you add f to a non-robust scheme, I don't understand how that makes it robust; I do see that it complicates analysis, but that seems like a drawback rather than an advantage.

    But I'll play along for now. What is f?

    b = f(a) is defined using the following matrix multiplication over GF(2) (each entry can be represented as a bit; a row or column as a byte.) Multiplication can be implemented as bitwise AND, and addition by bitwise XOR.

    In particular, note that f(00) = 63. So S(00) = f(g(00)) = f(00) = 63. So S-1(63) = 00.

    The reference implementation in the book uses hardcoded 256-byte lookup tables for S(a) and S-1(a). I wrote a Perl script which generates these lookup tables and prints them out. This script is attached.

    Here's its output, which matches the listing in the book:

    >perl -w s-box.pl
                              S(xy)
       | x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf
    ---+------------------------------------------------
    0y | 63 7c 77 7b f2 6b 6f c5 30 01 67 2b fe d7 ab 76
    1y | ca 82 c9 7d fa 59 47 f0 ad d4 a2 af 9c a4 72 c0
    2y | b7 fd 93 26 36 3f f7 cc 34 a5 e5 f1 71 d8 31 15
    3y | 04 c7 23 c3 18 96 05 9a 07 12 80 e2 eb 27 b2 75
    4y | 09 83 2c 1a 1b 6e 5a a0 52 3b d6 b3 29 e3 2f 84
    5y | 53 d1 00 ed 20 fc b1 5b 6a cb be 39 4a 4c 58 cf
    6y | d0 ef aa fb 43 4d 33 85 45 f9 02 7f 50 3c 9f a8
    7y | 51 a3 40 8f 92 9d 38 f5 bc b6 da 21 10 ff f3 d2
    8y | cd 0c 13 ec 5f 97 44 17 c4 a7 7e 3d 64 5d 19 73
    9y | 60 81 4f dc 22 2a 90 88 46 ee b8 14 de 5e 0b db
    ay | e0 32 3a 0a 49 06 24 5c c2 d3 ac 62 91 95 e4 79
    by | e7 c8 37 6d 8d d5 4e a9 6c 56 f4 ea 65 7a ae 08
    cy | ba 78 25 2e 1c a6 b4 c6 e8 dd 74 1f 4b bd 8b 8a
    dy | 70 3e b5 66 48 03 f6 0e 61 35 57 b9 86 c1 1d 9e
    ey | e1 f8 98 11 69 d9 8e 94 9b 1e 87 e9 ce 55 28 df
    fy | 8c a1 89 0d bf e6 42 68 41 99 2d 0f b0 54 bb 16

                            S^(-1)(xy)
       | x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf
    ---+------------------------------------------------
    0y | 52 09 6a d5 30 36 a5 38 bf 40 a3 9e 81 f3 d7 fb
    1y | 7c e3 39 82 9b 2f ff 87 34 8e 43 44 c4 de e9 cb
    2y | 54 7b 94 32 a6 c2 23 3d ee 4c 95 0b 42 fa c3 4e
    3y | 08 2e a1 66 28 d9 24 b2 76 5b a2 49 6d 8b d1 25
    4y | 72 f8 f6 64 86 68 98 16 d4 a4 5c cc 5d 65 b6 92
    5y | 6c 70 48 50 fd ed b9 da 5e 15 46 57 a7 8d 9d 84
    6y | 90 d8 ab 00 8c bc d3 0a f7 e4 58 05 b8 b3 45 06
    7y | d0 2c 1e 8f ca 3f 0f 02 c1 af bd 03 01 13 8a 6b
    8y | 3a 91 11 41 4f 67 dc ea 97 f2 cf ce f0 b4 e6 73
    9y | 96 ac 74 22 e7 ad 35 85 e2 f9 37 e8 1c 75 df 6e
    ay | 47 f1 1a 71 1d 29 c5 89 6f b7 62 0e aa 18 be 1b
    by | fc 56 3e 4b c6 d2 79 20 9a db c0 fe 78 cd 5a f4
    cy | 1f dd a8 33 88 07 c7 31 b1 12 10 59 27 80 ec 5f
    dy | 60 51 7f a9 19 b5 4a 0d 2d e5 7a 9f 93 c9 9c ef
    ey | a0 e0 3b 4d ae 2a f5 b0 c8 eb bb 3c 83 53 99 61
    fy | 17 2b 04 7e ba 77 d6 26 e1 69 14 63 55 21 0c 7d

    And here's the interesting part, the implementation of g(a) and f(a):

    sub g($) {
        my ($a) = shift;

        # g(0) we define to be 0
        return 0 unless $a;

        # otherwise g(a) = a^(-1)
        # a = (x + 1)^loga
        # so a^(-1) = (x + 1)^(-loga) = (x + 1)^(255 - loga)
        my $loga = $log_xplusone_of[$a];
        my $logb = 255 - $loga;
        $logb -= 255 if $logb >= 255;

        return $xplusone_to[$logb];
    }

    # f(a) = b is defined as follows:
    #
    # [ b7 ]   ( [ 1 1 1 1 1 0 0 0 ] [ a7 ] )   [ 0 ]
    # [ b6 ]   ( [ 0 1 1 1 1 1 0 0 ] [ a6 ] )   [ 1 ]
    # [ b5 ]   ( [ 0 0 1 1 1 1 1 0 ] [ a5 ] )   [ 1 ]
    # [ b4 ] = ( [ 0 0 0 1 1 1 1 1 ] [ a4 ] ) + [ 0 ]
    # [ b3 ]   ( [ 1 0 0 0 1 1 1 1 ] [ a3 ] )   [ 0 ]
    # [ b2 ]   ( [ 1 1 0 0 0 1 1 1 ] [ a2 ] )   [ 0 ]
    # [ b1 ]   ( [ 1 1 1 0 0 0 1 1 ] [ a1 ] )   [ 1 ]
    # [ b0 ]   ( [ 1 1 1 1 0 0 0 1 ] [ a0 ] )   [ 1 ]
    #
    # where the + is XOR
    sub f($) {
        my ($a) = @_;

        # start with the addition
        my $b = 0x63; # 0b01100011;

        # do the matrix multiplication
        # one matrix column at a time
        for (
            my ($c, $i) = (0x8f, 0x80); # 0b10001111, 0b10000000
            $i;
            $c = rotate_byte_right($c), $i >>= 1
        ) {
            # i is used to select a bit out of the a column
            # c is the matrix column which is multiplied by that bit
            # the resulting product influences the eventual b column

            # printf("%02x %02x\n", $c, $i);

            # if this bit in the a column is 0, all of the products are 0, so don't bother
            next unless $a & $i;

            # this bit in the a column is 1
            # so XOR b with the matrix column
            $b ^= $c;
        }

        return $b;
    }
  • Matthew van Eerde's web log

    Expressing a function f: GF(2⁸) → GF(2⁸) as a polynomial using a Lagrange polynomial

    • 0 Comments

    I talked about Rijndael in a couple of previous posts: Generating the Rijndael S-box, Efficient multiplication and division in GF(28), Sieving irreducible monic polynomials over a finite field, Addition and multiplication table for GF(22).

    I'm going to talk some more about it today.

    The Rijndael non-linear S-box S(x) is a composition of two invertible functions f(g(x)). Last time we showed how to generate these, and their inverses, as 256-entry tables.

    As Daemen and Rijmen point out, any function from a finite field to itself can be expressed as a polynomial. In fact, given a tabular form of the function, it is possible to generate the Lagrange polynomial and then simplify.

    They also give the polynomial for S(x):

    SRD[x] = 05·x255 + 09·x253 + F9·x251 + 25·x247 + F4·x239 + 01·x223 + B5·x191 + 8F·x127 + 63

    Well, let's check this.
    And while we're at it, let's find the polynomials for g(x), f(x) and even S-1(x) too.
    First of all, let's start with the Lagrange polynomial.

    Given a table of entries { (00, y00), (01, y01), ..., (ff, yff) }, there is a polynomial L(x) which gives the same output, namely:

    L(x) = Σi ∈ { 00, 01, ..., ff } yi pi(x)
    where pi(x) = Πj ∈ { 00, 01, ..., ff }, ji (x - j) / (i - j)

    Can we simplify this?
    Yes. Note that (i - j)-1 term varies over all the non-zero elements of the finite field. Since this is a field, every non-zero element has an inverse, which might or might not be itself.
    If the inverse is not itself, we can pair the two inverse elements together, and we get 01, which is the multiplicative identity, so we can ignore it.
    What are we left with? The product of those non-zero elements which are their own inverses.
    In the case of GF(28), there is only one such element, namely 01.
    Great! We can ignore the (i - j)-1 terms altogether:

    L(x) = Σi ∈ { 00, 01, ..., ff } yi Πj ∈ { 00, 01, ..., ff }, ji (x - j)

    What is this? A sum of 256 different polynomials.
    Each of these summands is a product of 255 polynomials of degree 1, and so is a polynomial of degree 255.
    But in GF(28), x255 = 01 for all x. So each of the summands is actually a polynomial of degree 254, or less.
    So the sum is also a polynomial of degree 254, or less; terms with an exponent >= 255 go away and just contribute to lower-order terms.

    Great. We have { (00, y00), (01, y01), ..., (ff, yff) } for f, g, S, and S-1. Let's plug them in and see what happens!

    I wrote a Perl script to do this; the script is attached. Here's the output. (It's quite slow; it takes about two and a half minutes to run.)

    g(x) = 01 x^254

    f(x) =
     63 + 05 x + 09 x^2 + f9 x^4 + 25 x^8 + f4 x^16 + 01 x^32 + b5 x^64 +
     8f x^128

    S(x) =
     63 + 8f x^127 + b5 x^191 + 01 x^223 + f4 x^239 + 25 x^247 + f9 x^251 + 09 x^253 +
     05 x^254

    S^(-1)(x) =
     52 + f3 x + 7e x^2 + 1e x^3 + 90 x^4 + bb x^5 + 2c x^6 + 8a x^7 +
     1c x^8 + 85 x^9 + 6d x^10 + c0 x^11 + b2 x^12 + 1b x^13 + 40 x^14 + 23 x^15 +
     f6 x^16 + 73 x^17 + 29 x^18 + d9 x^19 + 39 x^20 + 21 x^21 + cf x^22 + 3d x^23 +
     9a x^24 + 8a x^25 + 2f x^26 + cf x^27 + 7b x^28 + 04 x^29 + e8 x^30 + c8 x^31 +
     85 x^32 + 7b x^33 + 7c x^34 + af x^35 + 86 x^36 + 2f x^37 + 13 x^38 + 65 x^39 +
     75 x^40 + d3 x^41 + 6d x^42 + d4 x^43 + 89 x^44 + 8e x^45 + 65 x^46 + 05 x^47 +
     ea x^48 + 77 x^49 + 50 x^50 + a3 x^51 + c5 x^52 + 01 x^53 + 0b x^54 + 46 x^55 +
     bf x^56 + a7 x^57 + 0c x^58 + c7 x^59 + 8e x^60 + f2 x^61 + b1 x^62 + cb x^63 +
     e5 x^64 + e2 x^65 + 10 x^66 + d1 x^67 + 05 x^68 + b0 x^69 + f5 x^70 + 86 x^71 +
     e4 x^72 + 03 x^73 + 71 x^74 + a6 x^75 + 56 x^76 + 03 x^77 + 9e x^78 + 3e x^79 +
     19 x^80 + 18 x^81 + 52 x^82 + 16 x^83 + b9 x^84 + d3 x^85 + 38 x^86 + d9 x^87 +
     04 x^88 + e3 x^89 + 72 x^90 + 6b x^91 + ba x^92 + e8 x^93 + bf x^94 + 9d x^95 +
     1d x^96 + 5a x^97 + 55 x^98 + ff x^99 + 71 x^100 + e1 x^101 + a8 x^102 + 8e x^103 +
     fe x^104 + a2 x^105 + a7 x^106 + 1f x^107 + df x^108 + b0 x^109 + 03 x^110 + cb x^111 +
     08 x^112 + 53 x^113 + 6f x^114 + b0 x^115 + 7f x^116 + 87 x^117 + 8b x^118 + 02 x^119 +
     b1 x^120 + 92 x^121 + 81 x^122 + 27 x^123 + 40 x^124 + 2e x^125 + 1a x^126 + ee x^127 +
     10 x^128 + ca x^129 + 82 x^130 + 4f x^131 + 09 x^132 + aa x^133 + c7 x^134 + 55 x^135 +
     24 x^136 + 6c x^137 + e2 x^138 + 58 x^139 + bc x^140 + e0 x^141 + 26 x^142 + 37 x^143 +
     ed x^144 + 8d x^145 + 2a x^146 + d5 x^147 + ed x^148 + 45 x^149 + c3 x^150 + ec x^151 +
     1c x^152 + 3e x^153 + 2a x^154 + b3 x^155 + 9e x^156 + b7 x^157 + 38 x^158 + 82 x^159 +
     23 x^160 + 2d x^161 + 87 x^162 + ea x^163 + da x^164 + 45 x^165 + 24 x^166 + 03 x^167 +
     e7 x^168 + 19 x^169 + e3 x^170 + d3 x^171 + 4e x^172 + dd x^173 + 11 x^174 + 4e x^175 +
     81 x^176 + 91 x^177 + 91 x^178 + 59 x^179 + a3 x^180 + 80 x^181 + 92 x^182 + 7e x^183 +
     db x^184 + c4 x^185 + 20 x^186 + ec x^187 + db x^188 + 55 x^189 + 7f x^190 + a8 x^191 +
     c1 x^192 + 64 x^193 + ab x^194 + 1b x^195 + fd x^196 + 60 x^197 + 05 x^198 + 13 x^199 +
     2c x^200 + a9 x^201 + 76 x^202 + a5 x^203 + 1d x^204 + 32 x^205 + 8e x^206 + 1e x^207 +
     c0 x^208 + 65 x^209 + cb x^210 + 8b x^211 + 93 x^212 + e4 x^213 + ae x^214 + be x^215 +
     5f x^216 + 2c x^217 + 3b x^218 + d2 x^219 + 0f x^220 + 9f x^221 + 42 x^222 + cc x^223 +
     6c x^224 + 80 x^225 + 68 x^226 + 43 x^227 + 09 x^228 + 23 x^229 + c5 x^230 + 6d x^231 +
     1d x^232 + 18 x^233 + bd x^234 + 5e x^235 + 1b x^236 + b4 x^237 + 85 x^238 + 49 x^239 +
     bc x^240 + 0d x^241 + 1f x^242 + a6 x^243 + 6b x^244 + d8 x^245 + 22 x^246 + 01 x^247 +
     7a x^248 + c0 x^249 + 55 x^250 + 16 x^251 + b3 x^252 + cf x^253 + 05 x^254

    Daemen and Rijmen's polynomial rendition of S(x) is confirmed.

    Also note how simple g(x) is, and how complex S-1(x) is.

    Finally, I must confess that none of this is actually useful. The tabular form is much more convenient for applications. This is just a fun theoretical exercise.

    (Well, I had fun.)

  • Matthew van Eerde's web log

    Using the Speech API to convert speech to text

    • 0 Comments

    Some time ago I created a "listen.exe" tool which used SAPI's ISpRecoContext to listen to the microphone and dump any recognized text to the console.

    Today I had to debug an issue with SAPI reading from a .wav file, so I updated it to accept a listen.exe --file foo.wav argument; this consumes the audio in the .wav file instead of listening to the microphone.

    Pseudocode for the difference:

    CoCreate(ISpRecognizer);
    CoCreate(ISpStream);
    pSpStream->BindToFile(file);
    pSpRecognizer->SetInput(pSpStream);

    Also, we have to tell the ISpRecoContext that we're interested in SPEI_END_SR_STREAM events as well as SPEI_RECOGNITION events.

    Full source and binaries attached.

    A gotcha: the .wav file has to have a WAVEFORMATEX.wFormatTag = WAVE_FORMAT_PCM. If it's anything else, ISpRecoGrammar::SetDictationState fails with SPERR_UNSUPPORTED_FORMAT. Neither WAVE_FORMAT_IEEE_FLOAT nor (WAVE_FORMAT_EXTENSIBLE with SubFormat = KSDATAFORMAT_SUBTYPE_PCM) work.

  • Matthew van Eerde's web log

    Arbitrary HTML and JavaScript injection

    • 0 Comments
    1. Inject arbitrary HTML into this page:
      Any HTML you want

    2. Run arbitrary JavaScript on this page:
      4

  • Matthew van Eerde's web log

    Why is 1 Pascal equal to 94 dB Sound Pressure Level? (1 Pa = 94 dB SPL)

    • 0 Comments

    Last time we talked about why a full-scale digital sine wave has a power measurement of -3.01 dB FS (Spoiler: because it's not a square wave.)

    This time we'll discuss why an atmospheric sound which generates a root-mean-square pressure of 1 Pascal has a power measurement 94 dB SPL.

    As before, dB is defined as 10 log10(PA2 / PB2) where PB is a reference level.

    Before, we had a digital measurement with an obvious ceiling: sample values of -1 and 1. So the reference point 0 dB FS was defined in terms of the signal with the greatest possible energy.

    In the analog domain, there isn't an obvious ceiling. We instead consider the floor - the quietest possible signal that is still audible by human ears.

    This is a rather wishy-washy definition, but the convention is to take PB = 20 μPa = 0.00002 Pa exactly.

    So our 0 dB SPL reference point is when PA = PB: 0 dB SPL = 10 log10(0.000022 / 0.000022) = 10 log10(1) = 10 (0) = 0.

    What if the pressure level is 1 Pascal? This is a quite loud sound, somewhere between heavy traffic and a jackhammer.

    1 Pa in dB SPL =

        10 log10(12 / PB2) =

        20 log10(1 / PB) =

        -20 log10(PB) =

        -20 log10(2(10-5)) =

        -20 (log10 2 + log10 10-5) =

        -20 ((log10 2) - 5) =

        100 - 20 log10 2 ≈ 93.9794 dB SPL

    So 1 Pa is actually a tiny bit less than 94 dB SPL; it's closer to 93.98 = (100 - 6.02) dB SPL.

  • Matthew van Eerde's web log

    Sample app for RECT functions

    • 0 Comments

    Riffing on Raymond Chen's post today about SubtractRect I threw together a toy app which demonstrates three rectangle functions: UnionRect, IntersectRect, and SubtractRect.

    Usage:

    >rects.exe
    rects.exe
        union     (left1 top1 right1 bottom1) (left2 top2 right2 bottom2) |
        intersect (left1 top1 right1 bottom1) (left2 top2 right2 bottom2) |
        subtract  (left1 top1 right1 bottom1) (left2 top2 right2 bottom2)

    Sample output:

    >rects.exe union (2 2 6 6) (4 4 8 8)
          (left = 2; top = 2; right = 6; bottom = 6)
    union (left = 4; top = 4; right = 8; bottom = 8)
        = (left = 2; top = 2; right = 8; bottom = 8)

    Source and binaries (amd64 and x86) attached.

    Still no pictures though.

    Exercise: implement BOOL SymmetricDifferenceRect(_Out_ RECT *C, const RECT *A, const RECT *B).

  • Matthew van Eerde's web log

    An attempt to explain the twin prime conjecture to a five-year-old

    • 0 Comments

    Back in April, Zhang Yitang came up with a result that is a major step toward proving the twin prime conjecture that there are infinitely many primes p for which p + 2 is also prime.

    In a reddit.com/r/math thread on the subject, I made the following comment as an attempt to explain the twin prime conjecture to a five-year-old:

    ELI5 attempt at the twin prime conjecture

    Think of cookie parties.

    If you have 100 cookies, you could have a cookie party:

    • by yourself (you get all the cookies)
    • with two people (each person gets 50 cookies)
    • with four people (each person gets 25 cookies)
    • with five people (each person gets 20 cookies)
    • with ten people (each person gets ten cookies)
    • with 20 people (each person gets five cookies)
    • with 25 people (each person gets four cookies)
    • with 50 people (each person gets two cookies)
    • with 100 people (each person gets one cookie)

    If you're the only person at your party, it's a sad party.

    If everyone at the party gets only one cookie, it's a sad party.

    If someone gets more than someone else, it's a sad party.

    You don't want your party to be sad, so you have to be careful to have the right number of people to share your cookies.

    If you have two cookies, or three, or five, or seven, or eleven, then it's not possible to have a happy party. There's no "right number of people."

    People used to wonder whether you could be sure to have a happy party if you just had enough cookies. A famous person named Euclid figured out that, no matter how many cookies you had, even if it was, like, more than a million, you might be unlucky and have a sad number of cookies.

    If it's a birthday party, the birthday kid's mom might give the birthday kid an extra cookie. (Or they might get something else instead.) That would be OK.

    If it's a birthday party, then, yes, you can be sure to have a happy party if you just had enough cookies. In fact, even three cookies would be enough; you could have the birthday kid, and one friend; they would each have one cookie, and the birthday kid would get the extra one.

    But Sam and Jane have a problem. They're twins, and they always have the same birthday. One year they had 13 cookies, and it was a big problem. 13 is a sad number. Even if they both had an extra cookie, that would leave 11, and that is still a sad number.

    (If you allow the birthday kid to have two extra cookies, that would leave nine; they could invite one more person, give everyone three cookies, and then Sam and Jane could each have two extras. But this is not a happy party because the guests will get upset that the birthday kids got two extra cookies. I mean, come on!)

    Sam and Jane wondered whether they could be sure to have a happy party if they just had enough cookies.

    So they asked their mom, who is, like, super smart.

    But even she didn't know.

    In fact, no-one knows. They don't think so. But they're not, like, super-sure.

  • Matthew van Eerde's web log

    More on IAudioSessionControl and IAudioSessionControl2, plus: how to log a GUID

    • 0 Comments

    A while back I blogged about using IAudioSessionControl and IAudioSessionControl2 to get a list of active sessions, and then using IAudioMeterInformation to see what the amplitude level of the audio being played from each session was.

    I decided to go back and push this a little further and see what information there was to dig out. Pseudocode:

    CoCreate(IMMDeviceEnumerator)
    MMDevice = IMMDeviceEnumerator::GetDefaultAudioEndpoint(...)
    AudioSessionManager2 = MMDevice::Activate(...)
    AudioSessionEnumerator = AudioSessionManager2::GetSessionEnumerator()

    for each session in AudioSessionEnumerator {
        AudioSessionControl = AudioSessionEnumerator::GetSession(...)
        if (AudioSessionStateActive != AudioSessionControl::GetState()) { continue; }

        AudioSessionControl::GetIconPath (usually blank)
        AudioSessionControl::GetDisplayName (usually blank)
        AudioSessionControl::GetGroupingParam

        AudioSessionControl2 = AudioSessionControl::QueryInterface(...)
        AudioSessionControl2::GetSessionIdentifier (treat this as an opaque string)
        AudioSessionControl2::GetSessionInstanceIdentifier (treat this as an opaque string)
        AudioSessionControl2::GetProcessId (some sessions span multiple processes)
        AudioSessionControl2::IsSystemSoundsSession

        AudioMeterInformation = AudioSessionControl::QueryInterface(...)
        AudioMeterInformation::GetPeakValue

        for each top level window in the process pointed to by AudioSessionControl2::GetProcessId {
            Use WM_GETTEXTLENGTH and WM_GETTEXT to get the window text, if any
        }
    }

    Here's the output of the new version of meters.exe.

    >meters.exe
    -- Active session #1 --
        Icon path:
        Display name:
        Grouping parameter: {a204c0ad-03c4-4754-8e8f-92843aacb1fa}
        Process ID: 11812 (single-process)
        Session identifier: {0.0.0.00000000}.{125eeed2-3cd2-48cf-aac9-8ae0157564ad}|\Device\HarddiskVolume1\Windows\System32\WWAHost.exe%b{00000000-0000-0000-0000-000000000000}
        Session instance identifier: {0.0.0.00000000}.{125eeed2-3cd2-48cf-aac9-8ae0157564ad}|\Device\HarddiskVolume1\Windows\System32\WWAHost.exe%b{00000000-0000-0000-0000-000000000000}|1%b11812
        System sounds session: no
        Peak value: 0.2837

    -- Active session #2 --
        Icon path:
        Display name:
        Grouping parameter: {a2e2e0f5-81bb-407e-b701-f4f3695f9dac}
        Process ID: 15148 (single-process)
        Session identifier: {0.0.0.00000000}.{125eeed2-3cd2-48cf-aac9-8ae0157564ad}|\Device\HarddiskVolume1\Program Files (x86)\Internet Explorer\iexplore.exe%b{00000000-0000-0000-0000-000000000000}
        Session instance identifier: {0.0.0.00000000}.{125eeed2-3cd2-48cf-aac9-8ae0157564ad}|\Device\HarddiskVolume1\Program Files (x86)\Internet Explorer\iexplore.exe%b{00000000-0000-0000-0000-000000000000}|1%b15148
        System sounds session: no
        Peak value: 0.428589
        HWND: 0x0000000001330B12
        HWND: 0x0000000000361CA2
        HWND: 0x00000000019A07A8
        HWND: 0x0000000001411BF2
        HWND: 0x0000000000B60706
        HWND: 0x000000000231165A
        HWND: 0x0000000002631472
        HWND: 0x0000000000441D94

    -- Active session #3 --
        Icon path:
        Display name:
        Grouping parameter: {e191c91d-dc24-468d-b542-0d5f12ce8c48}
        Process ID: 2324 (multi-process)
        Session identifier: {0.0.0.00000000}.{125eeed2-3cd2-48cf-aac9-8ae0157564ad}|#%b{48FF2ED2-2CE8-40FB-AEF7-31FEFDBA7EF2}
        Session instance identifier: {0.0.0.00000000}.{125eeed2-3cd2-48cf-aac9-8ae0157564ad}|#%b{48FF2ED2-2CE8-40FB-AEF7-31FEFDBA7EF2}|1%b#
        System sounds session: no
        Peak value: 0.294137
        HWND: 0x0000000002900C86 Windows Media Player

    -- Active session #4 --
        Icon path: @%SystemRoot%\System32\AudioSrv.Dll,-203
        Display name: @%SystemRoot%\System32\AudioSrv.Dll,-202
        Grouping parameter: {e7d6e107-ca03-4660-a067-1a1f3dc1619c}
        Process ID: 0 (multi-process)
        Session identifier: {0.0.0.00000000}.{125eeed2-3cd2-48cf-aac9-8ae0157564ad}|#%b{A9EF3FD9-4240-455E-A4D5-F2B3301887B2}
        Session instance identifier: {0.0.0.00000000}.{125eeed2-3cd2-48cf-aac9-8ae0157564ad}|#%b{A9EF3FD9-4240-455E-A4D5-F2B3301887B2}|1%b#
        System sounds session: yes
        Peak value: 0.0502903

    -- Active session #5 --
        Icon path:
        Display name:
        Grouping parameter: {2a3e30fb-2ded-471e-9c2f-cbd8572b2af2}
        Process ID: 15948 (single-process)
        Session identifier: {0.0.0.00000000}.{125eeed2-3cd2-48cf-aac9-8ae0157564ad}|\Device\HarddiskVolume1\Program Files (x86)\VideoLAN\VLC\vlc.exe%b{00000000-0000-0000-0000-000000000000}
        Session instance identifier: {0.0.0.00000000}.{125eeed2-3cd2-48cf-aac9-8ae0157564ad}|\Device\HarddiskVolume1\Program Files (x86)\VideoLAN\VLC\vlc.exe%b{00000000-0000-0000-0000-000000000000}|1%b15948
        System sounds session: no
        Peak value: 0.287567
        HWND: 0x0000000000C8160C Opening Ceremony - VLC media player

    Active sessions: 5

    Part of this was logging the grouping parameter, which is a GUID. I've seen a lot of code that converts the GUID to a string and logs it using %s. Another way is to use a couple of macros and let the format string do the conversion for you:

    #define GUID_FORMAT L"{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}"
    #define GUID_VALUES(g) \
    g.Data1, g.Data2, g.Data3, \
    g.Data4[0], g.Data4[1], g.Data4[2], g.Data4[3], \
    g.Data4[4], g.Data4[5], g.Data4[6], g.Data4[7]

    ...

    GUID someGuid = ...;

    LOG(L"The value of someGuid is " GUID_FORMAT L".", GUID_VALUES(someGuid));

    Standard caveats about not using side effects inside a macro apply. For example, this would be a bug:

    for (GUID *p = ...) {
        LOG(L"p = " GUID_FORMAT L".", GUID_VALUES(*(p++)); // BUG!
    }

     Source, amd64 binaries, and x86 binaries attached.

     

  • Matthew van Eerde's web log

    Getting the package full name of a Windows Store app, given the process ID

    • 0 Comments

    Last time I talked about enumerating audio sessions and showed an example which listed several Desktop apps and one Windows Store app.

    Session instance identifier: {0.0.0.00000000}.{125eeed2-3cd2-48cf-aac9-8ae0157564ad}|\Device\HarddiskVolume1\Windows\System32\WWAHost.exe%b{00000000-0000-0000-0000-000000000000}|1%b11812

    It's possible to guess that this is a Windows Store app by the presence of the WWAHost.exe string in the session instance identifier. Don't rely on this, though; the session identifiers are opaque strings, and their formula can change at any time.

    We were able to get some additional information on the Desktop apps by enumerating their top-level windows and reading the window text. But how do we get more information on the Windows Store app? And how do we even know it's a Windows Store app without cracking the session identifier?

    By using the Application Model APIs - for example, GetPackageFullName.

    Pseudocode:

    ... get a process ID...

    OpenProcess(PROCESS_QUERY_LIMITED_USER_INFORMATION, FALSE, pid);

    GetPackageFullName(...)

    if APPMODEL_ERROR_NO_PACKAGE then the process has no associated package and is therefore not a Windows Store app.

    Updated sample output:

    -- Active session #4 --
    Icon path:
    Display name:
    Grouping parameter: {8dbd87b0-9fce-4c27-b7ff-4b20b0dae1a3}
    Process ID: 11644 (single-process)
    Session identifier: {0.0.0.00000000}.{125eeed2-3cd2-48cf-aac9-8ae0157564ad}|\Device\HarddiskVolume1\Windows\System32\WWAHost.exe%b{00000000-0000-0000-0000-000000000000}
    Session instance identifier: {0.0.0.00000000}.{125eeed2-3cd2-48cf-aac9-8ae0157564ad}|\Device\HarddiskVolume1\Windows\System32\WWAHost.exe%b{00000000-0000-0000-0000-000000000000}|1%b11644
    System sounds session: no
    Peak value: 0.395276
    Package full name: Microsoft.ZuneMusic_2.0.132.0_x64__8wekyb3d8bbwe

    Source and binaries attached.

  • Matthew van Eerde's web log

    Even if someone's signaling right, they still have the right of way

    • 0 Comments

    I was driving to work this morning and I had an experience which vindicated my paranoia, and may even have passed it on to someone else.

    I was heading East on NE 80th St approaching 140th Ave NE in Redmond. This is a two-way stop; drivers on 140th have the right of way and do not stop. Drivers on 80th (me) have to stop.

    I came to a full stop and signaled right (I wanted to head South on 140th). A driver (let's call him Sam) pulled up behind me, also signaling right. There were three cars heading South on 140th, all of them signaling right (they wanted to head West on 80th).

    At this point I had a conversation with myself that went something like this.

    Well, Matt, you could turn right now. All those cars are turning right, so they won't hit you.
    But wait, Matt. Those cars have the right of way. Sure they're signaling right. But that doesn't mean they'll actually turn right.
    Yup, you're right, Matt. Better to wait to see what actually happens.

    So I waited, and sure enough, all three cars actually turned right. So I suppose I could have gone. And more cars were feeding in to 140th from Redmond Way. And all of these cars were signaling right. And one was a school bus.

    At this point Sam (remember Sam?) got impatient and honked his horn. This shocked me a little.

    I imagine anyone who is from New York or Los Angeles is shaking their heads at me now. Not for waiting, but for being shocked. "He honked his horn? So what?"

    (This is a cultural difference. In New York or Los Angeles, if you're waiting at a red light, you will get honked at as soon as the other guy's light turns yellow. But in Washington, the guy behind you will calmly wait through two full greens, then politely knock on your window and ask if everything is OK.)

    I trust the school bus even less than the cars, so I let the school bus go.

    The car behind the school bus is a minivan. He's signaling right, too. But I let him go as well... and he goes straight!

    Behind the minivan, there's enough of a gap that I feel comfortable pulling out, so I do. And Sam pulls up to the line.

    As I'm cruising down 140th, I glance in my rear-view mirror. I see a line of cars coming down 140th to the intersection I just left, all signaling right...

    ... and I see my friend Sam...

    ... patiently waiting.

    May the Force be with you, Sam.

  • Matthew van Eerde's web log

    Grabbing large amounts of text from STDIN in O(n) time

    • 0 Comments

    Last time I blogged about an O(n log n) solution to finding the longest duplicated substring in a given piece of text; I have since found an O(n) algorithm, which I linked to in the comments.

    But my blog post used an O(n2) algorithm to read the text from STDIN! It looked something like this:

    while (!done) {
        grab 2 KB of text
        allocate a new buffer which is 2 KB bigger
        copy the old text and the new text together into the new buffer
        free the old buffer
    }

    There are two better algorithms:

    while (!done) {
        grab an amount of text equal to the amount we've grabbed so far
        allocate a new buffer which is twice as large as the last buffer
        copy the old text and the new text together into the new buffer
        free the old buffer
    }

    And:

    while (!done) {
        grab 2 KB of text
        add this to the end of a linked list of text chunks
    }

    allocate a buffer whose size is the total size of all the chunks added together
    walk the linked list and copy the text of each chunk into the buffer

    Both "better" algorithms are O(n) but the latter wastes less space.

    Here's the improved code:

    struct Chunk {
        WCHAR text[1024];
        Chunk *next;
       
        Chunk() : next(nullptr) { text[0] = L'\0'; }
    };

    class DeleteChunksOnExit {
    public:
        DeleteChunksOnExit() : m_p(nullptr) {}
        ~DeleteChunksOnExit() {
            Chunk *here = m_p;
            while (here) {
                Chunk *next = here->next;
                delete here;
                here = next;
            }
        }
        void Set(Chunk *p) { m_p = p; }
       
    private:
        Chunk *m_p;
    };

    ...

    LPWSTR ReadFromStdIn() {
        Chunk *head = nullptr;
        Chunk *tail = nullptr;

        DeleteChunksOnExit dcoe;
       
        size_t total_length = 0;
       
        bool done = false;
        while (!done) {
            Chunk *buffer = new Chunk();
            if (nullptr == buffer) {
                LOG(L"Could not allocate memory for buffer");
                return nullptr;
            }
           
            if (nullptr == head) {
                // this runs on the first pass only
                head = buffer;
                tail = buffer;
                dcoe.Set(buffer);
            } else {
                tail->next = buffer;
                tail = buffer;
            }
           
            if (fgetws(buffer->text, ARRAYSIZE(buffer->text), stdin)) {
                total_length += wcslen(buffer->text);
            } else if (feof(stdin)) {
                done = true;
            } else {
                LOG(L"Error reading from STDIN");
                return nullptr;
            }
        }
       
        // gather all the allocations into a single string
        size_t size = total_length + 1;
        WCHAR *text = new WCHAR[size];
        if (nullptr == text) {
            LOG(L"Could not allocate memory for text");
            return nullptr;
        }
        DeleteArrayOnExit<WCHAR> deleteText(text);
       
        WCHAR *temp = text;
        for (Chunk *here = head; here; here = here->next) {
            if (wcscpy_s(temp, size, here->text)) {
                LOG(L"wcscpy_s returned an error");
                return nullptr;
            }
           
            size_t len = wcslen(here->text);
            temp += len;
            size -= len;
        }
       
        deleteText.Cancel();
        return text;
    }

  • Matthew van Eerde's web log

    Changing the desktop wallpaper using IDesktopWallpaper

    • 0 Comments

    About a year ago I wrote about how to change the desktop wallpaper using SystemParametersInfo(SPI_SETDESKWALLPAPER).

    Windows 8 desktop apps (not Store apps) can use the new IDesktopWallpaper API to get a more fine level of control.  So I wrote an app which uses the new API, though I just set the background on all monitors to the same image path, and I don't exercise any of the advanced features of the API.

    Pseudocode:

    CoInitialize
    CoCreateInstance(DesktopWallpaper)
    pDesktopWallpaper->SetWallpaper(NULL, full-path-to-image-file)
    pDesktopWallpaper->Release()
    CoUninitialize

    Usage:

    >desktopwallpaper.exe "%userprofile%\pictures\theda-bara.bmp"
    Setting the desktop wallpaper to C:\Users\MatEer\pictures\theda-bara.bmp succeeded.

    Source and binaries attached

  • Matthew van Eerde's web log

    Programmatically adding a folder to a shell library (e.g., the Music library)

    • 0 Comments

    I wrote a selfhost tool which allows me to add a folder (for example, C:\music) to a shell library (for example, the Music library.)

    This was before I found out about the shlib shell library sample which Raymond Chen blogged about.  If you're looking for a sample on how to manipulate shell libraries, prefer that one to this.

    Pseudocode:

    CoInitialize
    pShellLibrary = SHLoadLibraryFromKnownFolder(library GUID)
    SHAddFolderPathToLibrary(pShellLibrary, path)
    pShellLibrary->Commit()
    CoUninitialize

    Usage:

    >shelllibrary
    shelllibrary add <path> to <library>
        <path> must already exist
        <library> must be one of:
            documents
            music
            pictures
            videos
            recorded tv
    >shelllibrary add C:\music to Music
    Added C:\music to Music library

    Source and binaries attached.

  • Matthew van Eerde's web log

    Generating sample first names

    • 0 Comments

    I had a need to write a script that would give me a random first name.  I grabbed the top 200 first names for baby boys in the US from 2000-2009, and the same list for baby girls:

    http://www.ssa.gov/OACT/babynames/decades/names2000s.html

    Boys Girls
    Jacob Emily
    Michael Madison
    ... ...

    My initial implementation just printed out the name, but I quickly realized I needed to print out the gender if I wanted to talk about what the (fictitious) person did.  So I updated it to print out the gender as well.

    In the course of this I realized that some names appeared on both lists.  In particular they are:

    • Alexis
    • Angel
    • Jordan
    • Peyton
    • Riley

    The script is called like this:

    >perl -w name.pl
    Wesley (male)

    And here's the source:

    use strict;

    # prints a randomly chosen name

    sub read_words();

    my @words = read_words();
    print $words[ rand(@words) ];

    sub read_words() {
        my @words = <DATA>;

        chomp @words;

        return @words;
    }
    __DATA__
    Aaliyah (female)
    Aaron (male)
    Abby (female)
    Abigail (female)
    Abraham (male)
    Adam (male)
    Addison (female)
    Adrian (male)
    Adriana (female)
    Adrianna (female)
    Aidan (male)
    Aiden (male)
    Alan (male)
    Alana (female)
    Alejandro (male)
    Alex (male)
    Alexa (female)
    Alexander (male)
    Alexandra (female)
    Alexandria (female)
    Alexia (female)
    Alexis (female)
    Alexis (male)
    Alicia (female)
    Allison (female)
    Alondra (female)
    Alyssa (female)
    Amanda (female)
    Amber (female)
    Amelia (female)
    Amy (female)
    Ana (female)
    Andrea (female)
    Andres (male)
    Andrew (male)
    Angel (female)
    Angel (male)
    Angela (female)
    Angelica (female)
    Angelina (female)
    Anna (female)
    Anthony (male)
    Antonio (male)
    Ariana (female)
    Arianna (female)
    Ashley (female)
    Ashlyn (female)
    Ashton (male)
    Aubrey (female)
    Audrey (female)
    Austin (male)
    Autumn (female)
    Ava (female)
    Avery (female)
    Ayden (male)
    Bailey (female)
    Benjamin (male)
    Bianca (female)
    Blake (male)
    Braden (male)
    Bradley (male)
    Brady (male)
    Brandon (male)
    Brayden (male)
    Breanna (female)
    Brendan (male)
    Brian (male)
    Briana (female)
    Brianna (female)
    Brittany (female)
    Brody (male)
    Brooke (female)
    Brooklyn (female)
    Bryan (male)
    Bryce (male)
    Bryson (male)
    Caden (male)
    Caitlin (female)
    Caitlyn (female)
    Caleb (male)
    Cameron (male)
    Camila (female)
    Carlos (male)
    Caroline (female)
    Carson (male)
    Carter (male)
    Cassandra (female)
    Cassidy (female)
    Catherine (female)
    Cesar (male)
    Charles (male)
    Charlotte (female)
    Chase (male)
    Chelsea (female)
    Cheyenne (female)
    Chloe (female)
    Christian (male)
    Christina (female)
    Christopher (male)
    Claire (female)
    Cody (male)
    Colby (male)
    Cole (male)
    Colin (male)
    Collin (male)
    Colton (male)
    Conner (male)
    Connor (male)
    Cooper (male)
    Courtney (female)
    Cristian (male)
    Crystal (female)
    Daisy (female)
    Dakota (male)
    Dalton (male)
    Damian (male)
    Daniel (male)
    Daniela (female)
    Danielle (female)
    David (male)
    Delaney (female)
    Derek (male)
    Destiny (female)
    Devin (male)
    Devon (male)
    Diana (female)
    Diego (male)
    Dominic (male)
    Donovan (male)
    Dylan (male)
    Edgar (male)
    Eduardo (male)
    Edward (male)
    Edwin (male)
    Eli (male)
    Elias (male)
    Elijah (male)
    Elizabeth (female)
    Ella (female)
    Ellie (female)
    Emily (female)
    Emma (female)
    Emmanuel (male)
    Eric (male)
    Erica (female)
    Erick (male)
    Erik (male)
    Erin (female)
    Ethan (male)
    Eva (female)
    Evan (male)
    Evelyn (female)
    Faith (female)
    Fernando (male)
    Francisco (male)
    Gabriel (male)
    Gabriela (female)
    Gabriella (female)
    Gabrielle (female)
    Gage (male)
    Garrett (male)
    Gavin (male)
    Genesis (female)
    George (male)
    Gianna (female)
    Giovanni (male)
    Giselle (female)
    Grace (female)
    Gracie (female)
    Grant (male)
    Gregory (male)
    Hailey (female)
    Haley (female)
    Hannah (female)
    Hayden (male)
    Hector (male)
    Henry (male)
    Hope (female)
    Hunter (male)
    Ian (male)
    Isaac (male)
    Isabel (female)
    Isabella (female)
    Isabelle (female)
    Isaiah (male)
    Ivan (male)
    Jack (male)
    Jackson (male)
    Jacob (male)
    Jacqueline (female)
    Jada (female)
    Jade (female)
    Jaden (male)
    Jake (male)
    Jalen (male)
    James (male)
    Jared (male)
    Jasmin (female)
    Jasmine (female)
    Jason (male)
    Javier (male)
    Jayden (male)
    Jayla (female)
    Jazmin (female)
    Jeffrey (male)
    Jenna (female)
    Jennifer (female)
    Jeremiah (male)
    Jeremy (male)
    Jesse (male)
    Jessica (female)
    Jesus (male)
    Jillian (female)
    Jocelyn (female)
    Joel (male)
    John (male)
    Johnathan (male)
    Jonah (male)
    Jonathan (male)
    Jordan (female)
    Jordan (male)
    Jordyn (female)
    Jorge (male)
    Jose (male)
    Joseph (male)
    Joshua (male)
    Josiah (male)
    Juan (male)
    Julia (female)
    Julian (male)
    Juliana (female)
    Justin (male)
    Kaden (male)
    Kaitlyn (female)
    Kaleb (male)
    Karen (female)
    Karina (female)
    Kate (female)
    Katelyn (female)
    Katherine (female)
    Kathryn (female)
    Katie (female)
    Kayla (female)
    Kaylee (female)
    Kelly (female)
    Kelsey (female)
    Kendall (female)
    Kennedy (female)
    Kenneth (male)
    Kevin (male)
    Kiara (female)
    Kimberly (female)
    Kyle (male)
    Kylee (female)
    Kylie (female)
    Landon (male)
    Laura (female)
    Lauren (female)
    Layla (female)
    Leah (female)
    Leonardo (male)
    Leslie (female)
    Levi (male)
    Liam (male)
    Liliana (female)
    Lillian (female)
    Lilly (female)
    Lily (female)
    Lindsey (female)
    Logan (male)
    Lucas (male)
    Lucy (female)
    Luis (male)
    Luke (male)
    Lydia (female)
    Mackenzie (female)
    Madeline (female)
    Madelyn (female)
    Madison (female)
    Makayla (female)
    Makenzie (female)
    Malachi (male)
    Manuel (male)
    Marco (male)
    Marcus (male)
    Margaret (female)
    Maria (female)
    Mariah (female)
    Mario (male)
    Marissa (female)
    Mark (male)
    Martin (male)
    Mary (female)
    Mason (male)
    Matthew (male)
    Max (male)
    Maxwell (male)
    Maya (female)
    Mckenzie (female)
    Megan (female)
    Melanie (female)
    Melissa (female)
    Mia (female)
    Micah (male)
    Michael (male)
    Michelle (female)
    Miguel (male)
    Mikayla (female)
    Miranda (female)
    Molly (female)
    Morgan (female)
    Mya (female)
    Naomi (female)
    Natalia (female)
    Natalie (female)
    Nathan (male)
    Nathaniel (male)
    Nevaeh (female)
    Nicholas (male)
    Nicolas (male)
    Nicole (female)
    Noah (male)
    Nolan (male)
    Oliver (male)
    Olivia (female)
    Omar (male)
    Oscar (male)
    Owen (male)
    Paige (female)
    Parker (male)
    Patrick (male)
    Paul (male)
    Payton (female)
    Peter (male)
    Peyton (female)
    Peyton (male)
    Preston (male)
    Rachel (female)
    Raymond (male)
    Reagan (female)
    Rebecca (female)
    Ricardo (male)
    Richard (male)
    Riley (female)
    Riley (male)
    Robert (male)
    Ruby (female)
    Ryan (male)
    Rylee (female)
    Sabrina (female)
    Sadie (female)
    Samantha (female)
    Samuel (male)
    Sara (female)
    Sarah (female)
    Savannah (female)
    Sean (male)
    Sebastian (male)
    Serenity (female)
    Sergio (male)
    Seth (male)
    Shane (male)
    Shawn (male)
    Shelby (female)
    Sierra (female)
    Skylar (female)
    Sofia (female)
    Sophia (female)
    Sophie (female)
    Spencer (male)
    Stephanie (female)
    Stephen (male)
    Steven (male)
    Summer (female)
    Sydney (female)
    Tanner (male)
    Taylor (female)
    Thomas (male)
    Tiffany (female)
    Timothy (male)
    Travis (male)
    Trenton (male)
    Trevor (male)
    Trinity (female)
    Tristan (male)
    Tyler (male)
    Valeria (female)
    Valerie (female)
    Vanessa (female)
    Veronica (female)
    Victor (male)
    Victoria (female)
    Vincent (male)
    Wesley (male)
    William (male)
    Wyatt (male)
    Xavier (male)
    Zachary (male)
    Zoe (female)
    Zoey (female)

  • Matthew van Eerde's web log

    How to dump Speech API object properties

    • 0 Comments

    Stamatis Pap asked in a forum thread how to use a Speech API ISpVoice with a non-default audio deviceThis MSDN article shows how to use SpEnumTokens to list all the currently active audio outputs, but the number and order of audio outputs is subject to change as things come and go, or as the default audio device changes.

    I spent some time poking around the Speech API documentation and discovered that each audio output object has a DeviceId string value which is the WASAPI endpoint ID; this is the way to recognize a given audio output rather than relying on enumeration order.

    As part of figuring this out, as a side effect I created a command-line tool to dump all the speech objects and all of their properties.

    Source and binaries attached.

    Pseudocode:


    for each object category in
        (audio outputs; audio inputs; voices; recognizers; etc.)
        SpEnumTokens(category)
        ISpEnumTokens::GetCount();

        for each token
            ISpEnumTokens::Next(1);

            SpGetDescription(ISpObjectToken);
            Log the description

            the ISpObjectToken is also an ISpDataKey
            the ISpDataKey may also contain subkeys
            log all subkeys and their values recursively
                using ISpDataKey::EnumKeys and ISpDataKey::OpenKey
            for each subkey including this one
                log all values in the ISpDataKey
                    using ISpDataKey::EnumValues and ISpDataKey::GetStringValue

    Here's the output on my system.  Note the audio output has a DeviceId string value which matches the WASAPI endpoint ID.

    >speech-attributes.exe

    -- SPCAT_AUDIOOUT --
      #1: [[Speakers] ([High Definition Audio Device])]
        Attributes
          Vendor = Microsoft
          Technology = MMSys
        (default) = [[Speakers] ([High Definition Audio Device])]
        CLSID = {A8C680EB-3D32-11D2-9EE7-00C04F797396}
        DeviceName = [[Speakers] ([High Definition Audio Device])]
        DeviceId = {0.0.0.00000000}.{c2cbdacb-a70d-4629-8368-542a00f5a4b0}

    -- SPCAT_AUDIOIN --

    -- SPCAT_VOICES --
      #1: Microsoft Zira Desktop - English (United States)
        Attributes
          Version = 10.4
          Language = 409
          Gender = Female
          Age = Adult
          SharedPronunciation =
          Name = Microsoft Zira Desktop
          Vendor = Microsoft
        (default) = Microsoft Zira Desktop - English (United States)
        LangDataPath = C:\Windows\Speech\Engines\TTS\en-US\MSTTSLocEnUS.dat
        VoicePath = C:\Windows\Speech\Engines\TTS\en-US\M1033ZIR
        409 = Microsoft Zira Desktop - English (United States)
        CLSID = {C64501F6-E6E6-451f-A150-25D0839BC510}

    -- SPCAT_RECOGNIZERS --

    -- SPCAT_APPLEXICONS --

    -- SPCAT_PHONECONVERTERS --
      #1: Simplified Chinese Phone Converter
        Attributes
          Language = 804
        (default) = Simplified Chinese Phone Converter
        PhoneMap = (lengthy value redacted)
        CLSID = {9185F743-1143-4C28-86B5-BFF14F20E5C8}
      #2: English Phone Converter
        Attributes
          Language = 409
        (default) = English Phone Converter
        PhoneMap = (lengthy value redacted)
        CLSID = {9185F743-1143-4C28-86B5-BFF14F20E5C8}
      #3: French Phone Converter
        Attributes
          Language = 40C
        (default) = French Phone Converter
        PhoneMap = (lengthy value redacted)
        CLSID = {9185F743-1143-4C28-86B5-BFF14F20E5C8}
      #4: German Phone Converter
        Attributes
          Language = 407
        (default) = German Phone Converter
        PhoneMap = (lengthy value redacted)
        CLSID = {9185F743-1143-4C28-86B5-BFF14F20E5C8}
      #5: Japanese Phone Converter
        Attributes
          Language = 411
          NumericPhones =
          NoDelimiter =
        (default) = Japanese Phone Converter
        PhoneMap = (lengthy value redacted)
        CLSID = {9185F743-1143-4C28-86B5-BFF14F20E5C8}
      #6: Spanish Phone Converter
        Attributes
          Language = 40A;C0A
        (default) = Spanish Phone Converter
        PhoneMap = (lengthy value redacted)
        CLSID = {9185F743-1143-4C28-86B5-BFF14F20E5C8}
      #7: Traditional Chinese Phone Converter
        Attributes
          Language = 404
          NumericPhones =
          NoDelimiter =
        (default) = Traditional Chinese Phone Converter
        PhoneMap = (lengthy value redacted)
        CLSID = {9185F743-1143-4C28-86B5-BFF14F20E5C8}
      #8: Universal Phone Converter
        Attributes
          Language = (lengthy value redacted)
        (default) = Universal Phone Converter
        PhoneMap = (lengthy value redacted)
        CLSID = {9185F743-1143-4C28-86B5-BFF14F20E5C8}

    -- SPCAT_RECOPROFILES --
      None found.

     

  • Matthew van Eerde's web log

    Linearity of Windows volume APIs - render session and stream volumes

    • 0 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. We have also talked about how to read IAudioMeterInformation and how the limiter can attenuate full-scale signals.

    The last post had a volume-linearity.exe which, when called with --signal, showed that IAudioMeterInformation is linear in amplitude.

    Today we'll look at the --stream, --channel, and --session arguments, which explore the linearity of IAudioStreamVolume, IChannelAudioVolume, and ISimpleAudioVolume respectively. Each of these modes plays a half-scale square wave, then set the volume API to various levels, and reads the resulting IAudioMeterInformation. We use a half-scale square wave to avoid running afoul of the limiter; we expect a meter reading of 0.5 when the volume is set to 1.  The graphs below have their meter readings doubled to account for the fact that we're using a half-scale square wave rather than a full-scale.

    Here's what we get for IAudioStreamVolume, graph-inated for your convenience:

    And IChannelAudioVolume:

    And ISimpleAudioVolume:

    We already know that IAudioMeterInformation is linear in amplitude. We now know that IAudioStreamVolume, IChannelAudioVolume, and ISimpleAudioVolume have a linear effect (with slope 1 and intercept 0) on IAudioMeterInformation. We infer that IAudioStreamVolume, IChannelAudioVolume, and ISimpleAudioVolume are linear in amplitude.

  • Matthew van Eerde's web log

    Nitpicking Sam Loyd - a wheel within a wheel

    • 0 Comments

    In August 1878 Sam Loyd published this mate in two and dedicated it to a friend of his named Wheeler:


    Mate in two; Black to move and mate in two; Selfmate in two; Black to move and selfmate in two

    While the mates appear to stand up, the problem position is not legal. White has three a-pawns; this implies at least three Black pieces were captured by a White pawn. But Black has fifteen pieces on the board; only one is missing!

    Looking at Black pawn captures - the b2-, c-, and d- pawns together account for three pawn captures. This seems OK at first glance since White has three pieces missing. But all the missing White pieces are pawns, and they are from the right half of the board... so they must have promoted. This implies more pawn captures to either get the Black pawns out of the way or to get the White pawns around them. (The promoted pieces could have been captured by the Black pawns, or the original pieces could have been captured in which case the promoted pieces are on the board now.)

    Finally, the h-pawns on h5 and h6 could not have got into their present position without at least one pawn capture by White, or at least two pawn captures by Black.

  • Matthew van Eerde's web log

    Mark your variadic logging function with __format_string to have PREfast catch format specifier errors

    • 0 Comments

    There are a handful of Problems (with a capital P) which occur over and over again in programming. One of them is Logging.

    It is incredibly convenient to use the variadic printf function to log strings with values of common types embedded in them:

    // spot the bug
    LOG(L"Measurement shows %lg% deviation", 100.0 * abs(expected - actual) / expected);

    However, printf is very error prone. It is very easy to use the wrong format specifier like %d instead of %Id, or to forget to escape a special character like % or \.
    In particular, the above line contains a bug.

    Static code analysis tools like PREfast are quite good at catching these kinds of errors. If my LOG macro was something like this, PREfast would catch the bug:

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

    This works because PREfast knows that the first argument to wprintf is a format string, and can match up the format specifiers with the trailing arguments and verify that they match.

    If you implement your own variadic logger function, though, PREfast doesn't necessarily know that the last explicit argument is a format specifier - you have to tell it. For example, PREfast will NOT catch format specifier issues if the LOG macro is defined like this:

    // PREfast doesn't know Format is a format string
    interface IMyLogger { virtual void Log(LPCWSTR Format, ...) = 0; };
    extern IMyLogger *g_Logger;
    #define LOG(fmt, ...) g_Logger->Log(fmt, __VA_ARGS__)

    How do you tell it? Well, let's look at the declaration of wprintf. It's in (SDK)\inc\crt\stdio.h:

    _CRTIMP __checkReturn_opt int __cdecl wprintf(__in_z __format_string const wchar_t * _Format, ...);

    The relevant part here is __format_string. So the fixed IMyLogger declaration looks like this:

    // Now PREfast can catch format specifier issues
    interface IMyLogger { virtual void Log(__format_string LPCWSTR Format, ...) = 0; };
    extern IMyLogger *g_Logger;
    #define LOG(fmt, ...) g_Logger->Log(fmt, __VA_ARGS__)

  • Matthew van Eerde's web log

    Beep sample

    • 0 Comments

    A question came in today about the Beep(...) API1 not being able to set the frequency of the beep that was generated. In order to confirm that it worked I whipped up a quick sample which would take the frequency (and duration) on the command line. Source and binaries attached.

    For fun I added the ability to pass in the frequency using Scientific pitch notation. Note that A4 is about 431 Hz using this scale, rather than the more standard 440 Hz2.

    for (int i = 1; i + 1 < argc; i += 2) {

         ULONG frequency;
         HRESULT hr = HertzFromScientificPitchNotation(argv[i], &frequency);
         if (FAILED(hr)) { return -__LINE__; }

         ULONG duration;
         hr = UlongFromString(argv[i + 1], &duration);
         if (FAILED(hr)) { return -__LINE__; }

         if (!Beep(frequency, duration)) {
             LOG(L"Beep(%u, %u) failed: GetLastError() = %u", frequency, duration, GetLastError());
             return -__LINE__;
         }
    }

    So, for example, you can play a certain well-known tune via Beep() using this command:

    >beep.exe C3 2000 G3 2000 C4 4000 E4 500 Eb4 3500 C3 500 G2 500 C3 500 G2 500 C3 500 G2 500 C3 2000

    1 More on the Beep(...) API:

    The official Beep(...) documentation

    A couple of blog posts from Larry Osterman:
    Beep Beep
    What’s up with the Beep driver in Windows 7?

    2 If you want the more standard pitch, change this line:

    double freq = 256.0;

    To this:

    double freq = 440.0 * pow(semitoneRatio, -9.0); // C4 is 9 semitones below A4

  • Matthew van Eerde's web log

    Command-line app to set the desktop wallpaper

    • 0 Comments

    Working on Windows, I find myself installing Windows a lot.

    I find that I like to change a lot of the settings that Windows offers to non-default values.  (That is, I'm picky.)

    I have a script which automates some of these things, which I add to now and again.  Some of the bits of the script are straightforward, but once in a while the tweak itself is of interest.

    One of the things I love about my work setup is the many large monitors.  So, one of the things I like to change is the desktop wallpaper image.

    Changing the desktop wallpaper required some code, which makes it "of interest."  Here's the code.

    // main.cpp

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

    int _cdecl wmain(int argc, LPCWSTR argv[]) {
        if (1 != argc - 1) {
            wprintf(L"expected a single argument, not %d\n", argc);
            return -__LINE__;
        }
       
        if (!SystemParametersInfo(
            SPI_SETDESKWALLPAPER,
            0,
            const_cast<LPWSTR>(argv[1]),
            SPIF_SENDCHANGE
        )) {
            DWORD dwErr = GetLastError();
            wprintf(L"SystemParametersInfo(...) failed with error %d\n", dwErr);
            return -__LINE__;
        }
       
        wprintf(L"Setting the desktop wallpaper to %s succeeded.\n", argv[1]);   
        return 0;
    }

     

    Binaries attached.

    Warning: if you pass a relative path to this tool, it won't qualify it for you, and the SystemParametersInfo call won't either - so the wallpaper you want won't be set, though all the calls will succeed.  Make sure to specify a fully-qualified path.

     

Page 5 of 6 (138 items) «23456