Marv’s brain consists of three ATMEGA16 microcontrollers running programs written in C designed specifically to run the hardware associated with actuating the vibraphone keys. Since the MIDI protocol has already been extensively developed we decided it would be the best way for the user to interface with Marv. The programs have been written to respond to streams of MIDI information by sending logic signals to the microcontroller’s I/O pins. All striker and damper circuitry react to these logical changes on the I/O pins.

The MIDI Protocol

MIDI stands for Musical Instrument Digital Interface. Many different instruments can either send or receive MIDI information and act in many ways. The MIDI protocol is just a way to communicate musical information digitally. On its own, MIDI does not have any sound, rather it is just a bit stream of digital information and because of this it can be transferred remarkably fast (31.25 kbits/sec). All MIDI information is transferred in a series of three bytes. These three bytes contain all that is needed to describe a specific musical function. An example of this kind of function would be: Turn On Note C#4 with a Velocity (intensity) of 100. Each byte in the series of three explains one part of the function. The first byte is the Status Byte, which indicates what kind of message is being sent. In the example, “Note On” is the Status Byte. Other kinds of Status Bytes are Note Off, Aftertouch On, or Sustain Pedal On, to name a few. The next two bytes are the Data Bytes. The byte immediately following the Status Byte contains the Note Name information. The Note Name in the example is C#4. The second Data Byte is the Velocity byte. This MIDI message contains information about how hard the note was played, which usually corresponds with the note’s intensity or loudness. One generic MIDI message is shown in the figure below.


The Status Byte is an 8-bit number and the most significant bit is always one. The high nibble tells what kind of message is being sent and the low nibble signifies which of the 16 MIDI channels the message is intended for. The two Data Bytes are also 8-bit numbers as well, but only 7 of the bits are used. Since the MSB of the Data Bytes is always zero, it is easy to quickly distinguish between a Status or Data Byte upon receiving a MIDI message.

In Marv’s case, we only care about two kinds of MIDI messages: “Note On” and “Note Off.” In MIDI, a Note On starts a note playing which continues to play until a Note Off is received. The Status Byte of a Note On ranges from 0x80 to 0x8F. The 0 in the low nibble corresponds to MIDI Channel 1 and the F corresponds to Channel 16. In the same way, the Note Off Status Byte ranges from 0x90 to 0x9F. Alternatively, many programmers choose another way to indicate a Note Off status by sending a Note On Status Byte + a Note Name Byte + a Velocity Byte of 0x00. For example, to turn off the note F3 (on MIDI Channel 1) we would send the bytes 0x80, 0x35, 0x00. This is equivalent to 0x90, 0x35, 0xFF. (In the Note Off case, the Velocity Byte has no meaning.)

Strikers and Dampers

As described above, Marv needs only to respond to the MIDI messages of “Note On” and “Note Off.” In response, Marv needs to run a sequence of events to either make the note sound or to quiet a note that is currently ringing. The code is written to change the voltage on the micrcontroller’s I/O pins and we assume there is one I/O pin per striker or damper and a logic high on the pin indicates that the striker or damper is active and a logic low indicates the striker or damper is inactive. Each ATMEGA16 has 32 I/O pins, of which 2 are used for serial USART communication, which is how the MIDI signals are transferred. Each chip should be able to control 13 strikers and 13 dampers which leaves 4 I/O pins on each chip available for other functions. Since the Vibraphone has 37 keys, a total of three ATMEGA16 microcontrollers are used for the project.

Solenoids are used as the striker and dampers, but act in different ways to achieve their specific function. The striker solenoid is such that it needs to receive a very short electrical pulse to provide the force needed to accelerate the plunger enough to strike the key. If the applied pulse is too short the plunger does not accelerate enough to strike the key and no sound is heard from the instrument. On the other hand, if the applied pulse is too long then the plunger holds against the key for too long and actually begins to dampen the key. The difference between these two pulse lengths is the working range of the striker solenoid. An added benefit is that longer working pulses are louder than shorter working pulses. This fact is exploited and is the way the dynamic range of the music is preserved in Marv’s performances. All dynamics in MIDI are encoded in the Velocity Data Byte and this information is converted to specific pulse lengths in the code to achieve different volumes. During calibration of the striker solenoids we determined that the working range is 1.2ms – 5.0ms (with 48V across the solenoid). This means that a Note On with Velocity 1 will apply a 1.2ms pulse to the solenoid and a Note On with Velocity 127 will apply a 5.0ms pulse.

The damper is much simpler in nature than the striker. It is normally pressed to the key by a spring and its solenoid operates in reverse when compared to the striker solenoid. Upon receiving a Note On message Marv first needs to release the damper from the key to be played. This ensures that the note will ring as loud as possible when the striker solenoid strikes it. The damper should be released from the key for as long as the user wishes to hold the note and should re-engage when the Note Off message is received. Since the damper only toggles in response to the On/Off messages, no predetermined pulse length need be applied.

The Code

MIDI messages can be sent to Marv at any time and Marv must be able to properly identify the message and respond accordingly within a time span that is imperceptible to the human listener. The fastest clock included internally in the ATMEGA16 package is 8MHz and this rate determines much of the structural format of the code. 8MHz is not terribly fast for a microcontroller and much of the processing code in turn takes a significant length of time to complete. This fact led us to an interrupt driven format of the code. Since so much MIDI data may be sent to the microcontroller in a short period of time we have created an interrupt routine that runs whenever a byte is received in the chip’s serial USART input pin. This interrupt routine only scans the USART input and stores the byte into a temporary storage element. The storage of these bytes is very important since without it Marv would not know when to a play a key or which key or what! As this is done in an interrupt routine we need to ensure that nothing will conflict with it. Keeping the interrupt lean and mean will do that, which is why the input byte is only stored in RAM and no processing is done on it yet. Once the byte is stored locally on the microcontroller Marv can process this byte slowly if needed since this is done outside the interrupt and inside the main loop.

Most of the processing is done in the main loop of the code, but another interrupt is included for the striker and is explained below. The processing loop is meant to scan for a Note On or Note Off and respond appropriately if these messages are detected. Since all MIDI messages come in the series of three bytes described above, Marv can scan for the Note On or Note Off Status Byte and wait for the following Data Bytes to determine which key to activate and how long the pulse length should be for the striker. When a Note On is detected Marv should 1) release the damper and 2) apply a pulse to the striker with a length dictated by the Velocity Data Byte. The damper is released by sending a logic high voltage directly to the specific I/O pin connected to the damper circuit for the particular key indicated in the Note Name Data Byte. The striker pulse is implemented with another interrupt routine that runs regularly at specific intervals. This routine checks an array that describes the state of all the strikers. The array contains a number for each striker corresponding to how many intervals the striker should remain active. If the array contains a zero then the striker should be off and a logic low is sent to the striker’s I/O pin. If the array contains a non-zero number then a logic high is sent to the I/O pin and the number is decremented. The value that is entered into the array is the pulse length divided by the interval length and this is set by the Velocity information. A specific equation converts the velocity byte (an integer from 1 – 127) to the number of intervals. During calibration, we found the shortest interval that the striker interrupt routine could run at without problems was 128us. This is the also the resolution of our dynamic range since volume is controlled by the pulse length which can only be an even multiple of 128us. The Note Off case is very simple and Marv only re-engages the damper solenoid by directly sending a logic low signal to the damper’s I/O pin.

Bell or Whistle

Even though Marv can play from soft to very loud, most MIDI music does not use the entire dynamic range and therefore Marv typically plays in the higher volume range. This is because a typical velocity number of 100 is much louder than a rare velocity number of 1. To overcome this we decided to implement a volume knob to scale the loudness of the instrument. Each microcontroller has 4 I/O pins unused by the USART or strikers/dampers, and so we connected a 16-position, 4-bit switch to them as the volume control. Marv reads the logical values on these 4 input pins and converts them to an integer between 1 and 16. This number is used to divide the pulse lengths, which creates a lower volume. For example, with the switch at 16, we get the full range; ie, a pulse with length from 1.2ms to 5.0ms depending on the velocity information. However, with the switch at 1, we get a shorter pulse or 1.2ms to (1.2 + [5.0-1.2]/16)ms = a range from 1.2ms to 1.44ms. This still preserves the dynamic range encoded in the velocity byte, but reduces the overall volume. Unfortunately the volume resolution is determined by the interrupt interval duration of 128us and we lose some of this when we turn the volume down. This is still a good trade-off when there is a need to play at lower levels.


                                                Flow Chart of Code Structure


The results are very good. The code works great, but it wasn't always like that. The best diagnostic tool was a light board that represented the vibraphone system. Using this board we could visually see if the code was working. We used this board rather than actually hooking up the solenoids in case the logic signals stuck high and latched the solenoids. It was also easier to see if all the I/O assignments were correct. Once we found that the code worked on the light board we moved to the final assembly. At this point we found that the volume was not a gradient, but a binary state. The use of fractions in the code gave us issues and more clever equations were needed to get the gradient effect.

To improve the performance we could use a different microcontroller with a faster clock speed. This would allow us to process the MIDI information faster and theoretically reduce the timer interrupt interval. This would give us finer resolution of the pulse length and correspondingly the volume as well. This also might give us time to perform even more routines and add to the functionality of the instrument. Another issue with the ATMEGA16 is the amount of I/O pins. If we used a different chip with enough pins we could use just one chip to control all the strikers and dampers and reduce the number of PCBs to save cost. One other improvement could be to also implement a sustain action. MIDI has a status byte for the sustain pedal which can be used to disengage all the dampers to get an overall sustain effect on the keys. This would give the vibraphone a more natural sound when playing MIDI songs with any sustain pedal in it.

*** All Marv's Code is written and designed by Brock Roland

Tim O'Keefe,
Dec 28, 2009, 11:59 PM