Matthew van Eerde's web log

  • 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

    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.

  • 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

    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.

Page 1 of 1 (5 items)

December, 2010