Now we start to get complicated. Before we can move on from "Hello (MIDI) World," however, we need to understand a little more about what we are dealing with.
Turn the Way-Back Machine to the early 80's and the rise of synthesizer-based music. Yamaha was riding high on their flagship DX-series FM synthesizers, and Roland was moving in fast with LAS -- the first of the true sample-manipulation synthesizers that now dominate the virtual instrument market. And over several meetings, the leading electronic instrument manufacturers got together to talk about integration; about a single standard to allow machines to communicate with each other.
Dave Smith was the pioneer here. He introduced his fledgling "Musical Instrument Digital Interface" at AES in 1981, and by the 1983 NAMM show he was able to demonstrate a connection between one of his own Prophet synths and a Roland JP-5.
MIDI came out of the speculative academic environment of experimental music; out of a background that was re-thinking the shape of interfaces (such as Don Buchla's unique controllers), the role of the synthesizer in music (from Wendy Carlos to Kraftwerk), even the role of the musician (Brian Eno building on the work of John Cage and others). This heady experimental atmosphere led, I believe, to the construct of MIDI as an open-ended and extensible language.
It would have been so easy to make MIDI dogmatic; to restrict it, for instance, to only describing events within the beat and tempo parlance of Western music. (And, yes; working with microtonal music is not quite as transparent as it could be in MIDI!) But, still, the very simplicity of what the language provides allows it to flexibly encompass all manner of events that might not otherwise be encompassed.
MIDI does not describe sound. It describes events. Although there are additions to the language that can specify timbres more closely, or even load in samples, interpretation of the events is still up to the machine receiving the MIDI message. It is sheet music for computers.
This also makes it extremely flexible for other uses. In my current MIDI-controlled servo for instance, noteOn is interpreted as a command to move, to a position specified by the note number, and at a slew rate specified by the velocity.
The MIDI specification -- as it has been amended and expanded over the years by the MIDI consortium while still maintaining full backwards compatibility -- includes as do most connection protocols a hardware layer and a software layer.
The heart of the traditional MIDI hardware is the opto-isolator. MIDI gear is designed so the OUT port of one piece of equipment drives an opto-isolator behind the IN or THRU port of the next piece of gear. An opto-isolator is an LED stuck in a black box with a photo-transistor. This isolation means there can never be a ground loop between pieces of MIDI gear*, and any voltage spike will blow out a $2 part instead of the whole synthesizer.
*technically. Standard practice is to ground the shield/pin 2 at the transmitter side, with the other end of the cable ground left unconnected, but not all manufacturers follow suit.
Before you make any electronic connection to a piece of MIDI gear, remember this; the IN port is expecting the voltage to drive an LED (5v TTL; it is expecting 3-5 volts for a "1" and 0-2 volts for a "0.") The OUT port is expecting to see the load of a a small LED. So don't connect a motor to a MIDI OUT port and expect it to run for very long before something breaks! (According to the spec, the current in the loop should be 5 mA. Most gear is capable of sinking somewhat more than that.)
The only two pins connected on the 5-pin DIN connector (a connector type standardized by the Deutsches Institut für Normung) are pins 4 and 5. Polarity matters, because like all LEDs the opto-isolator is one-way.
The rest of the physical layer is, and I paraphrase Wikipedia on this: A simplex digital current loop electrical connection sending asynchronous serial communication data at 31,250 bits per second. 8-N-1 format, i.e. one start bit (must be 0), eight data bits, no parity bit and one stop bit (must be 1).
Actually, the take-home here, besides the fact that communication is simplex and asynchronous (aka one-way, with no shared clock signal or other handshake needed), is that the normal state of the system is digital 1 -- which corresponds to no current flow.
When the system encounters current it calls that a start bit (the 0), and then parses an 8-bit word starting with the first edge it encounters. Technically, you could sleep the transmitter for quite some time between words, as each byte sent is uniquely interpreted.
As it happens, 8-N-1 is the default of the built-in UART on the AVR at the heart of the Arduino, therefor the serial out on the Arduino naturally sends the correctly formatted signal. All that is necessary to get an Arduino to send a bitstream that will be interpreted as a potential MIDI message is to set the BAUD rate to 31250 (which is easily done within the Arduino IDE via the "Serial.begin(31250);" init.)
On the receiver side, it is technically possible to connect the Arduino's serial port, but it is strongly not recommended. An opto-isolator will provide a lower current drain for the transmitter and protect the Arduino as well. Plus the physical layer assumes you are inverting anyhow. I'll get into how to wire the opto in a bit here.
Because of the simplicity of asynchronous simplex it is entirely possible to bitbang an acceptable MIDI message on an AVR without a built-in UART. That's basically what Software Serial does anyhow. I'll be documenting my experiments in bitbanging MIDI from cheap through-hole AVRs in the future, but for now just ignore all that stuff about framing bits and parity and just think of MIDI messages as a series of ordinary 8-bit words.
8 bits are a byte. 8 bits are enough to do simple ASCII (not expanded ASCII). It also works out to 2 characters in hexadecimal. And reading "0Fh" is a lot easier than reading "00001111b." For technical and historical reasons MIDI is generally documented in the terms of hex pairs. If you are going to get down and dirty with MIDI commands, you have to get used to switching back and forth between hex, decimal in 0-9 and 1-10 formats, and a bit of binary now and then.
The format of almost all MIDI messages is "Opcode Data (Data) (Data)...."
In the case of the ubiquitous NoteOn message, this works out as;
First byte; "NoteOn for channel 15"
Second byte: "for the F# above Middle C"
Third byte: "with a velocity of 127."
Messages come in all different lengths. The System Exclusive message, for instance, can be multiple megabytes of data -- as long as it is bracketed by "System Exclusive Begin" and "System Exclusive Ends" (which includes a check-sum).
System Exclusive, by the way, is one of the open message formats that made MIDI so readily expandable. (Another major one was NRPNs).
Just a reminder here. When we are working with a nice friendly platform like the Arduino, what that noteOn message above translates as is three consecutive one-byte integers. Think of it as;
Serial.print(159, BYTE);
Serial.print(66, BYTE);
Serial.print(127, BYTE);
And, yes, you can do exactly this and it will be interpreted as a legitimate MIDI message. The only reason to play around with variables is to give yourself the flexibility to actually compose useful messages.
MIDI is a channeled system. Although some of the opcodes are global (meaning they are addressed to all devices on the network), most messages are prefixed with the channel number. This allows multi-timbral performance off a single MIDI stream; channel 10 might be handling drum sounds, channel 1 a piano sound, channel 2 a bass, etc.
In the example code I gave in Part I, I arrived at the correct channel-designated opcode by adding the raw opcode (144 for noteOn) to the channel number. A different way of looking at this, however, is that the 16 possible channels are expressed using the last four bits of the first byte sent. The top four bits are the opcode.
Thus, a noteOn for channel 15 is 9h, Fh. Or 10011111 in binary -- 159 in decimal.
This is why thinking in hex pairs can simplify your work when you are dealing with channel messages; just remember the channel is the low nibble, and the opcode is the high nibble.
In the case of data bytes, the high bit is unused. This limits the possible values to 0-127.
Here's the opcodes in binary format as listed by the MIDI Consortium: MIDI Messages As I said, if you are going to work with MIDI in the raw form, expect to be going back and forth between hex, binary, and decimal a bunch!
Here would be a nice place to point out that not all devices interpret (or send) all MIDI messages. If you have your hands on a manual, there will be a couple of sheets in the back, around the index, that specify what messages are used by that device.
So now that you know how to make a connection to the physical layer, and you've seen how to format a message in the software layer, it should be fairly straightforward to write software routines that send useful signals.
In one of my recent projects I needed to compose just a simple "Go" command in MSC (MIDI Show Control, an expansion of the basic MIDI 1.0 spec for use in theatrical applications). Instead of writing something elegant in software I just wrote a stack of Serial.print commands, each containing the actual binary needed;
Serial.print(10001110b);
etc.
For the four-button remote control MIDI device I was using for a time, the note number was determined by a scan of the open buttons, using a simple matrix;
for (button = 0; button<5; button++)
{
pressed[button] = digitalRead(inp[button]);
if (pressed[button] == HIGH)
{
noteOn(channel, button + 60, velocity);
}
}
That's a bit pseudo-code up there; don't use it as is.
Another necessary trick was polyphonics. What I did, was add a flag variable; sounding[button]. Whenever a noteOn was sent, I'd set the flag to know I'd already sent a note event for that button, rather than re-sending every time the loop polled the button status.
And that's long enough for this entry, except I want to make note of another peculiarity of MIDI messages. And that is Running Status.
Running status means that if you have sent the opcode for a noteOn event, you can follow it with more than one data pair and they will be interpreted as additional notes. This is extremely useful for continuous controller messages, which otherwise would be rather longer.
The format here is;
Opcode (noteOn channel n)
status1 (note)
status2 (velocity)
status1 (another note)
status2 (the velocity for that second note)
etc., etc.
The receiving device will continue to accept these as noteOn events until it sees a new opcode.
And now it becomes clear why all opcodes have a leading "1," whereas all data bytes have a leading "0"!
No comments:
Post a Comment