Using the Low level MIDI API, you need to first call midiOutOpen() or midiInOpen() to open some MIDI device for output or input respectively.

In order to write out MIDI data to a particular device, you need to first call midiOutOpen() once, passing it the Device ID of that desired device. Then, you can subsequently call a function such as midiOutShortMsg() which (immediately) outputs MIDI data to that device.

In order to read incoming MIDI data from a particular device, you need to first call midiInOpen() once, passing it the Device ID of that desired device. Then, Windows will subsequently pass your program each incoming MIDI message from that device.

After you're done inputting or outputting to a device (and have no further use for it), you must close that device.

Think of a MIDI device like a file. You open it, you read or write to it, and then you close it.


Opening the default MIDI device for input or output

How does your program choose a MIDI device for input or output? There are several different approaches you can take, depending upon how fancy and flexible you want your program to be.

Recall that Windows maintains separate lists of the devices which are capable of inputting MIDI data, and the devices capable of outputting MIDI data. Remember that the first device in each list has a Device ID of 0. This would be the "default" MIDI Input device and MIDI Output device respectively. So, if you simply want to open the default MIDI Output device, then use a Device ID of 0 with midiOutOpen() as so:

unsigned long result;
HMIDIOUT      outHandle;

/* Open the default MIDI Out device */
result = midiOutOpen(&outHandle, 0, 0, 0, CALLBACK_NULL);
if (result)
{
   printf("There was an error opening the default MIDI Out device!\r\n");
}
Of course, if the user has no device installed capable of outputting or playing MIDI data, the above call returns an error, so always check that return value.

Likewise, use a Device ID of 0 with midiInOpen() to open the default MIDI Input device. (Note that these two default devices may or may not be components of the same card. In other words, whichever MIDI IN jack is the default MIDI Input, and whichever MIDI OUT jack is the default MIDI Output, could be on two entirely different cards. But that is irrelevant to your purposes).

unsigned long result;
HMIDIIN      inHandle;

/* Open the default MIDI In device. Note: myWindow is a handle to some open window */
result = midiInOpen(&inHandle, 0, (DWORD)myWindow, 0, CALLBACK_WINDOW);
if (result)
{
   printf("There was an error opening the default MIDI In device!\r\n");
}

So what actually is the default MIDI Output device? Well, that's whatever device that the user choose from the list of MIDI Output devices under "Single Instrument" of Control Panel's Multimedia utility (ie, on the "MIDI" page). The list on this page is Windows actually displaying all of the names that were added to its list of devices capable of outputting or playing MIDI data.

On the other hand, the default MIDI Input device is whichever MIDI device happened to first get into the list of MIDI Input devices upon bootup. The user really has no control over setting this.

Opening the MIDI Mapper for output

Whereas the above method would work to choose the MIDI Output device, there is another option you can use instead. You can open the MIDI Mapper for MIDI output. What is the advantage of this? Well, it allows the user to use the MIDI Mapper to route the MIDI data (according to MIDI channel) to different output devices. For example, he could set all MIDI events on channel 1 to go to the built-in wavetable module on his Creative Labs sound card, and all MIDI events on channel 2 to go to the built-in wavetable on his Turtle Beach sound card. In this way, although your program outputs to only one device, it actually supports having the various MIDI channels going to different devices (which the user may desire for more polyphony or because some cards are better suited for certain sounds, etc).

NOTE: In Windows 95, the MIDI Mapper settings are actually the "Custom configuration" settings upon the Multimedia utility's MIDI page. These allow for even more flexible routing of MIDI data, plus the "Add new Instrument" feature allows the user to apply Instrument Definition Files thus remapping your program's MIDI output even more, for example, to make non-General MIDI instruments conform to General MIDI.

The MIDI Mapper has a defined Device ID of -1, so to open MIDI Mapper for MIDI Output:

unsigned long result;
HMIDIOUT      outHandle;

/* Open the MIDI Mapper. Note: myWindow is a handle to some open window */
result = midiOutOpen(&outHandle, (UINT)-1, (DWORD)myWindow, 0, CALLBACK_WINDOW);
if (result)
{
   printf("There was an error opening MIDI Mapper!\r\n");
}
One drawback with MIDI Mapper is that it does impose an extra layer of software processing upon your MIDI output. If the user never enables the "Custom configuration", then all MIDI data ends up going to one device anyway, so you gain nothing here (and lose a little efficiency).

The most flexible way to choose a MIDI device for input or output

The most flexible way would be to present the user with all of the names in the list of MIDI Output devices and let him choose which one he wants (or if your program supports multiple MIDI output devices, you may wish to let him pick out several names from the list, and assign each sequencer "track" to one of those Device IDs. This is how professional sequencers implement support for multiple cards/outputs).

Whereas Windows maintains separate lists of MIDI Input and Output devices, so too, Windows has separate functions for querying the devices in each list.

Windows has a function that you can call to determine how many device names are in the list of devices that support outputting or playing MIDI data. This function is called midiOutGetNumDevs(). This returns the number of devices in the list. Remember that the Device IDs start with 0 and increment. So if Windows says that there are 3 devices in the list, then you know that their Device IDs are 0, 1, and 2 respectively. You then use these Device IDs with other Windows functions. For example, there is a function you can call to get information about one of the devices in the list, for example its name, and what sort of other features it has. You pass the Device ID of the device which you want to get information about (as well as a pointer to a special structure called a MIDIOUTCAPS into which Windows puts the info about the device), The name of the function to get information about a particular MIDI Output device is midiOutGetDevCaps().

Here then is an example of going through the list of MIDI Output devices, and printing the name of each one:

MIDIOUTCAPS     moc;
unsigned long   iNumDevs, i;

/* Get the number of MIDI Out devices in this computer */
iNumDevs = midiOutGetNumDevs();

/* Go through all of those devices, displaying their names */
for (i = 0; i < iNumDevs; i++)
{
    /* Get info about the next device */
    if (!midiOutGetDevCaps(i, &moc, sizeof(MIDIOUTCAPS)))
    {
        /* Display its Device ID and name */
        printf("Device ID #%u: %s\r\n", i, moc.szPname);
    }
}
Likewise with MIDI Input devices, Windows has a function that you can call to determine how many device names are in the list of devices that support inputting or creating MIDI data. This function is called midiInGetNumDevs(). This returns the number of devices in the list. Again, the Device IDs start with 0 and increment. There is a function you can call to get information about one of the devices in the list, for example its name, and what sort of other features it has. You pass the Device ID of the device which you want to get information about (as well as a pointer to a special structure called a MIDIINCAPS into which Windows puts the info about the device), The name of the function to get information about a particular MIDI Input device is midiInGetDevCaps().

Here then is an example of going through the list of MIDI Input devices, and printing the name of each one:

MIDIINCAPS     mic;
unsigned long    iNumDevs, i;

/* Get the number of MIDI In devices in this computer */
iNumDevs = midiInGetNumDevs();

/* Go through all of those devices, displaying their names */
for (i = 0; i < iNumDevs; i++)
{
    /* Get info about the next device */
    if (!midiInGetDevCaps(i, &mic, sizeof(MIDIINCAPS)))
    {
        /* Display its Device ID and name */
        printf("Device ID #%u: %s\r\n", i, mic.szPname);
    }
}
You can download my ListMidiDevs C example to show how to print the names of all the installed MIDI Input and Output devices, as well as other info about each device. Included are the Project Workspace files for Visual C++ 4.0, but since it is a console app, any Windows C compiler should be able to compile it. Remember that all apps should include MMSYSTEM.H and link with WINMM.LIB (or MMSYSTEM.LIB if Win3.1). This is a ZIP archive. Use an unzip utility that supports long filenames.


Outputting MIDI data (except System Exclusive)

How does an application tell Windows to output some MIDI bytes? That depends upon whether you're outputting System Exclusive Messages, or some other kind of MIDI message. All MIDI messages, except for System Exclusive, always have 3 or less bytes. So, Windows has a function through which you can pass such a MIDI message in its entirety for output. This function is called midiOutShortMsg(). What you do is pack up the 3 or less bytes of that MIDI message as an unsigned long value, and pass it to midiOutShortMsg(). The bytes of this MIDI message then get output as soon as possible (ie, hopefully immediately).

With midiOutShortMsg(), you need to pack up these 3 bytes into one unsigned long which is passed as one arg. The LSB of the low word is the MIDI status (for example, 0x90 for MIDI channel 1). The MSB of the low word is the first MIDI data byte, if any. (For Note events, this would be the MIDI note number). The LSB of the high word is the second MIDI data byte, if any. (For Note events, this would be the note velocity). The MSB of the high word is not used.

Note: Always include a status byte. The device driver for the card will implement running status when it outputs the MIDI message.

Let's take an example of playing a 3 note chord -- a C chord (ie, C, E, and G notes).

Each musical pitch of a chord is expressed as a MIDI note number (middle C is note number 60, so D# above middle C is #61, etc). We'll create a MIDI message for each one of those 3 note numbers. A MIDI message takes the form of 3 bytes; the Status byte, the note number, and velocity (usually implements note volume). The Status byte for turning a note on is 0x9X where X is the MIDI channel number desired (0 to F for MIDI channels 1 to 16 -- we'll use a default of 0 but you may want to allow the user to change this). So for MIDI channel 1, the status is always 0x90. For velocity, we'll use a default of 0x40.

HMIDIOUT    handle;

/* Open default MIDI Out device */
if (!midiOutOpen(&handle, 0, 0, 0, CALLBACK_NULL) )
{
    /* Output the C note (ie, sound the note) */
    midiOutShortMsg(handle, 0x00403C90);

    /* Output the E note */
    midiOutShortMsg(handle, 0x00404090);

    /* Output the G note */
    midiOutShortMsg(handle, 0x00404390);

    /* Here you should insert a delay so that you can hear the notes sounding */

    /* Now let's turn off those 3 notes */
    midiOutShortMsg(handle, 0x00003C90);
    midiOutShortMsg(handle, 0x00004090);
    midiOutShortMsg(handle, 0x00004390);

     /* Close the MIDI device */
     midiOutClose(handle);
}
Note: If your application is doing some sort of sequencing (ie, playback of a musical piece), you'll have to maintain a timer in order to figure out when it's time to output the next MIDI message via midiOutShortMsg(). (Note that 32-bit Windows MultiMedia Timer callbacks under Win95 may suffer severe timing fluctuations. Since Win95's multimedia system is still 16-bit, you need to put your callback (and any functions it calls) into a 16-bit DLL in order for it to exhibit solid performance under Win95. WinNT doesn't exhibit this aberrant behavior with 32-bit code).

You can download my Twinkle C example to show how to use midiOutShortMsg to play MIDI notes on the default MIDI Out device. Included are the Project Workspace files for Visual C++ 4.0, but since it is a console app, any Windows C compiler should be able to compile it.

Outputting System Exclusive MIDI messages

Since System Exclusive messages can be any length, Windows has a different means for outputting them. You use the function midiOutLongMsg(), passing it a buffer filled with the MIDI message. (In fact, you can use midiOutLongMsg to pass a buffer filled with several non-System Exclusive messages. This is very handy if you need to output several MIDI messages that should occur simultaneously. Using midiOutShortMsg() for each individual MIDI message as we did in the example above may cause too much of a delay inbetween each MIDI message). You actually pass a special structure called a MIDIHDR which has a field where you store the pointer to your buffer containing the MIDI data.

But there are a few caveats:

  1. If running Win3.1, the data buffer (and perhaps the MIDIHDR structure, although MS examples show otherwise) must be allocated with GlobalAlloc() using the GMEM_MOVEABLE and GMEM_SHARE flags, and locked with GlobalLock. Under Win95 and WinNT, this no longer appears to be a requirement.

  2. Before you pass the buffer to midiOutLongMsg(), you must first "prepare" it by calling midiOutPrepareHeader().

  3. The MIDI Output device's driver determines whether the data is sent synchronously or asynchronously. So, with some devices, your app won't return from the call to midiOutLongMsg() until all of the data is output, whereas with other devices, you may return immediately and the driver will continue outputting the data in the background.

  4. After you're done with the buffer, you must "unprepare" it by calling midiOutUnprepareHeader().

Here's an example of outputting a System exclusive message under Win3.1:

HMIDIOUT    handle;
MIDIHDR     midiHdr;
HANDLE      hBuffer;
UINT        err;
char		sysEx[] = {0xF0, 0x7F, 0x7F, 0x04, 0x01, 0x7F, 0x7F, 0xF7};

/* Open default MIDI Out device */
if (!midiOutOpen(&handle, 0, 0, 0, CALLBACK_NULL))
{
    /* Allocate a buffer for the System Exclusive data */
    hBuffer = GlobalAlloc(GHND, sizeof(sysEx));
    if (hBuffer)
    {
        /* Lock that buffer and store pointer in MIDIHDR */
        midiHdr.lpData = (LPBYTE)GlobalLock(hBuffer);
        if (midiHdr.lpData)
        {
            /* Store its size in the MIDIHDR */
            midiHdr.dwBufferLength = sizeof(sysEx);

            /* Flags must be set to 0 */
            midiHdr.dwFlags = 0;

            /* Prepare the buffer and MIDIHDR */
            err = midiOutPrepareHeader(handle,  &midiHdr, sizeof(MIDIHDR));
            if (!err)
            {
                /* Copy the SysEx message to the buffer */
                memcpy(midiHdr.lpData, &sysEx[0], sizeof(sysEx));

                /* Output the SysEx message */
                err = midiOutLongMsg(handle, &midiHdr, sizeof(MIDIHDR));
                if (err)
                {
                    char   errMsg[120];

                    midiOutGetErrorText(err, &errMsg[0], 120);
                    printf("Error: %s\r\n", &errMsg[0]);
                }

                /* Unprepare the buffer and MIDIHDR */
                while (MIDIERR_STILLPLAYING == midiOutUnprepareHeader(handle, &midiHdr, sizeof(MIDIHDR)))
                {
                    /* Should put a delay in here rather than a busy-wait */
                }
            }

            /* Unlock the buffer */
            GlobalUnlock(hBuffer);
        }

        /* Free the buffer */
        GlobalFree(hBuffer);
    }

    /* Close the MIDI device */
    midiOutClose(handle);
}
Win95 and WinNT are easier. Here's an example to output a System Exclusive message under Win95/NT:
HMIDIOUT    handle;
MIDIHDR     midiHdr;
UINT        err;
char		sysEx[] = {0xF0, 0x7F, 0x7F, 0x04, 0x01, 0x7F, 0x7F, 0xF7};

/* Open default MIDI Out device */
if (!midiOutOpen(&handle, 0, 0, 0, CALLBACK_NULL))
{
    /* Store pointer in MIDIHDR */
    midiHdr.lpData = (LPBYTE)&sysEx[0];

    /* Store its size in the MIDIHDR */
    midiHdr.dwBufferLength = sizeof(sysEx);

    /* Flags must be set to 0 */
    midiHdr.dwFlags = 0;

    /* Prepare the buffer and MIDIHDR */
    err = midiOutPrepareHeader(handle,  &midiHdr, sizeof(MIDIHDR));
    if (!err)
    {
        /* Output the SysEx message */
        err = midiOutLongMsg(handle, &midiHdr, sizeof(MIDIHDR));
        if (err)
        {
            char   errMsg[120];

            midiOutGetErrorText(err, &errMsg[0], 120);
            printf("Error: %s\r\n", &errMsg[0]);
        }

        /* Unprepare the buffer and MIDIHDR */
        while (MIDIERR_STILLPLAYING == midiOutUnprepareHeader(handle, &midiHdr, sizeof(MIDIHDR)))
        {
            /* Should put a delay in here rather than a busy-wait */		    
        }
    }

    /* Close the MIDI device */
    midiOutClose(handle);
}
You can download my MidiVol C example to show how to use midiOutLongMsg to output a MIDI System Exclusive on the default MIDI Out device. Included are the Project Workspace files for Visual C++ 4.0, but since it is a console app, any Windows C compiler should be able to compile it.


Inputting MIDI data

In the examples of outputting MIDI data, you'll notice that in the call to midiOutOpen(), I specified CALLBACK_NULL. What this flag means is that (other than the return codes from functions such as midiOutShortMsg or midiOutLongMsg), I require no feedback from Windows on the progress of the output of that MIDI data. (Remember that these output functions may return to the app before the data is finished being sent, if the driver has some means of doing that output in the background). I could have asked Windows to provide me with feedback (as we'll do in our MIDI input examples), but it wasn't necessary in those output examples, and it's simpler not to do it (although you should do it if you've called midiOutLongMsg -- in order to know when it's time to call midiOutUnprepareHeader. Otherwise, you'd have to do polling on the MHDR_DONE bit of the dwFlags field of the MIDIHDR structure. This bit will be set when the MIDI driver is finished with the MIDIHDR).

With MIDI input, you must provide a means of Windows giving you some sort of feedback. Why? Because you can't keep continuously polling the MIDI In port of a MIDI Input device waiting for incoming MIDI messages. That's an outdated MS-DOS programming technique. Rather, Windows programs are supposed to relinquish control back to Windows when the program has nothing to do except wait for something to happen, (Although it's possible, albeit not good programming practice, to do polling of the MHDR_DONE bit when inputting System Exclusive messages, for input of other types of MIDI messages, Windows requires that you provide a different means for Windows to pass you MIDI data).

Instead, Windows will interact with your program whenever each MIDI message is input (ie, all of the bytes in it, for non-System Exclusive messages) or some input buffer you've supplied is filled (for System Exclusive messages). How does Windows interact with your program? You have several options as follows:

  1. CALLBACK_EVENT -- You allocate some Event with CreateEvent(), and Windows uses this to signal your app. (ie, Your app can wait on that Event signal, for example with WaitForSingleObject). You pass the handle of the Event as the 3rd arg to midiInOpen().

  2. CALLBACK_THREAD -- Windows causes some suspended thread within your app to run. (ie, Your app's thread can suspend itself via SuspendThread). You pass the Thread ID of the desired thread to be run as the 3rd arg to midiInOpen().

  3. CALLBACK_WINDOW -- Windows sends a message to some open window in your app. The parameters for the message will contain additional information about what caused Windows to send that message. You pass the desired window's handle as the 3rd arg to midiInOpen().

  4. CALLBACK_FUNCTION -- Windows directly calls some function in your app. It passes args that contain additional information about what caused Windows to call your function. You pass a pointer to the desired function as the 3rd arg to midiInOpen(). The 4th arg to midiInOpen() can be anything you desire, and this will be passed to your callback function each time that Windows calls your callback.

The latter two methods allow you to better determine what exactly caused Windows to notify you, because they supply additional information to you. In fact, for regular MIDI messages (ie, everything except System Exclusive messages -- I'll simply refer to these as "regular messages"), the latter two methods are the only methods you can use to actually get Windows to pass you the incoming MIDI data. (For System Exclusive, you could use the CALLBACK_EVENT or CALLBACK_THREAD, if you're not really interested in being notified of errors). For this reason, I'll only detail the latter two methods.

So when does Windows notify you? Here are the times when Windows notifies you:

  1. When you open a MIDI In device via midiInOpen().

  2. When you close a MIDI In Device via midiInClose().

  3. When Windows has finished inputting one regular MIDI message in its entirety.

  4. When Windows has filled a MIDIHDR's buffer with a portion, or all, of a System Exclusive message.

  5. When there has been an error inputting a regular or System Exclusive MIDI message.

  6. When your handling of a particular MIDI message (that you've been passed) is so slow that the MIDI driver (and possibly the MIDI Interface) has had to throw away incoming MIDI data while it was waiting for you to finish processing a previous message.

In conclusion, you can have Windows call a function you have written, passing the MIDI data that has been input, or you can have Windows pass a message to one of your program's windows, with the MIDI data that has been input as part of that message.

To have Windows call a function you have written, when you open the device, you specify the flag CALLBACK_FUNCTION. The third arg is a pointer to your function. (The fourth arg can be any value that you want Windows to pass to your function each time that function is called).

result = midiInOpen(&inHandle, 0, (DWORD)myFunc, 0, CALLBACK_FUNCTION);
To have Windows pass a message to one of your windows, when you open the device, you specify the flag CALLBACK_WINDOW. The third arg is a handle to your window. (The fourth arg is not used).
result = midiInOpen(&inHandle, 0, (DWORD)myWindow, 0, CALLBACK_WINDOW);
One caveat with this second method is that Windows doesn't timestamp each MIDI message. So if you need timestamps, you would have to timestamp each MIDI message yourself using some software timer (ie, perhaps the one that you're using to time the output of MIDI messages, for example, a Windows MultiMedia timer implemented using functions such as timeGetTime). But such message passing is not very efficient. By the time that your window procedure finally got around to pulling that MIDI data out of a message and obtaining a time stamp for it, a long come could have transpired since it actually arrived at the computer's MIDI IN. Trying to get an accurate time stamp using this method is very difficult, especially if other things are happening in the system such as mouse and window movement. It is recommended that you use CALLBACK_WINDOW only when you don't need time stamps. Otherwise, use the CALLBACK_FUNCTION method.

If using the CALLBACK_FUNCTION method, then you need to write a function that has the following declaration (although you can name the function anything you like):


void CALLBACK midiCallback(HMIDIIN handle, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2);
As mentioned, you pass a pointer to this function as the 3rd arg to midiInOpen(). The 4th arg to midiInOpen() can be anything you desire, and this will be passed to your callback function (as the dwInstance arg) each time that Windows calls your callback. The handle arg is what was returned from midiInOpen().

The other args may be interpreted differently depending upon why Windows has called your callback. Here are those reasons:

  1. You open a MIDI In Device via midiInOpen(). In this case, the uMsg arg to your callback will be MIM_OPEN.

  2. You close a MIDI In Device via midiInClose(). In this case, the uMsg arg to your callback will be MIM_CLOSE.

  3. One, regular (ie, everything except System Exclusive messages) MIDI message has been completely input. In this case, the uMsg arg to your callback will be MIM_DATA. The dwParam1 arg is the bytes of the MIDI Message packed into an unsigned long in the same format that is used by midiOutShort(). The dwParam2 arg is a time stamp that the device driver created when it recorded the MIDI message.

  4. midiInOpen has either completely filled a MIDIHDR's memory buffer with part of a System Exclusive message (in which case you had better continue queuing the MIDIHDR again in order to grab the remainder of the System Exclusive), or the MIDIHDR's memory buffer contains the remainder of a System Exclusive message (or the whole message if it happened to fit into the memory buffer intact). In this case, the uMsg arg to your callback will be MIM_LONGDATA. The dwParam1 arg is a pointer to the MIDIHDR whose memory buffer contains the System Exclusive data. The dwParam2 arg is a time stamp that the device driver created when it recorded the MIDI message.

  5. Your callback is not processing data fast enough such that the MIDI driver (and possibly the MIDI In port itself) has had to throw away some incoming, regular MIDI messages. In this case, the uMsg arg to your callback will be MIM_MOREDATA. The dwParam1 arg is the bytes of the MIDI Message that was not handled (by an MIM_DATA call) packed into an unsigned long in the same format that is used by midiOutShort(). The dwParam2 arg is a time stamp that the device driver created when it recorded the MIDI message. In handling a series of these events, you should store the MIDI data in a global buffer, until such time as you receive another MIM_DATA (which indicates that you can now do the more time-consuming processing that you obviously were doing in handling MIM_DATA). In other words, when Windows calls your callback with MIM_MOREDATA, this is it's way of saying "You're handling your previous MIM_DATA messages too slowly. (And in fact, I may have preemptively interrupted a previous MIM_DATA handling of your callback). This is your last chance to quickly do something with this one message that I'm passing you now. Otherwise, you're so far behind in handling the MIDI input that data is about to be permanently lost".

    NOTE: Windows sends an MIM_MOREDATA event only if you specify the MIDI_IO_STATUS flag to midiInOpen().

  6. An invalid, regular MIDI message was received. In this case, the uMsg arg to your callback will be MIM_ERROR. The dwParam1 arg is the bytes of the MIDI Message that was not handled (by an MIM_DATA call) packed into an unsigned long in the same format that is used by midiOutShort(). The dwParam2 arg is a time stamp that the device driver created when it recorded the MIDI message.

  7. An invalid, System Exclusive message was received. In this case, the uMsg arg to your callback will be MIM_LONGERROR. The dwParam1 arg is a pointer to the MIDIHDR whose memory buffer contains the System Exclusive data. The dwParam2 arg is a time stamp that the device driver created when it recorded the MIDI message.

MIDI time stamps are defined as the time the first byte of the message was received and are specified in milliseconds. The midiInStart() function resets the time stamps for a device to 0.

You can download my Midi In Callback C example to show how to input MIDI messages on the default MIDI In device. Included are the Project Workspace files for Visual C++ 4.0, but since it is a console app, any Windows C compiler should be able to compile it.