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
Discussion
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