您的位置:首页 - Hover - 正文
Windows Low level MIDI API
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.