After you're done outputting to a stream device (and have no further use for it), you must close that device.
Think of a stream device like a file. You open it, you write to it (ie, queue MIDI events to it which Windows "plays"), and then you close it.
unsigned long result, deviceID; HMIDISTRM outHandle; /* Open default MIDI Out stream device */ deviceID = 0; result = midiStreamOpen(&outHandle, &deviceID, 1, 0, 0, CALLBACK_NULL); if (result) { printf("There was an error opening the default MIDI stream device!\r\n"); }
Individual Windows 95 drivers for any installed cards may be specially written to directly support the Stream API. When you query a particular MIDI Output device (using midiOutGetDevCaps), you can check the dwSupport field of the MIDIOUTCAPS structure. If the driver directly supports the Stream API, the MIDICAPS_STREAM bit of dwSupport will be set. (Download my ListMidiDevs C example to show how to query the MIDI Output devices for such support). Such a driver may offer special features such as allowing the Stream Manager to sync playback to incoming SMPTE or MIDI Time Code. Those features would be determined by what code has been added to the driver, as well as perhaps hardware support by the MIDI card itself.
One caveat is that the MIDI Mapper cannot be opened as the output for a MIDI stream. The Windows MIDI Stream Manager supports only one MIDI output at a time (unless the card's driver directly supports the Stream API and adds some sort of routing feature based upon the MIDIEVENT's dwStreamID field. Currently, the Stream Manager doesn't support this routing).
Here's how you pass a MIDI message to be played back:
The MIDI message is now queued for playback. If you haven't called midiStreamRestart() yet, then Windows does not yet start timing out and outputting this event. So, you can queue several MIDI messages prior to the start of playback by repeating the above steps, using a separate MIDIEVENT and MIDIHDR for each message.
It's also possible to queue several MIDI messages using one MIDIHDR and one call to midiOutPrepareHeader() and midiStreamOut(). You do this by using an array of MIDIEVENT structures (ie, one for each of your MIDI messages). Place each MIDI message into one of the MIDIEVENT structures in that array. Then place a pointer to the entire array in the MIDIHDR's lpData field. Of course, the MIDIHDR's dwBufferLength and dwBytesRecorded fields are set to the size of the entire array of MIDIEVENT structures. It is much more efficient and memory-conserving to combine all MIDI messages that occur upon the same musical beat into one array of MIDIEVENTS, and cue them with one MIDIHDR.
Note that after Windows finishes playing all of the events queued with a particular MIDIHDR, it sets the MHDR_DONE bit of the dwFlags field. This may be very useful later when you're dealing with processing MIDIHDRs as a result of being notified by Windows that a MIDIHDR's events have finished playing. Also note that a MIDIHDR has a dwUser field that you can use for your own purposes.
Now it's certainly feasible to queue up all of the MIDI messages of a sequence prior to calling midiStreamRestart() by simply putting them into one gigantic array of MIDIEVENT structures, and passing the whole array in one call to midiStreamOut(). You can download my Simple Stream C example which illustrates this approach. 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. My code takes this approach in order to present the simplest possible example of using the Stream API to play a musical sequence, and therefore give you an easy introduction to how the Stream API works.
But that approach is sort of like using the High level MIDI API. After all, by passing all of the data all at once, you then lose control over too much of the playback process. The whole point of the Stream API is to feed Windows a few events at a time so that the playback doesn't get too far ahead that you lose the sense of real-time control over individual events. If you want to be doing real-time mixing of several "tracks" of MIDI messages into a single stream, giving the user the option to mute one of the tracks at any time for example, then obviously you don't want to queue MIDI messages too far in advance.
But, if you want a smooth playback, you obviously have to queue those events before they need to be played. The best approach to take is to use a sort of double-buffering scheme. In other words, you'll always have one "block" of MIDI messages queued while the current block of MIDI messages is playing. When the current block is finished playing (and the queued block starts playing), then you'll queue another block.
You'll select a brief "time window", for example 1 quarter note. (ie, Assume our "musical beat" is a quarter note). Then, before playback is started with midiStreamRestart(), you'll place all of the MIDI messages whose timing falls within the first quarter note (ie, the first beat) into an array of MIDIEVENTS (and a MIDIHDR) and queue it with midiStreamOut(). You'll also place all of the MIDI messages whose timing falls within the second quarter note (ie, second beat) into another array of MIDIEVENTS (and another MIDIHDR) and queue it with midiStreamOut(). Then, you'll call midiStreamRestart() to start playback. As soon as the last MIDI message in the first array of MIDIEVENTS is finished playing, you'll place all of the MIDI messages whose timing falls within the third quarter note into that same array of MIDIEVENTS and queue it with midiStreamOut(). Of course, while you're doing this, the second queued array has been playing. After that array's last MIDI message is played, you'll use that array to queue another "block" of MIDI messages whose timing falls within the fourth quarter note. Etc. In this way, you always have data queued for continuous, smooth playback, but the playback is only 1 musical beat ahead of any real-time action the user instructs you to perform upon the data. So, for example, if he tells you to mute one of the tracks, maybe it won't effectively work out that way for one musical beat (some events on that track may already be queued for the next beat), but that's plenty close enough.
So what if there happens to be no MIDI events that fall within the next musical beat? What do you queue? As you'll see in the next section, you can queue a single NOP event, timed to delay for an entire beat.
typedef struct { DWORD dwDeltaTime; DWORD dwStreamID; DWORD dwEvent; DWORD dwParms[]; } MIDIEVENT;The dwDeltaTime field is just like the timing in MIDI File Format (except that it's an unsigned long rather than a variable length quantity). It represents the amount of time to delay before outputting this MIDI message. You can specify the time in terms of PPQN clocks, or a SMPTE time. (When using the former, you can send "Tempo Events" to the stream device to change the tempo, or call a Stream function that allows you to change the tempo on the fly. When using the latter, Windows can sync the MIDI playback to streaming video or other SMPTE cues).
Currently, the dwStreamID field isn't used and should be set to 0. (My own tests show that whatever value you stuff into this field is ignored, but unless you're using a driver that directly supports the MIDI Stream API and the driver uses this field, it is safest to set it to 0 in case some future version of Windows utilizes this field).
Although declared as an unsigned long, the 4 bytes of the dwEvent field are actually individual pieces of information. The highest byte contains some flag bits and some bits that form an "event type" value. I'll refer to this as the Event Type byte.
If this MIDIEVENT contains a normal MIDI Voice message such as a Note-On, Program Change, Aftertouch, or any of the other MIDI messages that are 3 or less bytes, then the Event Type byte is set to the value MEVT_SHORTMSG.
In this case, the remaining 3 bytes of this field are the MIDI Status byte, the first MIDI data byte (if any), and the second MIDI data byte (if any). Note that this is packed up exactly the way that a MIDI message is passed to midiOutShortMsg(). Do not use running status. The stream device will implement running status when it outputs the MIDI messages.
Here then is how you would initialize a MIDIEVENT with a Note-On MIDI message for middle C on channel 1 (with velocity of 0x40) and a delta time of 0 (gets output as soon as Windows finishes playing the previously queued event):
MIDIEVENT mevt; mevt.dwDeltaTime = 0; mevt.dwStreamID = 0; mevt.dwEvent = ((unsigned long)MEVT_SHORTMSG<<24) | 0x00403C90;Note that since MEVT_SHORTMSG is really 0x00, we can simply the above by actually removing the setting of the Event Type byte (since its already 0 in our packed MIDI message):
mevt.dwEvent = 0x00403C90;
What about that dwParms field? Well, if you look closely at the declaration, it isn't really there. There's no size for this dwParms array. Say what?!? That's right. There is no dwParms field on the above MIDIEVENT. The structure really has only 3 fields. It should have been declared as something like this (which I'll call MY_MIDI_EVT):
typedef struct { DWORD dwDeltaTime; DWORD dwStreamID; DWORD dwEvent; } MY_MIDI_EVT;
So what was that dwParms field doing there? Well, here's where it gets tricky. There are other types of events that you can pass. For example, consider a System Exclusive event. It can have many more than 3 bytes. Where do you put all of those bytes? Well, now you use that extra dwParms array appended to the end. Its size is set to however many bytes you need to pass. In essense, you're appending the data to the end of the MIDIEVENT structure. That's right. The size of the MIDIEVENT structure you pass will vary depending upon the type of event it contains.
Let's take an example where we want to pass a System Exclusive event that consists of 7 bytes. Well, the first thing that we need to do is redeclare the MIDIEVENT structure, thereby creating a new structure. I'll call this MY_SYSEX_EVT.
typedef struct { DWORD dwDeltaTime; DWORD dwStreamID; DWORD dwEvent; unsigned char dwParams[8]; } MY_SYSEX_EVT;See? I declared an array large enough to hold my extra bytes. Why did I use a size of 8 rather than 7? Well, the Stream API requires that the size of all MIDIEVENT structures passed to it be aligned on a doubleword boundary. So, if I'm going to stuff this MIDIEVENT into a buffer containing another MIDIEVENT that comes after it, I want that second MIDIEVENT to be properly aligned. In essense, I've added a pad byte above to make the structure's size a multiple of 4, and thereby ensure that subsequent MIDIEVENT structures in the same array are properly aligned.
OK, now let's initialize it. For such a MIDIEVENT, its Event Type byte must be the value MEVT_LONGMSG. Furthermore, the remaining 3 bytes of the dwEvent field must be a 24-bit count of the number of bytes in our System Exclusive message (ie, 7 in this case).
MY_SYSEX_EVT mevt; unsigned char sysEx[] = {0xF0, 0x7F, 0x7F, 0x01, 0x02, 0xF7}; mevt.dwDeltaTime = 100; /* Just for the hell of it, delay 100 clocks before sending it */ mevt.dwStreamID = 0; mevt.dwEvent = ((unsigned long)MEVT_LONGMSG<<24) | sizeof(sysEx); memcpy(&mevt.dwParams[0], &sysEx[0], sizeof(sysEx));What other Event Types are there (besides the "short" type for MIDI messages of 3 bytes or less, and the "long" type for System Exclusive)? The most useful is a Tempo event. For such a MIDIEVENT, its Event Type byte must be the value MEVT_TEMPO. Furthermore, the remaining 3 bytes of the dwEvent field must be a Tempo value as a 24-bit value. It is expressed in microseconds per quarter note, just like in the MIDI File Format's MetaTempo event.
Another Event Type is a comment. Like a System Exclusive MIDIEVENT, the characters that form the comment are appended to the end of the MIDIEVENT structure itself. Its Event Type byte must be the value MEVT_COMMENT. Furthermore, the remaining 3 bytes of the dwEvent field must be a 24-bit count of the number of chars in the comment. Again, pad out the structure's size to a multiple of 4 if you're going to put it in an array with other MIDIEVENTs after it. Windows ignores comment events, so you can use them for your own purposes.
Another Event Type is a NOP (no operation). It's a "short" type of event, like Tempo and regular MIDI Voice messages, so no extra fields are appended to the MIDIEVENT structure. Its Event Type byte must be the value MEVT_NOP. Furthermore, the remaining 3 bytes of the dwEvent field can be used for any purpose you wish. Windows ignores NOP events, so you can use them for your own purposes.
The final Event Type is a version event. It has a MIDISTRMBUFFVER structure appended to the end of the MIDIEVENT structure. This extra structure just contains version information about the Stream. Its Event Type byte must be the value MEVT_VERSION. Furthermore, the remaining 3 bytes of the dwEvent field must be a 24-bit count of the size of the MIDISTRMBUFFVER structure.
OK, I'm sure that there is one nagging question in your mind. If MIDIEVENT structures can be variable size, depending upon the Event Type, then how do you declare a static array of them? Well, you can't really (unless you happen to stick to only using the "short" types). What you'll need to do is just copy them into one large char buffer, using a pointer to that buffer, and do some creative casting. For example, here I copy two MIDIEVENTs into one buffer:
MY_SYSEX_EVT * xevt; MY_MIDI_EVT * mevt; unsigned char sysEx[] = {0xF0, 0x7F, 0x7F, 0x01, 0x02, 0xF7}; unsigned char buffer[20]; /* Format for a Note-On */ mevt = (MY_MIDI_EVT *)&buffer[0]; mevt->dwDeltaTime = 0; mevt->dwStreamID = 0; mevt->dwEvent = 0x00403C90; mevt++; /* Format for a System Exclusive */ xevt = (MY_SYSEX_EVT *)mevt; xevt->dwDeltaTime = 100; xevt->dwStreamID = 0; xevt->dwEvent = ((unsigned long)MEVT_LONGMSG<<24) | sizeof(sysEx); memcpy(&xevt->dwParams[0], &sysEx[0], sizeof(sysEx));Of course, I should make sure that buffer is aligned upon a doubleword boundary (which you can do with your compiler's alignment directive).
But another approach to take is to simply declare your array to be an array of unsigned longs. After all, since all MIDIEVENT structures need to be padded out to a multiple of 4 bytes, then the net result is that each MIDIEVENT structure consists 3 or more unsigned longs. So, here's the above array initialized this way:
unsigned long buffer[] = { 0, 0, 0x00403C90, /* The Note-On */ 0, 0, ((unsigned long)MEVT_LONGMSG<<24) | 7, 0x017F7FF0, 0x00F70201}; /* The SysEx */Note the order of the packed SysEx bytes. Each unsigned long contains the next 4 bytes, and remember that we're using little endian order on each unsigned long.
unsigned long err; err = midiStreamRestart(outHandle); if (err) { printf("An error starting playback!\n"); }Note that when playback begins, the stream device's current time is set to 0 (if the playback hasn't been paused).
unsigned long err; err = midiStreamStop(outHandle); if (err) { printf("An error stopping playback!\n"); }This flushes all of the queued MIDIEVENTs, and you can subsequently midiOutUnprepareHeader() the MIDIHDRs. (The MHDR_DONE bit is set in the dwFlags field of all queued MIDIHDR structures).
It also turns off any notes that are still turned on. (By contrast, midiOutReset() turns off all notes regardless, and is more of a "panic" button type of response to turn off any "stuck notes").
Calling this function when the output is already stopped has no effect, and doesn't return an error.
WARNING! WARNING! WARNING! The Windows Stream Manager appears to be severely broken. Calling midiStreamStop() (or midiStreamPause) does indeed stop playback, but it appears to also close the stream handle that you pass to it. The result is that a subsequent call to another function using that handle results in the Stream Manager returning an error that the handle is no longer valid. The only options you have are:
unsigned long err; err = midiStreamPause(outHandle); if (err) { printf("An error pausing playback!\n"); }If you wish to subsequently resume playback from the point at which it was paused, then call midiStreamRestart(). The stream device's time is not reset to 0, and playback resumes. If instead, you wish to stop the device and flush the queued MIDIEVENTs (perhaps in order to reset its time to 0 and start playback from the beginning of a sequence), then instead call midiStreamStop().
Calling this function when the output is already paused has no effect, and doesn't return an error.
Here I set the Timebase to 96 PPQN (which is the default if you don't specify a Timebase):
unsigned long err; MIDIPROPTIMEDIV prop; prop.cbStruct = sizeof(MIDIPROPTIMEDIV); prop.dwTimeDiv = 96; err = midiStreamProperty(outHandle, (LPBYTE)&prop, MIDIPROP_SET|MIDIPROP_TIMEDIV); if (err) { printf("An error setting the timebase!\n"); }Besides putting Tempo events in the stream in order to set tempo at any given time, you can also call midiStreamProperty() with the MIDIPROP_SET and MIDIPROP_TEMPO flags as the third arg to set tempo. The second arg is a pointer to a MIDIPROPTEMPO structure whose dwTempo field is initialized to your desired Tempo. Note that this is irrelevant when using a SMPTE Timebase.
Here I set the Tempo to 120 BPM (which is the default if you don't specify a Tempo):
unsigned long err; MIDIPROPTEMPO prop; prop.cbStruct = sizeof(MIDIPROPTEMPO); prop.dwTempo = 0x0007A120; err = midiStreamProperty(outHandle, (LPBYTE)&prop, MIDIPROP_SET|MIDIPROP_TEMPO); if (err) { printf("An error setting the tempo!\n"); }Of course, you can query a stream device's current timebase or tempo by using the MIDIPROP_GET flag instead of MIDIPROP_SET. Windows fills in the MIDIPROPTIMEDIV or MIDIPROPTEMPO you pass.
Here I query the current Tempo:
unsigned long err; MIDIPROPTEMPO prop; prop.cbStruct = sizeof(MIDIPROPTEMPO); err = midiStreamProperty(outHandle, (LPBYTE)&prop, MIDIPROP_GET|MIDIPROP_TEMPO); if (err) { printf("An error requesting the tempo!\n"); } else { printf("Tempo = %u\n", prop.dwTempo); }
The latter two methods allow you to better determine what exactly caused Windows to notify you, because they supply additional information to you.
So when does Windows notify you? Here are the times when Windows notifies you:
Here's an example of setting the MEVT_F_CALLBACK flag of an event.
xevt->dwEvent = ((unsigned long)MEVT_LONGMSG<<24) | sizeof(sysEx) | MEVT_F_CALLBACK;
Now, after Windows plays that above event, it notifies you. For example, if you've chosen CALLBACK_FUNCTION method, Windows calls your callback function.
Windows also will notify you when the last event in a MIDIHDR's block of events is played. This is the point at which you will queue the next array of MIDIEVENTs using that same array (and its associated MIDIHDR).
In fact, you could strategically place NOP events with their dwDeltaTime set so that they just happen to fall upon each musical downbeat, and set the MEVT_F_CALLBACK flag of each of those event's dwEvent fields. Then, everytime that your callback is called, you can update a graphical display counting off the beat. (My Stream Callback C example shows that using CALLBACK_FUNCTION method).
Here's an example of using the CALLBACK_EVENT method to play an array of MIDIEVENTs. This is a complete example that shows you all the bare minimum details you need to know to play a stream of MIDIEVENTs.
/* The array of MIDIEVENTs to be output. We only have 2 */ unsigned long myNotes[] = {0, 0, 0x007F3C90, /* A note-on */ 192, 0, 0x00003C90}; /* A note-off. It's the last event in the array */ HANDLE event; HMIDISTRM outHandle; MIDIHDR midiHdr; MIDIPROPTIMEDIV prop; unsigned long err; /* Allocate an Event signal */ if ((event = CreateEvent(0, FALSE, FALSE, 0))) { /* Open default MIDI Out stream device. Tell it to notify via CALLBACK_EVENT and use my created Event */ err = 0; if (!(err = midiStreamOpen(&outHandle, &err, 1, (DWORD)event, 0, CALLBACK_EVENT))) { /* Windows signals me once when the driver is opened. Clear that now */ ResetEvent(event); /* Set the timebase. Here I use 96 PPQN */ prop.cbStruct = sizeof(MIDIPROPTIMEDIV); prop.dwTimeDiv = 96; midiStreamProperty(outHandle, (LPBYTE)&prop, MIDIPROP_SET|MIDIPROP_TIMEDIV); /* If you wanted something other than 120 BPM, here you should also set the tempo */ /* Store pointer to our stream (ie, array) of messages in MIDIHDR */ midiHdr.lpData = (LPBYTE)&myNotes[0]; /* Store its size in the MIDIHDR */ midiHdr.dwBufferLength = midiHdr.dwBytesRecorded = sizeof(myNotes); /* Flags must be set to 0 */ midiHdr.dwFlags = 0; /* Prepare the buffer and MIDIHDR */ err = midiOutPrepareHeader(outHandle, &midiHdr, sizeof(MIDIHDR)); if (!err) { /* Queue the Stream of messages. Output doesn't actually start until we later call midiStreamRestart(). */ err = midiStreamOut(outHandle, &midiHdr, sizeof(MIDIHDR)); if (!err) { /* Start outputting the Stream of messages. This will return immediately as the stream device will time out and output the messages on its own in the background. */ err = midiStreamRestart(outHandle); if (!err) /* Wait for playback to stop. Windows signals me using that Event I created */ WaitForSingleObject(event, INFINITE); } /* Unprepare the buffer and MIDIHDR */ midiOutUnprepareHeader(outHandle, &midiHdr, sizeof(MIDIHDR)); } /* Close the MIDI Stream */ midiStreamClose(outHandle); } /* Free the Event */ CloseHandle(event); }
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(HMIDIOUT handle, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2);As mentioned, you pass a pointer to this function as the 4th arg to midiStreamOpen(). The 5th arg to midiStreamOpen() can be anything you desire, and this will be passed to your callback function each time that Windows calls your callback. Windows calls your function whenever 1 of 4 possible things happen:
NOTE: The dwParam2 arg is not used. This is reserved for future use.
Here's an example of using the CALLBACK_FUNCTION method to play an array of MIDIEVENTs. This is a complete example that shows you all the bare minimum details you need to know to play a stream of MIDIEVENTs.
/* The array of MIDIEVENTs to be output. We only have 2 */ unsigned long myNotes[] = {0, 0, 0x007F3C90, /* A note-on */ 192, 0, 0x00003C90}; /* A note-off. It's the last event in the array */ HANDLE Event; void CALLBACK midiCallback(HMIDIOUT handle, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) { LPMIDIHDR lpMIDIHeader; MIDIEVENT * lpMIDIEvent; /* Determine why Windows called me */ switch (uMsg) { /* Got some event with its MEVT_F_CALLBACK flag set */ case MOM_POSITIONCB: /* Assign address of MIDIHDR to a LPMIDIHDR variable. Makes it easier to access the field that contains the pointer to our block of MIDI events */ lpMIDIHeader = (LPMIDIHDR)dwParam1; /* Get address of the MIDI event that caused this call */ lpMIDIEvent = (MIDIEVENT *)&(lpMIDIHeader->lpData[lpMIDIHeader->dwOffset]); /* Normally, if you had several different types of events with the MEVT_F_CALLBACK flag set, you'd likely now do a switch on the highest byte of the dwEvent field, assuming that you need to do different things for different types of events. */ break; /* The last event in the MIDIHDR has played */ case MOM_DONE: /* Wake up main() */ SetEvent(Event); break; /* Process these messages if you desire */ case MOM_OPEN: case MOM_CLOSE: break; } } int main(int argc, char **argv) { HMIDISTRM outHandle; MIDIHDR midiHdr; MIDIPROPTIMEDIV prop; unsigned long err; /* Allocate an Event signal */ if ((event = CreateEvent(0, FALSE, FALSE, 0))) { /* Open default MIDI Out stream device. Tell it to notify via CALLBACK_EVENT and use my created Event */ err = 0; if (!(err = midiStreamOpen(&outHandle, &err, 1, (DWORD)midiCallback, 0, CALLBACK_FUNCTION))) { /* Set the timebase. Here I use 96 PPQN */ prop.cbStruct = sizeof(MIDIPROPTIMEDIV); prop.dwTimeDiv = 96; midiStreamProperty(outHandle, (LPBYTE)&prop, MIDIPROP_SET|MIDIPROP_TIMEDIV); /* If you wanted something other than 120 BPM, here you should also set the tempo */ /* Store pointer to our stream (ie, array) of messages in MIDIHDR */ midiHdr.lpData = (LPBYTE)&myNotes[0]; /* Store its size in the MIDIHDR */ midiHdr.dwBufferLength = midiHdr.dwBytesRecorded = sizeof(myNotes); /* Flags must be set to 0 */ midiHdr.dwFlags = 0; /* Prepare the buffer and MIDIHDR */ err = midiOutPrepareHeader(outHandle, &midiHdr, sizeof(MIDIHDR)); if (!err) { /* Queue the Stream of messages. Output doesn't actually start until we later call midiStreamRestart(). */ err = midiStreamOut(outHandle, &midiHdr, sizeof(MIDIHDR)); if (!err) { /* Start outputting the Stream of messages. This will return immediately as the stream device will time out and output the messages on its own in the background. */ err = midiStreamRestart(outHandle); if (!err) /* Wait for playback to stop. Windows calls my callback, which will set this signal */ WaitForSingleObject(event, INFINITE); } /* Unprepare the buffer and MIDIHDR */ midiOutUnprepareHeader(outHandle, &midiHdr, sizeof(MIDIHDR)); } /* Close the MIDI Stream */ midiStreamClose(outHandle); } /* Free the Event */ CloseHandle(event); } return(0); }NOTE: If you happen to have two or more events that occur upon the same beat (or close enough such that Windows doesn't even have time to call your callback once before both events time out), and both their MEVT_F_CALLBACK flags are set, then Windows only calls your callback once for all of those events. The dwOffset of the MIDIHDR would reference the last such event. Therefore, don't bother setting the MEVT_F_CALLBACK flag of more than one event that occurs upon a given time.
Furthermore, while Windows calls your callback, it continues timing out the next event in the background. If it happens that the next event happens to have its MEVT_F_CALLBACK flag also set, and it times out while your callback is still processing that previous MEVT_F_CALLBACK event, then Windows will call your callback again. In other words, if you're not sure that you've placed enough time inbetween each of your MEVT_F_CALLBACK flagged events in order to do what you need to get done in your callback, then you had better make sure your callback handles reentrancy properly. (ie, Don't use global variables whose value changes, or use some sort of mechanism to arbitrate access to those variables such as a Mutex). For practical purposes, you should avoid setting the MEVT_F_CALLBACK flag of an event whose time makes it occur less than 25 milliseconds after the previous event with its MEVT_F_CALLBACK flag set. Microsoft recommends the following approach to handling a situation where MEVT_F_CALLBACK events are timing out quicker than your callback can finish its work: "The callback should use the MIDIHDR's dwOffset to decide what to do. If it is expecting this to reference a particular location (ie, event) but it instead references a location somewhere further along in the array of MIDIEVENTs, it means that your callback is not keeping up or that the MEVT_F_CALLBACK flagged events are scheduled too closely together for the capabilities of the computer. The best thing to do in this case is to just throw the callback away because there will be another one in the queue that will be delivered just as soon as processing of the current callback is completed."
Before calling midiStreamPosition(), you set the wType field of the MMTIME structure to indicate the time format you desire returned. After calling midiStreamPosition(), you should check the wType field. Some stream devices may not support returning certain time formats, for example, a SMPTE format. In that case, midiStreamPosition() will set the wType field to the closest supported format, and return that time.
The allowable time formats (ie, for the wType field) are:
TIME_BYTES Current byte offset from beginning of the playback. TIME_MIDI MIDI time (ie, MIDI Song Position Pointer). TIME_MS Time in milliseconds. TIME_SAMPLES Number of waveform-audio samples (for syncing to WAVE playback). TIME_SMPTE SMPTE time. TIME_TICKS PPQN clocks.Here I query the current Time. (Note that the Time is set to 0 when playback starts, except for a paused playback that is resumed).
unsigned long err; MMTIME time; /* Request millisecond time returned */ time.wType = TIME_MS; err = midiStreamPosition(outHandle, &time, sizeof(MMTIME)); if (err) { printf("An error requesting the time!\n"); } else switch (time.wType) { case TIME_BYTES: printf("%u bytes have been played so far.\n", time.cb); break; case TIME_MIDI: printf("Midi Song Position Pointer = %u\n", time.midi.songptrpos); break; case TIME_MS: printf("Millisecond Time = %u\n", time.ms); break; case TIME_SAMPLES: printf("%u digital audio samples have been played so far.\n", time.sample); break; case TIME_SMPTE: printf("SMPTE Time (%u fps) = %u:%u:%u:%u\n", time.smpte.fps, time.smpte.hour, time.smpte.min, time.smpte.sec, time.smpte.frame); break; case TIME_TICKS: printf("PPQN Clocks = %u\n", time.ticks); }