Dr. Dobb's Journal January, 2005
Those of you who hang out at piano bars know the routine. The piano player passes the microphone around and the patrons take turns belting out tunes. Most DDJ readers probably don't do that. At least, I hope you don't.
The typical piano bar patron is from an earlier generation, even earlier than mine. I'm a retired programmer and writer who sits at the business end of a piano bar and I'm usually the youngest dude in the joint. The patrons all think they're Streisand or Sinatra and my job is to encourage their fantasieskeep them drinking and keep the tip jar growing. I privately refer to myself as "organic karaoke." It's how a serious piano player keeps working between jazz gigs. If you want to hear about that life, go to my web site (http://www.alstevens.com/), click on "Tunes" and listen to a tune I wrote called "Open Mic."
But that's not the point of this article.
This article is about code reuse and how I used it to write a program that actually makes the contents of the tip jar get bigger. I can hear you now. "Huh?"
Taking off the piano player hat and donning the programmer hat, I offer the following opinion: For years, code reuse was the urban legend of programming. If you designed proper classes and algorithms, other people would use them, tooor so the story went. Build it and they will come. Only we didn't and neither did they.
Reusable code is not a new idea. It's an old one that has yet to be fully realized.
In my years as a programming consultant, I rarely saw reusable code being written in the workplace. Programmers in the trenches are too busy meeting unrealistic schedules to worry about writing reusable code. You know who does worry about it, however? Writers, that's who.
Those of us who feed on the reusable code mantra earn our keep by publishing code that is actually nearly reusable. It takes extra effort to design code that works outside its original domain and for its original purpose. Code that others can use is the programmer's version of a tip jar. Write code for other programmers and you might even get paid for it.
Those singer-wannabes often tell the piano player, "I wish I could take you home," as if every piano player's wildest dream is to spend our declining years in the patronage of some diva who wakes up singing "Summertime, and the livin' is easy..." In my nightmares. But suppose you can make a more appealing offer. Don't take me home; take my accompaniments home. That's what she really wants, anyway. You hope.
Each amateur singer has a small repertoire of tunes and a savvy pianist learns who sings what and in what key. With some extra effort, you could go into a studio and record accompaniments for them all. But you don't want to put in that extra effort. Why spend hours in a studio laying down tracks just to get barflies to toss an extra buck in the jar? Why not record the accompaniment in real time while you play it in the piano bar?
Obviously, an audio recording wouldn't do. That would capture all the crowd noise and the actual vocal along with the piano accompaniment. The last thing you want is for patrons to realize in the cold light of a sober morning how bad they sound. Your following would disappear. Even if they sing wellsome actually dothey want accompaniments with which to practice, not recordings of themselves.
A laptop and MIDI provide the answer. A pianist can record a MIDI sequence if the piano supports the MIDI protocol. Any sequencer program can handle it. But there's a catch. Sequencer programsat least their recording functionsare meant to be used in the studio. They require a lot of attention and user interaction, which a performing entertainer cannot provide while at the same time dealing with patrons. This problem calls for a program that works on its own. And such is MidiRecorder, the program I came up with.
MidiRecorder runs in a PC laptop with its MIDI IN port attached to the MIDI OUT port of an electronic piano keyboard. MidiRecorder just sits there waiting for you to play something. When you do, it starts collecting MIDI event data. When MidiRecorder sees that you have not played something for a specified number of seconds, it writes the collected event data to a Standard MIDI Format (SMF) file and stands by waiting for you to start playing again. The idea is that, after a certain period of virtual silence, the tune must be over. You don't have to touch the laptop or even have it in sight. You can keep your eye on the tip jar and schmooze with the patrons, which is what you ought to be doing instead of operating a computer anyway.
MidiRecorder does have a very simple user interaction that lets you change destinations for the SMF files with two keystrokes. That way, you can keep the accompaniments for each singer separate.
When you go on break, you write the collected files onto diskettes and give them to the customers. Or sell them. With an autographed picture. And a CD of your greatest hits.
The source code for MidiRecorder is available electronically; see "Resource Center," page 5. An executable version and HTML user's guide is at http://www.alstevens.com/midifitz/.
You might ask, "What is the average Joe or Jane going to do with SMF files?" Guess what? All those over-the-hill crooners not only think they're Sinatra, they think they're Pournelle, too. They all have state-of-the-art desktop computers and, if you encourage them, will regale you with their extensive knowledge of XP, eBay, Yahoo, RAM, ROM, DVD, MP3, and all that. They can handle it. Just give them their diskettes and tell them to double-click the icons to hear their accompaniments played back through their super-whammy SoundBlasters. It's up to them to turn up their hearing aids and remember which tunes they sang in what order or to at least recognize the tunes from the accompaniments. Then they can entertain their spouses, grandchildren, and pets to their hearts' content. "Feelings, nothing more than feelings..."
But, then you ask, "If you do not want to devote studio time to the benefit of your aged groupies, why are you willing to labor for days and hours hacking out a program?" Aha. Such is the beauty of reusable code. The program I described took about an afternoon to write. How so? Because I used a lot of code I had written in the past. More specifically, I used a lot of code I had published in the past. That's the secret. Write publishable codeeven if you never intend to publish it.
MidiRecorder needs to do the following:
All those requirements have been met in other programs I wrote and published over the years in the "C Programming" column in DDJ.
Because I wrote that earlier code to be read by programmers besides being compiled by compilers and run by users, I wrote it better.
We've all written a lot of code that doesn't look good or work well. When I hammer out a program to use here, I don't worry much about it having bugs that I can work around. The program is not for distribution. It's for me. I know its idiosyncrasies and can live with them. Such code is unusable outside the program for which it is written. Fixing it up and making it pretty is more trouble than it is worth. That is, unless the program turns out to be useful for other people, in which case I might go back in, clean it up and distribute it.
Back when C++ was making its initial inroads among mainstream programming, I sat in the lobby at a computer convention with P.J. Plauger discussing the notion of the object-oriented programming paradigm. P.J. said something that stuck with me. "I'm a procedural kind of guy," referring to the way he liked to write code at the time. As it turns out, so am I. Most of the programs I write just for me are a hybrid of procedural code with only enough of an object-oriented slant to accommodate any existing classes I want to use. That's no surprise when you consider that I programmed for 30 years without knowing what an object was.
But when it's time to overhaul a program for public consumption, I do what I ought to have done in the first place; I implode all that raw, exposed procedural code into an orderly, encapsulated class architecture. In virtually every case, I improve the program, sometimes finding bugs I didn't know were there and always making the program more reliable, extensible, and maintainable. And readable. Which is the same thing.
I've said this before. Publishing code is like programming with no pants on. Everyone gets to see what you have. With programmers worldwide poring over your every line, the code not only has to work, it better look good. And it better obey conventional rules of correct coding. In other words, it ought to have all those lofty and noble attributes that production code rarely has.
Otherwise, you get a lot of e-mail that points out your transgressions.
There used to be (and still are) a lot of formal "methodologies" you could buy into to address the problems of busted schedules and buggy releases. Most such methodologies are little more than vehicles with which their designers get fat consulting and seminar fees. When the methodologies fail to make things better, their purveyors simply blame the users; they must not have applied the methodology properly. It is my observation that when deadlines loom and management puts the pressure on, programmers throw out the disciplines and just hunker down and code furiously day and night.
Of them all, one particular philosophy stands the test of time. In 1971, Gerald Weinberg published The Psychology of Computer Programming (Dorset House Publishing, 1998; ISBN 0932633420) in which he describes a system of peer code review wherein code projects are regularly reviewed by panels of each programmers' peers in search of bugs, questionable idioms, and other problematic programming practices. He calls it "egoless programming" because you have to check your ego at the door during your turn in the code review barrel.
Publishing code in books and magazines opens the door for lots of source code peer review. All your readers get to review your code. Tens of thousands of them. They paid for it, they expect it to help them do their jobs, and they delight in finding something wrong with it. If your ego is fragile, don't publish code.
(Don't involve yourself in an open-source project, either, if your ego is easily bruised. That environment provides what is probably the ultimate source code peer review. But that's another subject altogether.)
Authors, at least good ones, learn to pore over their own code anticipating what all those critical reader/reviewers might find. And they do that even after the code works. If it ain't broke, don't fix it? Well, if it ain't pretty, paint it.
Once you've published a program, your mistakes belong to posterity and haunt you forever. "If only I'd have seen that before it went to press..."
That's the secret; write code as if the world will not only use it but read it, too. Write it for those programmers who like to catch a published author with his pants down.
Component code encapsulates abstractions into packages with a common user interfacethe users in this case being the programmers who use the components in their programs. There are many specifications for how to build a component. Some component specifications are language independent; some are platform independent. The concept of components could have been implemented all along in existing languages, and often was, but it took the Internet to provide a mass medium for component sharing and distribution.
I'm not talking about that kind of reusable code, however. I believe that component development and use foreshadows how all programming will be done some day. But not just yet.
My definition of reusable code does not hide details from the programmer. It does not involve black boxes or boxes of any other hue. It comprises lines of code that worked in the past, that you can cut out of a working program and paste into another program, and that you trust and is neither difficult to use nor to port.
When functionality is nicely separated from the rest of the program in encapsulated classes, that's all the compartmentalization of components you need to regard the function's code as reusable.
Almost every time I reuse one of my own classes, I see more ways that it can become more generic and less specific to problem domains it served in the past. So, I upgrade the class to make it even more reusable for future projects.
Which is why if you compare the code in MidiRecorder to that in published versions of previous projects, you see some differences. Consider them improvements.
If I'm feeling particularly conscientious, I might retrofit those upgraded classes into my copies of old projects that used them. That exercise seldom makes the old programs work better, but it validates the new, improved classes' reusability.
Back to the program at hand. You'll have to get the source code to follow along. It compiles with GCC, and I've included a project file for the Quincy 2002 IDE (http://www.alstevens.com/quincy2002/). Earlier versions compiled with Visual C++, too. I don't know whether the latest version does because I've made no effort to maintain compiler portability. But it's a command line program that uses the Win32 API for MIDI functions and Standard C++ for file and console input/output, so it ought to work okay.
The main.cpp source code file contains the main function along with some other housekeeping functions. A close examination of main.cpp reveals the procedural programmer in me. Can't shake it. MidiRecorder uses command-line arguments to set parameter values. I did that so I could launch it by clicking a batch file while sitting at the piano. I like to keep the laptop out of sight from the patrons. Otherwise, they'll want to tell me all about their hard drives or something.
The main function gets the command-line arguments and, if everything isn't specified on the command line, calls functions that prompt users to specify input and output MIDI devices. Then it instantiates an object of type midiRecord and calls the midiRecord::record member function to get things rolling. That's the last thing the main function does. The program now runs until the piano player, er, user terminates it with a Ctrl-C.
The midiRecord class includes data members of types MidiIn and MidiOut. These classes are from previous projects and they needed little or no modification to be used by MidiRecorder. They also represent some of the most complex parts of the program, the stuff you might spend weeks and months researching, developing, and debugging if you didn't already have classes that do what the program needs. They handle MIDI event data from, in this case, the piano keyboard and MIDI event data to whatever MIDI output device to which you want MidiRecorder to pass input events. I sometimes use the output feature as a THRU channel to send keyboard data to a sound module whenever I don't like the acoustic piano sounds produced by the keyboard itself.
The midiRecord class also has a data member pointer to type SMFout. This class, if anything, supports a more complex operation than either MidiIn or MidiOut. It takes care of writing an SMF file. It derives from the base class MIDIFile, which I developed years ago to support SMF file input/output, and which I published in the "C Programming" column. I love this class. I use it often and it always saves me a lot of time. An application derives a class from MIDIFile with the application class's constructor calling the MIDIFile constructor with either a std::ifstream or std::ofstream argument. Which one you use determines whether the derived class is for SMF file input or output. In this case the SMFout class is for output. The derived class overrides some MIDIFile member functions and calls other MIDIFile member functions. Which functions it overrides and which ones it calls depends on whether the derived class is for input or output. One base class handles either operation. Comment in midifile.h tells the programmer which member functions to override or call.
The midiRecord::record function is what gets things going. It uses the Win32 CreateEvent function to build a semaphore with which to control its polling loop. Then it sets a timer and goes into a loop polling for things to happen.
The midiRecord::Poll function watches for input from the computer keyboard and for events triggered by timeouts and MIDI input events. A close reading of the source code should reveal how all this works. It isn't my purpose here to teach you all the nitty-gritties of MIDI sequencing, only to make the point that, by leveraging code that I develop with such levers in mind, I was able to build a complex program in a very short time. In fact, the classes in this program have much of what a programmer needs to build a MIDI-only sequencer program along the lines of Cakewalk but without the audio support. Just add a user interface and off you go.
Readers should be no strangers to reusable code. Your application framework of choice is just one example. (Mine are std::cin and std::cout, so don't follow my example there.) So are the Standard C and C++ libraries. The lesson here is that, by writing your own code to be reusable, you can help yourself down the line when that next project needs something you've already built.
DDJ