Information for MIDI driver and DirectMusic application developers


This article describes the problems with system exclusive and DirectMusic and offers solutions to the developers of drivers for MIDI hardware and to developers of applications that make use of the DirectMusic technology. The article is targeted at people that are already familiar with either MIDI driver development or programming against the DirectMusic API. It does not offer an indepth description of these technologies.



Description of the problem

An application developer on the Windows platform that needs to work with MIDI can choose between two API's that offer this functionality. There is the Multi Media Extensions API (MME) or the newer DirectMusic API.
A developer who chooses the DirectMusic API may find himself confronted with a peculiarity of the DirectMusic system if his application needs to receive a certain type of MIDI message called system exclusive. System exclusive messages are an extension to the MIDI protocol, allowing manufactures of MIDI hardware to define their own MIDI messages. Within certain boundaries manufactures are free to construct these messages as they please. One of the consequences of this is that system exclusive messages can be any size, from a few bytes to several thousands of bytes.

The problem starts when an application wants to receive a system exclusive message larger than a few hundred bytes. The application will most likely have set up a separate thread in which it waits for a notification event to become signaled whenever MIDI data arrives. The event handle has been passed to the input port through a call to IDirectMusicPort::SetReadNotificationHandle().
Because the system exclusive message is large it will not be received as a whole. The total system exclusive message will be split up in smaller packets. These packets are handed down to the user mode application one by one. The maximum observed size of these packets is 44 bytes. This makes sense as it is the size of a 64 bytes buffer minus the size of a DMUS_EVENTHEADER (64 - 20 = 44). The actual data bytes of a DirectMusic event follow the event header immediately.

The big problem is that at some point the packet order will become mixed up. Ideally one would expect the packets to be received in the order in which they were sent. Unfortunately this is not the case. Whether this is a bug or if it was designed this way is not clear to me. Fact is that it makes life hard for the developer. The only clue he has to the original packet order is the timestamp of each packet. He can thus try to piece together the original message by comparing the timestamp of each packet and putting them in chronological order.
But here is where disaster really strikes. Most of the times the timestamps of the packets are identical or, even worse, false! False in this sense meaning that the timestamp indicates a packet A to have been received earlier than packet B while it in fact contains data that comes after the data in packet B (in the original system exclusive message).
In this situation the developer has no means to find out the true order of the data packets, unless he knows what the total message must be. Because of the random nature of system exclusive messages the latter option can be discarded as fantasy.

It was this problem that I found myself confronted with while I was working on a driver for a certain piece of MIDI hardware called the Midi Translator PC. I had written a little utility to test the proper functioning of the driver. It worked simply by sending MIDI messages to a MIDI output port that was connected via a MIDI-cable to an MIDI input port. The application would read the incoming data from the input and compare them to what it had sent out. If you want to try this utility yourself, it can be downloaded for free here.
Because I wanted the driver to be DirectMusic capable I used both the MME as well as the DirectMusic API to test. It was thus that I discovered that the same driver would have no problems transferring system exclusive messages using the MME API but refused to send messages larger than a few hundred bytes with the DirectMusic API.
Not only did it show this behaviour with my own driver, but with every other driver/MIDI hardware combination that I tested it with. Very suspicious indeed.
Yet I still wanted the driver to be DirectMusic capable. The reason for that was one of the new features that DirectMusic offered: the timestamping of MIDI input data. A DirectMusic capable driver can timestamp the data it receives, which in theory increases the timing accuracy of any user mode application using it. Because drivers work at a higher priority level in the system, the chance of other processes disturbing the timing accuracy diminishes.

Fortunately, after many hours of experimentation, I was able to modify my driver in such a way that large system exclusive transfers were again possible, even with the DirectMusic API. And much later I discovered I could solve the system exclusive problem at the application level as well.
Because I think that DirectMusic has some benefits over using the MME API and I feel that system exclusive is an important aspect of the MIDI protocol, I want to share my findings here with other developers so they can learn from it and hopefully this will boost the usuage of DirectMusic in professional and semi-professional MIDI/audio applications.

To sum up the situation; DirectMusic adds a new feature to MIDI drivers, namely the ability to timestamp MIDI data as it is received.This capability is desirable for (semi-)professional MIDI/audio applications because it improves timing accuracy. At the same time the problems with system-exclusive make it unsuitable for professional use as many professionals will require applications to communicate with outboard equipment through system exclusive messages.



DirectMusic capable MIDI drivers

Like probably all other device driver developers I used a DDK sample driver as a basis for my own driver. To be precise, my first driver version was based on the IMiniportMidi class. I believe the sample was called midiuart. Later I built a version based on IMiniportDMus, because I wanted the DirectMusic capabilities and also because Micorsoft advised device driver writers to use this class instead of the older IMiniportMidi. The IMiniportDMus based sample driver is called dmusuart.
The problem is that if you follow this DDK example your driver will automatically inherit the system exclusive peculiarity at no extra cost.
The sample driver shows how the MIDI data can be sent down to the system. Data is packaged into DMUS_KERNEL_EVENT structures which are retrieved from an allocator and sent down to an IMXF stream. In the sample driver MIDI data is sent one byte at a time and each package has a flag set to the value DMUS_KEF_PACKET_INCOMPLETE. According to the DDK documentation this approach is perfectly alright. The systems receives the incomplete packages and constructs complete MIDI messages out of them, which are then passed on to user mode applications.

However, there is one, not exactly documented, disadvantage to this approach. One question I asked myself is where does the final MIDI message gets its timestamp from? Each DMUS_KERNEL_EVENT you send down the IMXF stream has its own timestamp. Suppose we have a MIDI message of three bytes. One would expect that the final MIDI message has either the timestamp of the first or the last data byte. Or maybe the timestamp of the middle byte or the average of the first and the last. A few conceivable methods come to mind.
Through experimentation I found out that the final MIDI message gets the timestamp of the first data byte, or at least a timestamp that comes very close to this. For some reason somewhere along the line a small amount is subtracted from the timestamp. Not a constant value but something around 700 100-nanosecond units. I don't really know why this is.
In at least one case it would be desirable to have more control over how data is timestamped. That is when you are sending down system exclusive messages. If you send them as raw data (DMUS_KEF_PACKET_INCOMPLETE) all packets will get the timestamp of the first data byte. No problem if the packet order is unchanged but as we noticed this becomes unreliable if messages consist of many packets.

The way to go therefore is to only send complete messages to the IMXF stream. You can do this by setting the DMUS_KERNEL_EVENT flag to DMUS_KEF_PACKET_COMPLETE. Now every packet will have it's own timestamp when it is received by the user mode application. The timestamp is still decremented by a small amount but at least the packets can be discerned from each other.
The disadvantage of this approach is that now the driver needs to interpret the MIDI data stream. It needs to know the MIDI protocol and know which types of messages have how many bytes and such. This makes the driver more complex. Yet I still chose to use this approach because by taking one more step it also seemed to solve the system exclusive troubles.
This sounds easier than it actually was. I first had to find out how many bytes I should put in one system exclusive packet. With much trial and error I found out that it only worked if I created packets of exactly 44 bytes. Why 44? Because that is the maximum size of the packets a user mode application will receive. Any other packet size would lead to repackaging somewhere along the line which in all cases would lead to packets being swapped and data corruption as a result. So by offering the system exclusive messages in packets that could be immediately passed on, without the need to repack them, the system seemed just to be able to handle the data stream.

So to summarize: drivers should only send complete MIDI messages to the system and therefore interpret the MIDI stream as it is received from the hardware. A system exclusive message must be split into packets of 44 bytes each. In most cases that will leave one final packet containing the remaining bytes which can have a size from 1 to 43 bytes. If the packet to be sent has a size larger than sizeof(PBYTE) (which is 4 on a 32 bit operating system) you must allocate a separate buffer for the data by calling IAllocatorMXF::GetBuffer(). See the DDK documentation for more information.
The driver must always set the DMUS_KERNEL_EVENT flag to DMUS_KEF_PACKET_COMPLETE, even if more packets of the same system exclusive message follow.



DirectMusic applications

Very recently I discovered that it is possible to resolve the system exclusive problems at the application level as well. It is possible to access a DirectMusic capable MIDI driver via a different access method, bypassing the DirectMusic user mode component completely and thereby getting rid of the packet order corruption mentioned previously.
This access technique is called Direct Kernel Streaming. It is being used by some audio applications (like Sonar) to access soundcard drivers. This is done to circumvent the inherent latency that comes with the DirectSound architecture. By using kernel streaming, audio applications are able to achieve much lower latencies.
An excellent code sample explaining this technique was posted by Microsoft's Martin Puryear on the WDMAudioDev mailing list. Download the sample here. The sample is a bit hard to follow due to the rigorous object oriented structure, but all the necessary information is there.

Few people probably realized that this same technique also works for MIDI pins. At least it works for pins that support KSDATAFORMAT_SUBTYPE_DIRECTMUSIC. The benefits of using Direct Kernel Streaming are: slightly faster access, no problems with large system exclusive messages and leaving out a lot of DirectMusic stuff that is probably only interesting for game developers anyway. Also it makes it possible to receive the actual timestamp of a MIDI message as it was handed out by the driver. Apparently the user mode portion of DirectMusic is responsible for the perceived timestamp meddling (the small amount subtracted from it). In bypassing it you can have access to the pure timestamp.

I implemented Direct Kernel Streaming in the latest version of my test utility MidiTest. The application now has a check box named 'Direct mode' which enables Direct Kernel Streaming if checked. The device driver of your MIDI hardware must be DirectMusic capable, otherwise it will not even show up in the list of devices to choose from.

The fact that the system exclusive problems disappear completely when bypassing the user mode portion of DirectMusic proves to me conclusively that the cause can be found right there. The user mode portion of DirectMusic is implemented in dmusic.dll. Although various versions of this dll exist, depending on the DirectX version, the problem is present in all and has been there since the first version.
I have reasons to believe that the problem is time related. I did an experiment where I sent a large system exclusive message in one go, as a chain of packets all containing 44 bytes of data. Surprisingly the problem reappeared, even though I had made the packets the right size. Apparently the problem can occur if several packets arrive at the same time. If packets don't have the right size they will be repackaged, thereby increasing the chance of two packets arriving together.
The reason the 44 byte trick normally works is the relatively slow speed of MIDI transmission (roughly three bytes per millisecond). By the time a new packet has been received and assembled almost 15 milliseconds have passed. Sufficient time for the system to have passed the previous packet to the user mode application.
This could also mean that the 44 byte trick will not work in all situations. It may sometimes be necessary to add a short delay between sending two packets. No more than 15 milliseconds will do, so it seems.

Why then has dmusic.dll problems with keeping packets in the right order? What method does dmusic.dll use to receive MIDI data? The answer to the last question is simple: dmusic.dll uses Direct Kernel Streaming. The answer to the first question I don't know.
But that doesn't matter because we can do it better ourselves. By using kernel streaming to read the MIDI data we don't need dmusic.dll anymore. My utility MidiTest shows that it is perfectly possible to do it properly.

At this moment the only drawback I can see to Direct Kernel Streaming is that it only works with pins that support the DirectMusic format (KSDATAFORMAT_SUBTYPE_DIRECTMUSIC). There must be a way to read the older MIDI pins (KSDATAFORMAT_SUBTYPE_MIDI) as well, because dmusic.dll already does this. These older MIDI pins appear as emulated MIDI ports to the DirectMusic API. I just have not found out how to do it yet.
It might not even be necessary because an application could use MME for older hardware and kernel streaming for newer.



Conclusion

Even though Direct Kernel Streaming can solve the system exclusive problems at the application level, I still think that the driver modifications as I propose them can serve a purpose. It makes hardware manufacturers less dependant on software developers using the right technology (kernel streaming). Also I have the impression that complete messages travel faster through the operating system part of the driver, because there is no need to interpret them. It would be nice if a developer could trust that the OS handled the raw data efficiently and accurately, but at this moment I am not too sure about that.
I encourage software developers to start using Direct Kernel Streaming for MIDI applications. It provides the benefits of DirectMusic (timstamping), but does not suffer from the system exclusive problems DirectMusic has.


Evert van der Poll
CEO of Earth Vega Connection

http://www.earthvegaconnection.com
mailto:evert@evc-soft.nl






Created:     September 22nd, 2005.
Modified:     September 27th, 2005.