Tuesday, December 04, 2007

Midi Device Schema Language - part II

After more than a year of doing other things I picked up the Midi Device Schema Language project again. This post describes the results I got so far.

A short recap: I previously wrote about how I used Xml Schema to describe the content and structure of a Midi System Exclusive Message (SysEx). By describing the structure of the SysEx message allows any Midi application to interpret the content of that message in a logical way. The language describes the logical content of the message using data types that represent the physical structure. This means that for instance a patch name that is the SysEx message is one logical element in the Device Schema (perhaps called PatchName) and is declared with a data type that indicates how many bytes are part of the text string. Or a logical field can be a Boolean that is taken from a specific bit-position in a data byte and multiple logical Boolean's can draw from the same data byte.

The following schema describes test data that I use for unit testing. The Schema declares a simple (Data) type that defines a ProductName of 5 characters long. A complex (Record) type defines the SysEx message from start (F0) to end (F7).

<xs:schema id="TestSchema" xmlns="..." xmlns:m="..." >
<xs:import namespace="urn:midi-device-definition:midiTypes" schemaLocation="MidiTypes.xsd" />
<xs:complexType name="deviceSchemaTest">
<xs:sequence>
<xs:element name="SOX" type="m:midiSysEx" fixed="240" />
<xs:element name="SysExData" type="m:midiSysExData" />
<xs:element name="Bit0" type="m:midiBit0" />
<xs:element name="Bit1" type="m:midiBit1" />
<xs:element name="Bit2" type="m:midiBit2" />
<xs:element name="Bit3" type="m:midiBit3" />
<xs:element name="Bit4" type="m:midiBit4" />
<xs:element name="Bit5" type="m:midiBit5" />
<xs:element name="Bit6" type="m:midiBit6" />
<xs:element name="LowNibble" type="m:midiLSN" />
<xs:element name="HiNibble" type="m:midiMSN" />
<xs:element name="Name" type="ProductName" />
<xs:element name="EOX" type="m:midiSysEx" fixed="247" />
</xs:sequence>
</xs:complexType>
<xs:simpleType name="ProductName">
<xs:restriction base="m:midiString">
<xs:length value="5" />
</xs:restriction>
</xs:simpleType>
</xs:schema>

The fields inside the SysEx message range from single bit values to a multiple bytes value.
The SOX field has a fixed value F0 (240 decimal) and marks the start of the SysEx message.
The SysExData field is a 7-bit SysEx data byte. The data of a SysEx message cannot have its highest bit set (bit 7) as specified in the Midi specifications.
The Bit0 to Bit6 fields all represent one bit of one data byte. Notice Bit7 is not in there.
Then the Low and High Nibble fields each represent 4 bits of data. The Most Significant Nibble (MSN) data type only has 3 bits. The highest bit (bit 7) is always cleared.
Then the Name field contains the product name text string of 5 characters.
Finally the EOX has a fixed value F7 (247) and marks the end of the SysEx message.

When this Schema is used to read this binary test SysEx message (hex):
F0 41 70 0F 4D 49 44 49 31 F7
the following Xml is generated*:

<deviceSchemaTest>
<SOX>240</SOX>
<SysExData>65</SysExData>
<Bit0>False</Bit0>
<Bit1>False</Bit1>
<Bit2>False</Bit2>
<Bit3>False</Bit3>
<Bit4>True</Bit4>
<Bit5>True</Bit5>
<Bit6>True</Bit6>
<LowNibble>15</LowNibbel>
<HiNibble>0</HiNibble>
<Name>MIDI1</Name>
<EOX>247</EOX>
</deviceSchemaTest>

*) Note that the framework does not generate this Xml. It is an application specific interpretation of the logical data the framework publishes. Technically an implementation of the IMidiLogicalWriter interface decides what happens with/to this logical data. The framework calls data typed methods on this interface to publish logical data.

As you compare the binary input and the logical output (Xml in this case) you clearly see that the framework is capable of mapping one physical byte to multiple logical fields (Bit0-Bit6, Low-HiNibble) and mapping multiple physical bytes to one logical field (ProductName).

The knowledge of how to map physical bytes to logical fields (and visa versa) is contained in a Converter. There is a Converter for each Data Type in the language. But we did not have to write a Converter for the ProductName we declared ourself. Because it derives from midiString it uses that Converter (StringConverter). But if we wanted to, we could have registered a custom Converter for that data type, allowing you to handle that case in a special way. The StringConverter looks for a length constraint to know how many characters to read or write to the physical data stream (midiString does not define the length constraint).

To read a binary SysEx message you have to specify the Record Type (deviceSchemaTest) from a Device Schema that you will be reading*. Then the framework will analyze the Fields for the Record Type and the Data Types used by those fields. For each field a Converter is matched and then reading of the binary stream can begin. Each Converter reads the bytes from the binary stream as it sees fit. The BitConverter (Bit0-Bit6) uses BitFlags to indicate which bit it wants to read. The stream position is only advanced to a new data byte when the requested bit(s) are no longer available in the Carry - a one byte data cache.

*) One of the problems that I still need to resolve is how to automatically determine the Schema from a binary data stream. One way to solve this might be to look for specific markers inside the SysEx message content. For example Roland specifies a Command ID in their SysEx messages to indicate what type of message you are dealing with. Other manufacturers may not leave any unique clues to determine the message type.