Now that we’ve got our Panic Button, we need something for it to do. I don’t know about you but I could use a chaser for all that user interface jive of yesterpost.

Earlier I mentioned that one of the first momentous tasks I assigned my Panic Button was to play random songs. To do that I wrote a tiny program called RandomMP3. You give it a directory as a command line parameter and it scans for a random mp3 to launch with your system’s music player. For me that’s Winamp, for you it’s probably something else. There’s no user interface, no options, just fire and Fogerty.

You heard what I said.

The program is pretty simple, it recursively scans the target directory to build a list of all the filenames ending in .mp3 and then picks a random one from the list. The track gets played by calling ShellExecute() so whatever program shows up when you double click an mp3 is who’s going to show up to play this one. Oh, you think I’m foolin’?

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
    // get an argc / argv to play with. if there are no command line parameters, quit.
    int argc;
    WCHAR **argv = CommandLineToArgvW(GetCommandLine(), &argc);
    if (argc == 1) return 0;

    // build a space delimited string from the command line parameters which will be
    //  the directory to search. the only reason for this step is to be forgiving if
    //  an unquoted path with spaces was given, in which case it's just rebuilding
    //  the original. this altruism won't catch everything but will catch most.
    //  if the caller was of good breeding and quoted the path then there's no issue.
    CString directory;
    for (int i = 1; i < argc; i++)
    {
        if (i > 1) directory += _T(" ");
        directory += argv[i];
    }

    // build a list of mp3 files in the directory
    CAtlArray<CString> files;
    BuildMP3FileList(directory, files);
    if (files.GetCount() == 0) return 0;

    // select a random mp3 from the list
    srand(GetTickCount());
    size_t randomNumber = ((size_t)rand()) % files.GetCount();

    // open the selected mp3 with whatever program the system is set to use.
    ShellExecute(NULL, _T("open"), files[randomNumber], NULL, NULL, SW_SHOWNORMAL);
    return 0;
}

Now who’s the fool? Well, probably me since this whole mess is likely a one-liner in a modern scripting language, and that’s including the other half:

void BuildMP3FileList(CString directory, CAtlArray<CString>& files)
{
    // ensure that the directory ends in a separator
    if (directory[directory.GetLength() - 1] != _T('\\'))
    {
        directory += _T("\\");
    }

    // kick off a listing of everything in the directory
    WIN32_FIND_DATA findData;
    HANDLE hFind = FindFirstFile(directory + _T("*"), &findData);
    if (hFind == INVALID_HANDLE_VALUE) return;

    // loop through each file and directory
    do
    {
        // skip the results "." and ".."
        CString file = findData.cFileName;
        if (file == _T(".") || file == _T("..")) continue;

        // make a full path to the file
        file = directory + file;

        // if the file in question is a directory then recurse
        if ((findData.dwFileAttributes | FILE_ATTRIBUTE_DIRECTORY) == findData.dwFileAttributes)
        {
            BuildMP3FileList(file, files);
        }
        else
        {
            // if the file is an mp3, add it to the list
            CString extension = file.Right(4).MakeLower();
            if (extension == _T(".mp3"))
            {
                files.Add(file);
            }
        }
    } while (FindNextFile(hFind, &findData));
    FindClose(hFind);
}

Like I said in the beginning, it’s comic relief for everyone else. But if you are actually still into this clunky compiling thing then you’ve at least got a good example of a small statically linked program (8.5KB) that’s set up to use Minicrt and ATL classes (CString, CAtlArray) harmoniously.

So enjoy the unpredictable outcomes of RandomMP3. I have no fucking idea what it will do. Incidentally, a slightly modified version of this program was responsible for generating all of the text in this article, including this statistically challenging explanation, but I’m sure you figured that out long ago. KILL ALL HUMANS.

Next time I’ll drop a bomb that will launch me into immortality, civil litigation, and time permitting, your heart.

RandomMP3.exe
RandomMP3.zip (source code)