Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

C Programming


OCT95: C PROGRAMMING

C PROGRAMMING

MidiFitz and Windows 95

Al Stevens

This is the October column, which I am writing in July. Next month Windows 95 will be officially released. I can say that with certainty; I now have a copy of the to-be released CD-ROM. It is installed and works okay. Bill Gates and Shaq O'Neal launched it on TV.

For years I was devoted to command-line user interfaces. I begrudgingly used Windows because of its superior applications for writing--word processors and e-mail, mainly. But to manage the computing environment--copy files, delete them, make subdirectories, format diskettes, make backups, keep track of everything--I used MS-DOS, sometimes by itself and other times from a Windows DOS box. MS-DOS is easier and more intuitive than the File Manager. I worked that way with Windows 3.0, 3.1, Windows for Workgroups 3.11, and Windows NT 3.5. Other PC users concur. They mostly use Windows to load and run graphical applications and MS-DOS to maintain their systems.

I've been a Windows 95 beta tester since it was called "Chicago." My forays into MS-DOS became less frequent as I learned more about Windows 95. There is little, if anything, that works better in MS-DOS. The only exceptions are applications that do not run under Windows, and I don't use many of them anymore. Most DOS game programs, which once required a dedicated machine--not a DOS box--run nicely in a Windows 95 DOS box.

In a previous column, I mentioned the artistic quality of Windows 95 beta CD-ROMs. The build-490 disc is the nicest one so far. It is a pleasant forest green, but its distinguishing characteristic is not its color, but its distinctive wintergreen aroma. Something like unused kitty litter. This was no fluke, no chance contamination of one particular disk by a fir falling next to a Redmond product packer's open window. Nosiree. I have two copies of build-490 from two sources, and both CD-ROMs have that same alluring scent. As I opened the jewel case, a sense of calm fell over me. I felt relaxed and had visions of a bucolic scene--pastures, trees, wildflowers, fluffy white clouds, a cooling breeze, deer grazing, and birds singing. Sort of the way that Edward G. Robinson voluntarily bites the big one in Soylent Green. That should have warned me. One should not trust scratch 'n' sniff software.

Throughout its documentation and Help displays, Microsoft refers to Windows 95 as, simply, Windows. Could Microsoft be trying to distance itself from what, in hindsight, seems to be a shot in the foot--a major product with a name that sports an unplanned early expiration date? Built-in obscurity like Brazil '66. Like a credit card or a carton of milk. How will you feel in 1996 running an old operating system? Better sniff it first. It might be spoiled.

MidiFitz

Last month I launched a new project, a Windows program that uses MIDI to emulate a jazz piano player's rhythm section in real time. The code that I discussed was an early version that simply read the keyboard's MIDI messages and displayed them in a list box. The full program, which is now complete, examines the notes being played, deduces a musical chord, and plays a bass line through the MIDI system.

The program's name is "MidiFitz," for reasons that will become obvious. If anyone has this name trademarked, don't go running to your lawyer. Just call, and we'll cave in...er, accommodate like we always do.

I learned several things during this program's development. I learned more about Visual C++ and its integration with the Microsoft Foundation Classes. I had to dig into the multimedia API to figure out the Windows MIDI system. Then there was the musical part--getting the program to play the bass violin correctly.

Visual C++ 2.0 and MFC

Visual C++ 2.0 is not the latest version, but it's the one I have, and it was sufficient to develop this program. Figure 1 shows the program's application window, which I designed completely from within the visual-programming environment.

There are several anomalies with Visual C++ 2.0 and Windows 95. First, the size and configuration of windows differ significantly between the visual development environment and the run-time environment. This makes design difficult, because the final product does not look exactly like what you designed. Under NT, the windows are the same, suggesting that later versions of Visual C++ will no doubt catch up to the alien Windows 95 operating environment. Second, MFC 3.0 supports a small subset of the controls that Windows applications use. For example, it does not support spin buttons, which I needed for setting the tempo of the song in the application. I had to contrive a spin button by associating a scroll bar with an edit box. Third, I could find no way to add a menu to a dialog-based application. Microsoft's documentation is huge--maybe even comprehensive--but try to find something when you don't know how or where Microsoft filed it. Fourth, the DDV/DDX associations fall way short of the mark. You can associate a control reference or a variable to a control on a dialog box, but not both. The implementation of DDV for radio buttons is cumbersome, at best. You can associate a BOOL variable with only one of the buttons in a group, no matter how many there are. What good is that? It's easier to forget DDV and do it yourself.

These criticisms are the musings of a novitiate in the hallowed halls of Windows programming. Those of you in the know could laugh at my floundering, and well you might. I'd rather you jumped on Microsoft and forced it to provide better documentation. These impediments are typical of what the rest of us can expect when we take the plunge.

Encapsulating the MIDI API

The MIDI API is a set of C functions that a Windows program can call. MFC ignores this API, so a C++ programmer has to encapsulate it. Eventually, I'll have the entire multimedia API nicely wrapped up. I'm sure others will do likewise. For now I took the pragmatic approach and designed two classes that encapsulate MIDI input and output specifically to support this program. Listings One through Four are midiin.h, midiin.cpp, midiout.h, and midiout.cpp, respectively.

Reading the MIDI Device

To figure out what chord the piano player is playing, the program reads the MIDI input device, intercepts and interprets the notes, and passes them through to the MIDI output device.

MIDI input is simple. A program calls the Windows midiInOpen API function specifying the input device number, the address of a handle that identifies the open device, and the handle of a window to which MIDI input messages are to be sent. The device number is 0 in this program. I'm assuming only one input device, the MIDI keyboard. The MidiIn class declared in midiin.h (Listing One) and midiin.cpp (Listing Two) encapsulates this interface. A program declares an instance of the MidiIn class passing a window handle as the only parameter to the MidiIn constructor. When a MIDI input event occurs, the designated window receives an MM_MIM_DATA message, which is a packet containing an event code and, when appropriate, a note code and a velocity code.

MIDI Input Events

There are many different MIDI event messages; MidiFitz is concerned with only a few. The source-code file midifdlg.cpp, which is not published this month but is available electronically (see "Availability, page 3), receives and processes those event messages.

The controller change event signals that either the sustaining or dampening pedal was pressed or released. This event has nothing to do with chords, but the program passes it through to the MIDI output device. The Sound Blaster's MIDI system ignores the event. The Turtle Beach sound card recognizes and processes it appropriately.

The note on event arrives twice--once when you press a key and once when you release it. (The keyboard in this context is the MIDI 88-note keyboard, not the typewriter keyboard on your PC.) There is a note off event, but my keyboard does not send it. The note on event includes a note code to identify the note to play and a velocity parameter that specifies how hard the player hit the key. When the player releases the key, the keyboard sends a note on event with a velocity of 0. Other keyboards, particularly organ keyboards with no dynamics and, therefore, no meaningful velocity change, might not work the same way.

The input device can send three timing event messages: start-timing clock, timing clock, and stop-timing clock. The first is sent when the system starts playing a song, the second is sent at fixed intervals during the song, and the last event is sent when the song is finished. Timing-clock events are sent at a clock speed of 24 times per quarter note. A quarter note is typically the time duration of one beat in a song. Not always, but often enough to serve as the reference interval for the MIDI system. The faster the tempo, the higher the rate of timing-clock event messages. One device in the system sends the timing-clock event messages, and the other devices synchronize with them. The Timing radio buttons on the MidiFitz application window tell the program whether to generate and send the timing clock events or expect them from an external source.

Playing by Ear

Jimmy Durante claimed to have found the lost chord by sitting on the piano keys. "Dat's funny," he said, "I usually plays by ear."

A piano player doesn't think about how the bass player knows what note to play. Unless there is a chart, the bass player usually knows the song and plays a bass line that fits the song's chord progression as he hears it in his head. He "plays by ear." (I'm using male gender here because it's awkward to always say "his or her" and "he or she," and I don't believe political correctness should compromise readability. Besides, in many years of professional playing I've met only one female bass fiddle player.)

What about when I play something that the other guy never heard before? A musician deduces some things through experience. Certain patterns of chord sequences resolve in predictable ways. Not always, however, and I wondered how the good players deal with that. Piano players have some leeway when accompanying singers or horn players. We can wait until we hear the harmonic properties of the song and plug a chord in behind the beat. Comping, they call it, and if we do it right, it sounds like we know the tune. But bass players have to be right on the beat with the right note. How do they handle a tune they've never heard?

I consulted an expert. For several years I played in a duo with a truly great bass player/vocalist named John Fitzgerald. Fitz stands on my left and watches my left hand. If Fitz doesn't know the song, he reads my hand on the keyboard. After a few gigs with any piano player, Fitz can read that left hand unerringly. After playing one chorus of any tune, Fitz knows it for life.

Fitz provided the inspiration and the objectives (and the name) for this program. Like him, the program watches the chords during the first chorus and plays along. For subsequent choruses, the program plays a bass line based on the chords it learned during the first chorus, which means that the piano player can be less accurate in the voicings and timing of chords after the first chorus.

Parsing a Chord

Every time the piano player presses a note, the program puts a True value into an array of 88 bools. The value goes into the array element corresponding to the note on the keyboard. When the player releases the note, the program writes a False value to its element in the array. At any time during the running of the program, that array represents the notes currently being pressed on the MIDI keyboard. If you sit on the keyboard, you too might find the lost chord in the array.

For each keypress, the program tries to determine if a known chord (known to the program, that is) is being played. It takes at least two notes to represent a chord. The more notes, the more information the program can deduce about the chord.

There are 88 keys on the keyboard but only 12 basic tones. The groups of 12 are repeated at progressively higher pitches as you go up the keyboard. Each octave produces 12 tones that are each twice the frequency of the corresponding tones in the next-lower octave. The "A" in the center of the keyboard produces a tone at 440 Hz. The one below it is 220. The one above it is 880. The frequency of each next-adjacent tone can therefore be computed by multiplying the current frequency by 1.059463094359, which, when repeated 12 times, doubles the original frequency. And that's all I have to say about that.

Depending on the reach of your two hands (or your hindquarters, as the case may be), nearly any combination from 1 to 10 of the 88 notes is possible. Given the atonal nature of some contemporary music forms, any combination is acceptable, depending on what you like. From this chaos, our minds form order as we listen to the songs we like. From this same chaos, MidiFitz has to interpret the notes into a chord. Here is what it does.

MidiFitz does not apply any knowledge beyond what you are currently playing to interpret the chord. Unlike its namesake, MidiFitz does not know, for example, that a form of the C chord can be expected following a progression of DMINOR, G7TH. Although a bass player is almost always right to make that assumption, the times that it does not work often result in dissonance at best and angry singers at worst. Therefore, MidiFitz depends on the piano player to specify the chords.

The program collects the four lowest distinct notes being played and places them into an array. Notes repeated in higher groups of 12 are eliminated, so that only one instance of each tone is in the sample. The four notes might be close together (like Shearing) or spread out (like Brubeck) depending on how you voice the chord. MidiFitz collapses the four notes, which might be spread all across the keyboard, into the same grouping of 12. These close groupings of two-to-four notes now constitute a likely representation of the chord being played, assuming the piano player knows the right chord.

The two-to-four note sample is run past an array of chord voicings. Here I must confess: Those voicings reflect the way that I improvise. I've tried to include most conventional voicings as well, but there are many ways to play any chord, and I might have missed your favorite voicings. Every improvising piano player's sound is distinctive. Within four measures, I can identify the player if he or she is among my favorites. The sound is a product of the shape, weight, and dimensions of the player's hands, the player's voicing of chords, and the player's understanding of chord substitutions.

The array of voicings contains elements of the midiChord structure shown in Example 1. Each voicing in the array consists of from two to four notes. The first note, which represents the lowest note being played, is always represented by the number 1. The other notes are half-note offsets from the first. The root data member is the half-note offset from the lowest note that represents the root note of the chord. This method allows the piano player to express a chord with multiple inversions where the root is not necessarily the lowest note played. This feature is what sets MidiFitz apart from MIDI keyboards that require you to play root-position chords in order to build accompaniments in real time.

The six bool members identify the chord form--major, minor, seventh, diminished, augmented, and major seventh. When you play the chord, the program finds the voicing in the array, determines the root note by applying the root offset to the lowest note played, and determines the chord form based on the setting of the bool members.

Playing a Line

Given a chord, the program has to play a respectable bass line. For those of you who don't already know, a bass line is that bottom note that the guy with the four-string guitar plays in rock bands. The note usually provides the harmonic foundation of the music as well as keeping time. A bass line "walks" the chord progression in time with the drummer.

If you still don't know what I'm talking about, stand on any street corner and wait for an old car to go by with young occupants and huge speakers on the back window shelf. You'll hear the bass line: BOOM! BOOM! BOOM! BOOM! See the occupants mindlessly stare out the windows. Watch the car windows bulge, the occupant's heads implode slightly, and their eyes bug out with each pulse. Imagine their eardrums turning to leather. That's a bass line. MidiFitz, being a mainstream jazz player, is quieter and does not threaten your senses.

MidiFitz observes the current chord and determines a bass line to play from an array of bass lines. There are two forms: one for major and minor chords; and one for the sevenths, augmented, diminished, and minor seventh chords. Each line consists of measures and beats. Each beat consists of a numerical note relative to the root note of the chord. The program selects a measure from the eight possible measures based on the current measure within the song. The program selects a note from that measure based on the beat within the measure. If the chord is a minor or diminished and the note is a third, the note is flatted (decremented). If the chord is diminished and the note is a fifth or seventh, the note is flatted. If the chord is augmented and the note is a fifth, the note is sharped (incremented). Then MidiFitz plays the note.

Playing the note consists of sending a note on message with a velocity based on the current volume to an object of the MidiOut class. The class encapsulates the MIDI output API. You can't just turn a note on and leave it on; it might persist beyond its time. The program waits for a few clock ticks and sends a note on message with a zero velocity.

The MIDI output device is selected by the user from among those available. The Windows MIDI API includes functions that tell you how many MIDI output devices are available and what their names are. The MidiOut class uses those functions to load a drop-down list box with the names of the output devices. From that list, the piano player selects an output device for the bass line and the piano notes.

An output device has 16 channels, and you can assign instrument voices to each. I use channel 0 for the piano voice and channel 1 for the bass. The piano player selects from the standard piano and bass voices to assign to the channels. Those assignments, in MIDI parlance, are "patches." The MidiOut class includes a SetPatch member function to set the instrument voice patch for each channel.

The Form

The first version of MidiFitz is based on the 32-bar song structure. A bar is the same as a measure. Not all songs follow that structure, but it is a start. For the first 16 bars of the first chorus, MidiFitz plays two bass notes per measure: "In two." For the next eight bars, which is the bridge (or release) in a typical 32-bar song, MidiFitz plays four notes to the measure: "In four." Then back to two notes per measure for the last eight bars. After the first chorus, everything is in four. That pattern is a common jazz idiom used to add an element of swing to a song.

Remembering the Song

MidiFitz remembers the chord structure that you lay down in the first 32 bars and repeats it automatically for all subsequent choruses. That way, the piano player can play less diligently in the left hand after the first chorus, but the chords will still be as correct as in the first chorus. You can even let the bass play a solo after the first chorus.

Ending the Tune

During the course of a chorus, you can choose the Last Chorus button to tell MidiFitz to wind up the tune with a standard ending when the chorus ends. You can choose the Tag button to tell MidiFitz to put a standard tag on the tune. If MidiFitz is using an external clock, you can end at any time by stopping the MIDI device that supplies the external clock events. That practice is known among musicians as "chorus interruptus."

What Else?

I've had a lot of fun with this project, and it's a program that I use a lot to practice at home. It can't replace a live bass player and drummer, but its time is perfect and it doesn't drink, come in late, or bother the waitress.

I'd like to add a menu that saves songs and loads saved songs. I'd like more musical forms, such as the 12-bar blues or tunes with no bridge. Ask a musician in Louisville, Kentucky where the bridge to Indiana is, and he'll answer, "There ain't no bridge to Indiana." Count the measures in "The Lady is a Tramp" for a surprise.

If ever an application cried out for a touch-screen interface, this is it. I'd add more features if I could energize them with a quick tap on a large command button. A piano player's attention is on the other keyboard, and there are no hands left over for mice and the like.

Source Code

The source-code files for the MidiFitz project are free. You can download them from the DDJ Forum on CompuServe and on the Internet by anonymous ftp; see "Availability," page 3.

If you cannot get to one of the online sources, send a 3.5-inch diskette and an addressed, stamped mailer to me at Dr. Dobb's Journal, 411 Borel Avenue, San Mateo, CA 94402, and I'll send you the source code. Be sure to include a note that says which project you want. The code is free, but if you care to support my Careware charity, include a dollar for the Brevard County Food Bank.

Figure 1: The MidiFitz application.

Example 1: The midiChord structure.

struct midiChord {
    unsigned char note[4];
    unsigned char root;
    BOOL major;
    BOOL minor;
    BOOL seventh;
    BOOL diminished;
    BOOL augmented;
    BOOL major7;
    int operator!=(midiChord& crd);
};

Listing One

// ----- midiin.h
#ifndef MIDIIN_H
#define MIDIIN_H
#include "stdafx.h"
#include <mmsystem.h>
class MidiIn    {
    HMIDIIN hMidiIn;
public:
    MidiIn(HWND hWnd);
    ~MidiIn();
};
#endif

Listing Two

// ---- midiin.cpp
#include "stdafx.h"
#include "midifitz.h"
#include "midifdlg.h"
MidiIn::MidiIn(HWND hWnd)
{
    hMidiIn = 0;
    // ---- test for MIDI input devices
    if (midiInGetNumDevs() == 0)
        throw("No MIDI input devices");
    // ---- assume one MIDI input device, open the keyboard
    if (midiInOpen(&hMidiIn, 0, (unsigned long) hWnd, 0, 
                CALLBACK_WINDOW) != 0)
        throw("Cannot open MIDI input device");
    midiInStart(hMidiIn);
}
MidiIn::~MidiIn()
{
    if (hMidiIn != 0)    {
        midiInStop(hMidiIn);
        midiInClose(hMidiIn);
    }
}

Listing Three

// ----- midiout.h
#ifndef MIDIOUT_H
#define MIDIOUT_H
#include "stdafx.h"
#include <mmsystem.h>
class MidiOut    {
    HMIDIOUT hMidiOut;
    short int numDevices;
    short int currDevice;
    BOOL midimapper;
public:
    MidiOut(short int outputdevice);
    ~MidiOut();
    void DeviceList(CComboBox* dlist);
    void SetPatch(short int channel, short int voice);
    void NoteOn(short int channel, short int note, short int velocity);
    void NoteOff(short int channel, short int note, short int velocity);
    void Pedal(short int channel, short int pedal, short int velocity);
    void ChangeDevice(short int device);
    void StartMessage();
    void TimingMessage();
    void StopMessage();
};
#endif

Listing Four

// ---- midiout.cpp
#include "stdafx.h"
#include "midifitz.h"
#include "midifdlg.h"
MidiOut::MidiOut(short int outputdevice)
{
    hMidiOut = 0;
    numDevices = 0;
    // ---- test for MIDI input devices
    if ((numDevices = midiOutGetNumDevs()) == 0)
        throw("No MIDI output devices");
    MIDIOUTCAPS ocaps;
    midimapper =
        !midiOutGetDevCaps((unsigned)MIDIMAPPER, &ocaps, sizeof(ocaps));
    if (midimapper)
        --outputdevice;
    currDevice = outputdevice;
    if (midiOutOpen(&hMidiOut, currDevice, 0, 0L, 0L))
        throw ("Cannot open MIDI output device");
}
MidiOut::~MidiOut()
{
    if (hMidiOut != 0)
        midiOutClose(hMidiOut);
}
void MidiOut::ChangeDevice(short int device)
{
    if (midimapper)
        --device;
    ASSERT(device < numDevices);
    if (device != currDevice)    {
        midiOutClose(hMidiOut);
        currDevice = device;
        if (midiOutOpen(&hMidiOut, currDevice, 0, 0L, 0L))
            throw("Cannot open MIDI output device");
    }
}
void MidiOut::SetPatch(short int channel, short int voice)
{
     DWORD mmsg  = 0xc0 | channel | (voice << 8);
     midiOutShortMsg(hMidiOut, mmsg);
}
void MidiOut::NoteOn(short int channel, short int note, short int velocity)
{
     DWORD mmsg  = 0x90 | channel | (note << 8) | (velocity << 16);
     midiOutShortMsg(hMidiOut, mmsg);
}
void MidiOut::NoteOff(short int channel, short int note, short int velocity)
{
     DWORD mmsg  = 0x80 | channel | (note << 8) | (velocity << 16);
     midiOutShortMsg(hMidiOut, mmsg);
}
void MidiOut::Pedal(short int channel, short int pedal, short int velocity)
{
     DWORD mmsg  = 0xB0 | channel | (pedal << 8) | (velocity << 16);
     midiOutShortMsg(hMidiOut, mmsg);
}
void MidiOut::StartMessage()
{
     DWORD mmsg  = 0xfa;
     midiOutShortMsg(hMidiOut, mmsg);
}
void MidiOut::TimingMessage()
{
     DWORD mmsg  = 0xf8;
     midiOutShortMsg(hMidiOut, mmsg);
}
void MidiOut::StopMessage()
{
     DWORD mmsg  = 0xfc;
     midiOutShortMsg(hMidiOut, mmsg);
}
void MidiOut::DeviceList(CComboBox* dlist)
{
    ASSERT(dlist != 0);
    MIDIOUTCAPS ocaps;
    if (midimapper)
        dlist->AddString("MIDI Mapper");
    for (short int i = 0; i < numDevices; i++)    {
        midiOutGetDevCaps(i, &ocaps, sizeof(ocaps));
        dlist->AddString(ocaps.szPname);
    }
}


Copyright © 1995, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.