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. 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.
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).
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, 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.
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, 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.
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 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.