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.
Recall that Windows maintains separate lists of the devices which are capable of inputting MIDI data, and the devices capable of outputting MIDI data. For output, there is also the MIDI Mapper. It's not really a device in the MIDI Output list. (Ie, If you enumerate the devices in that list, you won't come across the MIDI Mapper). But it is there nonetheless. So what MIDI Output is the MIDI Mapper attached to? Well, that depends upon the settings that the user has made in Control Panel's Multimedia MIDI page. This Control Panel utility lets him route default MCI MIDI Output to a single MIDI Output (ie, one of the real MIDI Outputs in Windows' list). Or, he can use the "Custom configuration" setup (not available in Windows 2000/NT/XP) to split up the 16 MIDI channels among several of the real MIDI Outputs, 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. So when using the MIDI Mapper, 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). Plus, the "Add new Instrument" feature (not available in Windows earlier than Windows 95) 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.
When you use the MIDI Mapper for output, think of the Multimedia utility's "MIDI" page as becoming the "MIDI Setup" dialog for your own application. Whichever way the user set that page up is where your calls to midiOutShortMsg() and midiOutLongMsg() get routed. So, by opening the MIDI Mapper, you use the "default MIDI Out" setup.
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 */ result = midiOutOpen(&outHandle, (UINT)-1, 0, 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).
unsigned long result; HMIDIIN inHandle; /* Open the MIDI In device #0. 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"); }Of course, if the user has no device installed capable of inputting MIDI data, the above call returns an error, so always check that return value.
Note that the device(s) opened for output via MIDI Mapper, and the device opened for input above, 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).
The MIDI Input device with an ID of 0 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.
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.
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, (UINT)-1, 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 */ Sleep(1000); /* 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 timer 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).
Warning: There are some badly written drivers out there, especially for Windows NT/2000/XP. midiOutShortMsg() doesn't actually output the MIDI message. Instead, the driver puts the message in some internal queue for another thread (running inside of the driver) to output later, and then midiOutShortMsg() returns immediately. There's nothing wrong with that per se, but in these badly written drivers, when you call midiOutClose(), the driver discards any MIDI messages in its queue (as opposed to properly flushing them to MIDI Out). So if you call midiOutClose() too soon after calling midiOutShortMsg() (or perhaps even midiOutLongMsg()), then your MIDI output may end up being discarded before that other thread inside of the driver gets a chance to output the message. If the above example code results in stuck notes (or no sound at all) unless you put another Sleep() before the call to midiOutClose(), then you're dealing with such a badly designed driver. You could try to write some workaround that, whenever to call midiOutShort(), set a flag variable and start a one-shot timer. At the end of the timer, clear the flag. Before you ever call midiOutClose(), make sure that this flag is clear. If not, postpone the close. But a better solution is to contact the author of your card's driver, and tell him to fix his badly designed 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.
But there are a few caveats:
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, (UINT)-1, 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, (UINT)-1, 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 message 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.
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:
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:
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:
NOTE: Windows sends an MIM_MOREDATA event only if you specify the MIDI_IO_STATUS flag to midiInOpen().
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. 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.