Matthew van Eerde's web log

  • 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

    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

    How to install unsigned drivers

    • 4 Comments

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

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

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

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

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

    Even so, there are still workarounds provided:

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

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

    To sign a driver:

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

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

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

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

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

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

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

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

  • Matthew van Eerde's web log

    How to validate and log a WAVEFORMATEX

    • 4 Comments

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

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

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

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

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

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

    And here's the code that produces it:

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

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

                    return S_OK;
            }
        }
    }


  • Matthew van Eerde's web log

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

    • 2 Comments

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

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

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

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

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

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

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

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

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

  • Matthew van Eerde's web log

    Disney princesses: an attempt at a complete list

    • 24 Comments

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

    Snow White and the Seven Dwarfs (1937)

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

    Bambi (1942)

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

    Cinderella (1950)

    Cinderella
    Prince's Wife
    Disney Princess

    Peter Pan (1953)

    Tiger Lily
    Chief's Daughter
    Disney Princess

    Sleeping Beauty (1959)

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

    Robin Hood (1973)

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

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

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

    The Black Cauldron (1985)

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

    Castle in the Sky (1986: Studio Ghibli)

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

    Oliver & Company (1988)

    Jenny
    Wealthy
    Not a princess

    The Little Mermaid (1989)

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

    Beauty and the Beast (1991)

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

    Aladdin (1992)

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

    The Lion King (1994)

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

    Pocahontas (1995)

    Pocahontas
    Chief's Daughter
    Disney Princess

    Princess Mononoke (1997: Studio Ghibli)

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

    A Bug's Life (1998: Pixar)

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

    Atlantis: The Lost Empire (2001)

    Kida
    King's Daughter; Queen
    Disney Princess

    Tales from Earthsea (2006: Studio Ghibli)

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

    Ponyo (2008: Studio Ghibli)

    Ponyo
    Queen's Daughter
    Disney(?) Princess

    The Princess and the Frog (2009)

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

    Tangled (2010)

    Rapunzel
    King & Queen's Daughter
    Disney Princess

    Brave (2012)

    Merida
    King & Queen's Daughter
    Disney Princess

    Wreck-it Ralph (2012)

    Vanellope von Schweetz
    Princess
    Disney Princess

    Frozen (2013)

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

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

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

  • Matthew van Eerde's web log

    Disney princess pedigree: Belle

    • 1 Comments

    Is Belle a Disney princess?

    The case in favor

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

    Throughout the movie Belle handles herself with princessly aplomb:

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

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

    The case against

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

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

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

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

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

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

    The verdict

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

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

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

    Verdict: Controversial

  • Matthew van Eerde's web log

    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

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

    • 12 Comments

    Source and binaries (amd64 and x86) attached.

    Pseudocode:

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

    Output on my system:

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

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

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

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

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

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

  • Matthew van Eerde's web log

    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

    Car Talk puzzler analysis - palindromic odometers

    • 3 Comments

    Last week's Car Talk question was again of mathematical interest - it dealt with palindromic numbers on a car's odometer. (This is not the first palindromic odometer question they've done.)

    Read Car Talk's "Tommy's Drive to Work" puzzler question

    Short version:

    Tommy goes for a short drive (under an hour; possibly well under an hour) in his 19-year old new car. He notices that the odometer reading at the beginning and the end of the drive are both palindromes (we're given that his odometer shows six digits.) The question is, how many miles did he drive?

    You're supposed to do a bunch of math, and then experience an epiphany when you realize that, against all odds, there's a single unique answer, even though the odometer readings themselves cannot be uniquely determined.

    Alas, the problem is flawed. There are two perfectly good (and different) answers.

    The intended answer

    The first reading is given as a palindrome - the first three digits (let's call them a, b, c) are the same as the last three digits in reverse (c, b, a). So we can write the first number as abccba.

    The second reading is also given as a palindrome, and we're given that it's a different palindrome (we infer that the odometer is not broken, and he drove at least a mile.) So we can write the second number as xyzzyx.

    We want to find whether it's possible to get these two readings while driving a reasonably small distance. The distance driven is the difference between the odometer readings: xyzzyxabccba.

    xyzzyxabccba
         = (100001 x + 10010 y + 1100 z) – (100001 a + 10010 b + 1100 c)
         = 100001 (xa) + 10010 (yb) + 1100 (zc)

    Because xyzzyx > abccba, we know that xa. Also, if x = a, we know that yb. Finally, if x = a and y = b, we know that z > c (z cannot equal c in this case.) Let's look at these three cases separately.

    Case 1: x = a, y = b, z > c
    xyzzyxabccba
         = 100001 (xa) + 10010 (yb) + 1100 (zc)
         = 100001 (0) + 10010 (0) + 1100 (zc)
         = 1100 (zc)

    z and c can range from 0 to 9, and z > c, so (zc) can range from 1 to 9. So the smallest the distance can be is 1100 miles - for example, 250052 → 259952 (1100 miles.) This is a bit far for an hour's drive.

    Case 2: x = a, y > b, no conditions imposed on z vs. c
    xyzzyxabccba
         = 100001 (xa) + 10010 (yb) + 1100 (zc)
         = 100001 (0) + 10010 (yb) + 1100 (zc)
         = 10010 (yb) + 1100 (zc)

    y, b, z, and c can range from 0 to 9; y > b; and no conditions are imposed on z vs. c. (yb) can range from 1 to 9, (zc) can range from -9 to 9. So the smallest the distance can be is 100101 + 1100 (-9) = 110 miles - for example, 379973 → 380083 (110 miles.) It's certainly possible to drive 110 miles in an hour but not without attracting the attention of the local police!

    Case 3: x > a, no conditions imposed on y vs. b or z vs. c
    xyzzyxabccba
         = 100001 (xa) + 10010 (yb) + 1100 (zc)

    x, a, y, b, z, and c can range from 0 to 9; x > a; and no conditions are imposed on y vs. b, or z vs. c. (xa) can range from 1 to 9, (yb) and (zc) can range from -9 to 9. So the smallest the distance can be is 100001 (1) + 10010 (-9) + 1100 (-9) = 11 miles - for example, 499994 → 500005 (11 miles.) This is much more reasonable.

    The intended answer, then, is 11 miles. There are 9 possible ways to get this distance with palindromic starting and ending readings:

    099990 → 100001 (11 miles)
    199991 → 200002 (11 miles)
    299992 → 300003 (11 miles)
    399993 → 400004 (11 miles)
    499994 → 500005 (11 miles)
    599995 → 600006 (11 miles)
    699996 → 700007 (11 miles)
    799997 → 800008 (11 miles)
    899998 → 900009 (11 miles)

    Not a flaw

    Do we consider numbers with unmatched leading zeros (like 001221) to be palindromes? I would say no. If we did this, it would open up a whole new field of answers such as 77 → 101 (24 miles.) The problem statement seems to imply that all six digits of the number, including leading zeros, need to participate in the palindrome. We can probably infer that Tommy's odometer is an analog odometer that actually shows leading zeros, rather than a digital odometer which hides them; this is consistent with the car being 19 years old.

    The flaw

    All well and good. Alas, there's a flaw in this beautiful analysis - namely, odometers wrap. It's entirely possible for the assumption that xyzzyx > abccba to break down if Tommy started his drive with a number close to 999999, and his odometer wrapped around to 000000 during the drive.

    In fact, this leads to the second reasonable answer:
    999999 → 000000 (1 mile.)

    There are other "wrapped" palindromes, but the next smallest are 998899 → 000000 (1101 miles) and 999999 → 001100 (1101 miles) which are even worse than case 1.

    Could a 19-year-old car have a million miles on it?

    That comes out to an average of 144.1 miles a day, which is high, but achievable (it's only 6 mph.) It's true that Tommy is unlikely to have put this many miles on a car if he lives only a mile from work, but remember that this is his new car (that is, it only recently came into his possession.)

  • Matthew van Eerde's web log

    Translating Ada Lovelace - mathematical science is an instrument

    • 1 Comments

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

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

    Her translation and notes, in full

    The program (very large image)

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

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

    Wow, I thought.

    That's a heckuva sentence.

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

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

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

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

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

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

    There are two ways to view mathematical science.

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

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

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

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

    Geeks are specially interested in all such techniques.

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

    Car Talk puzzler analysis - Limerick equation

    • 2 Comments

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

    In brief, we are given the equation...

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

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

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

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

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

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

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

    This equation also holds true.

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

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

    Perhaps fittingly, this last equation has only imaginary solutions.

  • Matthew van Eerde's web log

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

    • 6 Comments

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

    The attachment to this blog post contains:

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

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

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

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

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

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

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

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

    UPDATE June 1 2011: added --capture mode

  • Matthew van Eerde's web log

    Basic audio volume theory

    • 2 Comments

    Last time we talked about the different Windows Audio Session APIs for setting volume. Let's talk a little about what volume means.

    For purposes of illustration, let's take our signal to be a full-scale square wave:

    Incidentally, the answer to the exercise

    completely characterize the set of digital signals that achieve 0 dB FS intensity. If you have, say, 1000 samples of mono 16-bit integer audio to play with, how many distinct signals achieve 0 dB FS intensity?

    is "all signals with N/2 samples having value 1 and N/2 samples having value -1". There are

    such signals. For N = 1000 this is comes out to about 2.7e299.

     

    Linear in amplitude

    If we multiply the signal by a number between 0 and 1, we reduce the volume. That's the first natural volume scale - the "linear in amplitude" scale. 0 is silence, 1 is an unchanged signal, and 1/2 is a signal with all the amplitudes divided by 2.

    Linear in power

    Recalling the formula for the intensity of a signal, we realize that the power (intensity squared) of a signal depends on the square of the magnitude. This leads us to the second natural volume scale - the "linear in power" scale. 0 is still silence, and 1 is still an unchanged signal, but now 1/2 maps to a signal with half of the power of the original signal. In particular, a full-scale square wave with power 1 would drop to a square wave with power 1/2 - this has amplitude sqrt(1/2) which is significantly more than 1/2.

    Linear in dB

    So far so good. However, sound is perceived in a relative fashion. Humans are not very good at determining absolute volume, but are very good at determining relative volume. If I play a sound and ask you "what is the dB SPL of the sound I just played", you would have trouble answering; but if I played two sounds and asked "which sound was louder", you would be able to tell me. You would even be able to give me an estimate such as "it's about twice as loud".

    So a natural scale for volume is based on relative power - that is, a logarithmic scale dB = 10 * log10( PA / P0 ) where PA is the attenuated signal and P0 is the original. That looks something like this:

    Note that the relative power scale has no bottom! It's turtles all the way down.

    So how do you map an infinite volume scale onto a finite slider, while preserving the nice property that equal distances map to equal power ratios?

  • Matthew van Eerde's web log

    Generating formulae for polygonal numbers (triangular, square, pentagonal...)

    • 3 Comments

    The formula for square numbers is easy to remember:
    sn = n2
    There are also triangular numbers, pentagonal numbers, hexagonal numbers, and in general k-gonal numbers. These have formulae too... for example, triangular numbers:
    tn = n (n + 1) / 2
    ... but they can be hard to remember.

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

    Some formulae are not worth memorizing - it is more worthwhile to grasp the ideas behind the formulae and rederive them when you need them (or look them up.) This blog post shows how to derive the formulae for k-gonal numbers.

    Let's define a few terms. I've defined sn and tn above, and I'll define pn as the nth pentagonal number and hn as the nth hexagonal number, but let's generalize... let gk,n = g(k, n) be the nth k-gonal number. For example:
    g(3, n) = tn = n (n + 1) / 2
    g(4, n) = sn = n2
    The task, then, is to find a formula for g(k, n) for general k and n.

    Consider the nth k-gonal number. For simplicity of illustration, I will focus on a particular number, but the approach followed here should be followed while keeping the general case in mind. So, specifically, let's take a look at the 4th pentagonal number p4 = g(5, 4):

    The idea is to conceptually "cut" (n – 1) specific lines...

    ... and then "unroll" the structure to form a kind of "bar chart":

    The base of the chart is precisely one edge of the original diagram and so has length n = 4 (counting dots, not lines.) There are a couple of ways to find the height of the last bar - you can observe that each bar (not counting the bottom dot) adds (k – 2) = 3 over the bar before it (as marked below,) and there are (n – 1) = 3 nontrivial bars.
    Or you can note that each bar is the bottom dot, plus the sum of (k – 2) = 3 edges, each of which has (n – 1) = 3 unique dots, if we don't count the first dot as being part of the edge.
    Either way, you come up with the height being:
    h = 1 + (k – 2)(n – 1) = 1 + 3(3) = 10

    So we know the base and the height of the triangle. How do we find the area? Well, if we double the triangle, we get a rectangle:

    The base of the rectangle is n = 4, and the height is:
    h + 1 = 2 + (k – 2) (n – 1) = (k – 2) n + (4 – k) = (5 – 2) 4 + (4 – 5) = 11
    The area of the triangle is half of the area of the rectangle:
    g(k, n) = n ((k – 2) n + (4 – k)) / 2 = 4 ((5 – 2) 4 + (4 – 5)) / 2 = 22
    Counting dots confirms that there are, in fact, 22.

    So we have a general theorem - let's try it out on a few special cases. We can use the ones we know as a check.

    g(3, n) = n ((3 – 2) n + (4 – 3)) / 2 = n (n + 1) / 2 = tn
    g(4, n) = n ((4 – 2) n + (4 – 4)) / 2 = n2 = sn
    g(5, n) = n ((5 – 2) n + (4 – 5)) / 2 = n (3n – 1) / 2 = pn
    g(6, n) = n ((6 – 2) n + (4 – 6)) / 2 = n (2n – 1) = hn
    g(7, n) = n ((7 – 2) n + (4 – 7)) / 2 = n (5n – 3) / 2
    ...
    g(k, n) = n ((k – 2) n + (4 – k)) / 2
    ...

    Further checks: g(k, 1) should be 1 for all k, and g(k, 2) should be k for all k. These also check out.

  • Matthew van Eerde's web log

    Windows audio render volume settings, from local to global

    • 2 Comments

    Windows has four different volume settings for any given piece of audio; they are all applied simultaneously. For example, if one of these settings is muted, you will not hear the audio, even if the others are turned up to "full". Here they are, from local to global:

    Stream volume

    The WASAPI API for this is IAudioStreamVolume.

    This most local volume control is used only in rather specialized situations. If a particular app wants to play multiple audio streams to the same audio device, and in the same session (see below), but wants to control the volumes for the streams independently, this is the volume control to use. For example:

    • A media playback app might want to cross fade songs n and n + 1 in a playlist; it could do this by playing the songs in different streams, gradually bringing the volume for song n down to zero, while simultaneously bringing the volume for song n + 1 up to full.
    • A game might offer distinct volume controls for:
      • The background music in the game
      • The sound effects (gunshots, explosions, and so on)
      • Voice chat in the game
      The first two would likely be in the same session and would thus use stream volume controls (exposed via the game UI - Windows does not offer UI for stream volume.) The third could very well be a different session, and even use a different device - the background music and sound effects might go to the default console device (e.g., speakers) while the voice chat might go to the default communications device (e.g., a headset.)

    Apps could also get these same effects by doing their own mixing and volume handling, and playing a single stream to the Windows audio engine, of course.

    Session volume

    By and large, this is the volume control that apps most commonly use. The WASAPI APIs for this are ISimpleAudioVolume and the more-rarely used IChannelAudioVolume.

    This is what people mean when they say "application volume" - a session is, by and large, an app (though there are exceptions in both directions.) This is the "S" in "WASAPI" - Windows Audio Session API.

    The session volume shows up in the Volume Mixer - each "Application" slider is a session volume control. This is also usually (but not always) tied to a volume control in the app.

    Endpoint volume

    The WASAPI API for this is IAudioEndpointVolume. Apps should generally prefer ISimpleAudioVolume to this because IAudioEndpointVolume will affect all apps on the system.

    This is what people mean when they say "system volume." It is exposed in the following three places in the UI. Note if you have multiple audio devices, each has its own setting.

    Ducking

    More about ducking

    When you get a phone call or start a voice chat, Windows (starting in Windows 7) will attenuate (or "duck") all of the audio on the system (except for phone calls and voice chats.) This is the "most global" because it affects all streams on all audio devices. Note that this volume control only has three settings (four if you count "mute".)

     

    Larry Osterman did a series of audio volume posts - one of them contains much of the information in this post, though the ducking feature was added after his post.

  • Matthew van Eerde's web log

    Spot the bug - control flow macro

    • 4 Comments

    Found this in a code review.

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

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

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

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

  • Matthew van Eerde's web log

    How much is a perfect Jeopardy! score?

    • 3 Comments

    Some sports and games have a notion of a "perfect score."  For example, a perfect score in bowling is 300 (twelve consecutive strikes;) a perfect score in golf would be 18 (18 holes-in-one.)

    After watching Watson destroy Ken Jennings and Brad Rutter I began to wonder what a perfect score in Jeopardy! would be.

    Assumptions:

    • The player knows (or can guess) all the correct questions for the given answers.
    • The player can consistently beat the other players to the buzzer.
    • The player knows (or can guess) where all three Daily Doubles are.
    • The Daily Double in the Jeopardy! round happens to be on a $200 clue.
    • The two Daily Doubles in the Double Jeopardy! round both happen to be on $400 clues.
    • The player is willing to "bet it all" at all four opportunities (the three Daily Doubles and Final Jeopardy!).

    Jeopardy! has three rounds:

    1. The Jeopardy! round
      The player starts this round with $0.
      There are six categories with five clues each: $200, 400, 600, 800, 1000.  That's $3000 for each category for a nominal total of $18,000.
      But wait - one of those clues is a Daily Double!  For maximum value we're assuming it's a $200 clue.
      The player gets all the normal clues correct and amasses $17,800.
      The player then "makes it a true daily double", gets it right, and doubles their total to $35,600.
    2. The Double Jeopardy! round
      The player starts this round with $35,600.  Again, there are six categories with five clues each.  But this time the values are $400, 800, 1200, 1600, and $2000.
      That's $6000 for each category for a nominal total of $36,000.
      But wait - two of those clues are Daily Doubles!  For maximum value we're assuming they're both $400 clues.
      The player gets all the normal clues correct and amasses an additional $35,200.  They now have $70,800.
      The player then selects the first of the two Daily Doubles for this round, "makes it a true daily double", gets it right, and doubles their total to $141,600.  (Jeopardy!, unlike some game shows, does not materialize winnings at the end of a round; there's just a single running total for each player throughout the episode.)
      The player then selects the last Daily Double, "makes it a true daily double", gets it right, and doubles their total to $283,200.
    3. Final Jeopardy!
      The player starts this round with $283,200.
      The player looks at the category, bets everything, looks at the answer, writes down the correct question, and doubles their total to $566,400.

    Conclusion: A perfect score for a single episode of Jeopardy! is $566,400.

    The actual record appears to be Roger Craig's win of $77,000 on September 14 2010.

  • 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

    How to write a leading apostrophe in Word

    • 4 Comments

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

    Word has a smart quotes feature where it will automagically transform

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

    into

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

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

    Right?

    Usually.

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

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

        -- Jabberwocky

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

    Check it out: a 57 Chevy!

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

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

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

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

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

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

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

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

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

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

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

  • Matthew van Eerde's web log

    Phase and stereo-to-mono downmix

    • 3 Comments

    Warning: trigonometry ahead.

    Last time we looked at how to downmix a stereo signal to mono (M = L/2 + R/2).  The resulting M signal can only be as powerful as the L and R signals if they are perfectly correlated (L = R); if the L and R signals are uncorrelated (no relationship between L and R), the resulting M signal is only half as powerful; and if the L and R signals are anticorrelated (L = -R), the resulting M signal vanishes.

    This time I want to look more closely at what happens to M as {L, R} varies from correlated via uncorrelated to anticorrelated.  In particular I'll take L and R to be full-scale sine waves of equal frequency and offset by a constant phase: {L, R} = {sin θ, sin(θ - φ)}.  We'll see what happens to IM / IL as a function of φ.

    Recall from before that the intensity of a signal s(t) over a time interval (a, b) is defined as

    where the dc value is

    Our downmixed signal is defined as

    So our dc is zero (note that I am taking (a, b) to be a single period:)

    A refresher on the angle-sum identities, which I will use without proof:

    • sin(A + B) = sin A cos B + cos A sin B
      • Corollary: sin(A - B) = sin A cos B - cos A sin B
    • cos(A + B) = cos A cos B - sin A sin B
      • Corollary: cos(A - B) = cos A cos B + sin A sin B

    We know from before that IL = IR = sqrt(1/2) = 0.707.... Let's calculate IM as a function of φ:

    Nice and simple.

    Are we done? No.

    When you see a fact, try to see it as intuitively as possible -- Pólya

    OK, let's graph this.

     

    That looks suspiciously like a cosine in absolute value.  In particular, after messing around with a graphing calculator, it looks like:

     

    Is this in fact a trigonometric identity?  Let's see...

    Much nicer and simpler.  So why didn't our original derivation land us here?

    Let's go back to our original graph and shift our frame of reference forward by φ/2.   Instead of {L, R} = {sin θ, sin(θ - φ)} we get {L, R} = {sin(θ + φ/2), sin(θ - φ/2)}.  Solving for IM now gives:

    This result is much easier to digest.  The sqrt(1/2) is the intensity of the pre-mixed signal; the loss of intensity due to the downmixing, then, is |cos(φ/2)|.

  • Matthew van Eerde's web log

    Perl script to parse MPEG audio header

    • 1 Comments

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

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

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

    Here's the source for the perl script:

    use strict;

    # based on http://www.mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm
    # assumes that the file you point it at starts with an MPEG audio header

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    my $header = "";

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

    close(MPEG);

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

    my @bits = ();

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

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

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

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

  • Matthew van Eerde's web log

    optimal tic-tac-toe (XKCD)

    • 1 Comments

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

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

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

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

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

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

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

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

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

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

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

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

  • Matthew van Eerde's web log

    Downmixing stereo to mono

    • 1 Comments

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

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

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

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

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

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

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

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

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

Page 3 of 6 (138 items) 12345»