You are on page 1of 73

ENGR 466: Integrated Design Project Course

Instructor:

Daniela Constantinescu, Mechanical Engineering Department

Client:

George Tzanetakis, Computer Science Department

Final Design Report for a Robotic Drummer

Group Members:

Neil MacMillan, V00182450

Matthew Loisel, V00194616

Daniel Partridge, V00334811


Page |i

Contents
List of Tables ...................................................................................................................................iii
List of Figures ..................................................................................................................................iii
Project Overview:............................................................................................................................ 1
Major Design Criteria: ..................................................................................................................... 1
Secondary Goals:............................................................................................................................. 1
Mechanical Design .......................................................................................................................... 2
Workspace Envelope ................................................................................................................... 2
Vertical Displacement ............................................................................................................. 2
Rotary Displacement ............................................................................................................... 3
Output Frequency ....................................................................................................................... 4
Output Force ............................................................................................................................... 4
Design .......................................................................................................................................... 6
Electrical Design .............................................................................................................................. 7
Overall Design ............................................................................................................................. 7
Electrical Components ................................................................................................................ 7
Power Supply .......................................................................................................................... 7
Microcontroller ....................................................................................................................... 8
Solenoid and Transistor Driver ............................................................................................. 10
Stepper Motor and Driver ..................................................................................................... 11
MIDI Connector and Interface Board.................................................................................... 12
Circuit Layout, Mounting, and Wiring ....................................................................................... 13
Software Implementation ............................................................................................................. 15
The MIDI Protocol ..................................................................................................................... 15
Note On Command ............................................................................................................... 15
Pitch Bend Command ........................................................................................................... 16
Serial Protocol ....................................................................................................................... 18
Command Stream ................................................................................................................. 18
Firmware Implementation ........................................................................................................ 20
P a g e | ii

Activity Diagram .................................................................................................................... 21


Object Design ........................................................................................................................ 23
Sequence Diagrams............................................................................................................... 24
Workstation Software ............................................................................................................... 27
DrummerBot Test ................................................................................................................. 27
Music Analysis ....................................................................................................................... 28
References .................................................................................................................................... 30
Appendix A: Pontiac Coil Solenoid ................................................................................................ 32
Appendix B: Stepper Motor Torque vs. Speed Specification ........................................................ 33
Appendix C: Part Drawings ........................................................................................................... 34
Appendix D: Code Listings............................................................................................................. 44
P a g e | iii

List of Tables
Table 1: Force Calculations ............................................................................................................. 5
Table 1 - Power Supply Pin Connections ........................................................................................ 8
Table 2 - Microcontroller Pin Connections ..................................................................................... 9
Table 3 - Stepper Motor Wiring .................................................................................................... 11
Table 4 - Base Unit to Top Unit Wiring ......................................................................................... 14
Table 5 - MIDI Commands............................................................................................................. 15

List of Figures
Figure 1 - Vertical Displacement of Drumstick End ........................................................................ 3
Figure 2 - Horizontal Displacement of Drumstick End .................................................................... 4
Figure 3 - Force vs. Stroke Length for 1-1/4" Solenoid ................................................................... 5
Figure 4 - 466Bot Located Near Sample 50 cm Drum..................................................................... 6
Figure 5 - System Block Diagram .................................................................................................... 7
Figure 6 - Power Supply Pin Configuration ..................................................................................... 7
Figure 7 - Arduino Duemilanove Microcontroller Board ................................................................ 9
Figure 8 - Power MOSFET and Solenoid Circuit ............................................................................ 10
Figure 9 - Stepper Motor Driver Circuit ........................................................................................ 11
Figure 10 - MIDI Interface Circuitry and Connector ..................................................................... 12
Figure 11 - Approximate Circuitry Layout ..................................................................................... 13
Figure 12 - Picture of Circuitry Layout .......................................................................................... 13
Figure 13 - Base Unit to Top Unit Wiring Connector .................................................................... 14
Figure 14 - The "Note On" Command ........................................................................................... 16
Figure 15 - The "Pitch Bend" Command ....................................................................................... 17
Figure 16 - Firmware Activity Diagram ......................................................................................... 21
Figure 17 - Class Diagram .............................................................................................................. 23
Figure 18 - Sequence Diagram for Explicit Commands ................................................................. 24
Figure 19 - Sequence Diagram for Implicit Commands ................................................................ 25
Figure 20 - Sequence Diagram for Determining if the Ring Buffer Contains an Implicit Command
....................................................................................................................................................... 26
Figure 21 - The DrummerBot Test Console After Sending a Note On and Pitch Bend Command 28
Figure 22 - Accompany Data Flow Diagram .................................................................................. 29
Figure 23 - Solenoid Data Sheet.................................................................................................... 32
Figure 24 - Torque vs. Speed specifications from Digikey ............................................................ 33
Page |1

Project Overview:
As described by George Tzanetakis this project is to design and build a robotic drummer. This
robot is to be capable of holding a variety of drumsticks, translating radially along the
drumhead and striking with a variety of forces to create louder or softer sounds. The project is
motivated by the design of Ajay Kapur, a recent grad student, who created a similar device to
provide percussion backup to his work on a sitar. The minimum required by George is a robot
arm capable of handling a drumstick and hitting a variety of positions on a drumhead with
varying force. Further development of multiple robots capable of hitting different drums or
cymbals would be appreciated and considered an additional benefit.

Major Design Criteria:


In order of importance as we understand from meetings with George Tzanetakis the design
must be:

Robust – since the device is to be used as a teaching tool the design must be able to
withstand being handled by multiple users and capable of working properly after being
stored and shipped.
Repositionable – often times this device will be taken off campus for use in
demonstrations at schools and other institutes, for this the robot has to be light weight,
easy to setup and use.
Replicable – to make an adequate teaching instrument many copies of this device will
be created, to ease future manufacturing the device has to be simple, and require
minimal manufacturing time.

Secondary Goals:
To meet the major design criteria, secondary goals have been added for consideration. These
objectives are to:

 Create a manipulator capable of holding a variety of drumsticks.


 Design a robot able to move vertically and able to move the end of a drumstick
to various location on a single drum.
 Explore different methods of actuations including solenoid and DC motor based
designs.
 Implement the ability to stand alone or link with other robots for greater
flexibility.
 Explore the possibility of using wireless communication.
Page |2

 Provide full drawing set, bill of materials and specifications with procedures.

Mechanical Design
The design used for this project is based on the Trimpin Hammer used by Ajay Kapur in the
MahaDeviBot. There are a number of reasons why this design was chosen over the three other
alternatives. These advantages include:

 Large workspace envelope


 High output frequency
 Greater range of output force than other options
 Robust design

Workspace Envelope
The workspace envelope is defined as the three-dimensional space within which physical work
is done; it is determined by functional arm reach which, in turn, is influenced by the direction of
reach and the nature of the task being performed. The envelope for the 466Bot is determined
by the drumstick’s vertical displacement from the upper rest position to the drumhead and the
horizontal position of the end of the drumstick relative to the drumhead. The vertical and
horizontal displacements are controlled using a solenoid and a stepper motor respectively.

Vertical Displacement
The vertical displacement is controlled using a Pontiac Coil, Inc. pull-type solenoid. The solenoid
chosen for this purpose is the 1-1/4” 24VDC solenoid shown in Appendix A. This solenoid was
chosen due to its large stroke length and high force capabilities. Using a 15 inch drumstick
(standard drumsticks range in length from 13-17 inches) protruding 13 inches from the pivot,
with the solenoid attached 2 inches from the pivot, the vertical displacement of the drumstick
end can be calculated as 8.13 inches:

The following figure shows the range of the vertical displacements capable of being achieved
using this solenoid.
Page |3

Figure 1 - Vertical Displacement of Drumstick End

In Figure 1 - Vertical Displacement of Drumstick End the two blue lines represent the limits of
the vertical workspace envelope.

Rotary Displacement
Striking a drum at different locations on the drum head produces different sounds. To produce
a greater range of sounds the 466Bot will use a stepper motor mounted to the base to position
the end of the drumstick over the drum head. With drums ranging in diameter from 10cm to
50cm, a stepper motor with a resolution of 3.6 degrees per step was chosen as it allows the
robot the ability to hit a variety of locations while still being able to translate fast enough to
reduce lag between the input from the controller and the vertical actuation of the solenoid.

The motor used for the 466Bot has a maximum speed of 450 steps per second. A 39.6 degree
rotation, which covers the entire envelope for a 50 cm diameter drum, would require 11 steps
and take approximately 24ms. Although this delay seems large, research has shown that
different sounds less than 50ms apart are not discernable as distinct. The figure below shows
the 466Bot and a model of a 50cm drum. The blue lines represent the envelope achievable
using the 3.6 degree stepper motor.
Page |4

Figure 2 - Horizontal Displacement of Drumstick End

Output Frequency
Using the data collected by Ajay Kapur in designing and building the MahaDeviBot the projected
maximum frequency of the 466Bot is 18Hz. According to research by Harvard University’s
Division of Engineering and Applied Sciences an expert drummer can achieve a frequency of
40Hz [10]. This is achieved using both arms each equipped with a drumstick and a technique
known as “drumstick bounce” whereby a drumstick is loosely held in the hand and allowed to
strike the drum two or three times per movement of the wrist. Since the 466Bot will have one
arm drumming at a frequency of 18Hz, it will almost be able to match in speed one arm of an
expert drummer. Two 466Bots will be able to produce the output of an expert drummer, if they
are programmed correctly.

Output Force
The force with which the drumstick will strike the drum is determined by three things: the
length of the solenoid actuation; the moment arm for both the drumstick end and the solenoid;
and the strength of the spring mounted behind the pivot.

Solenoids contain a ferrous core surrounded by copper windings. When a current passes
though the windings a magnetic field is produced and applies a force to the core. As the
solenoid moves, the core retracts, and less of the core is within the windings; this results in less
force being applied to the core. The data sheet in Appendix A and the graph in Figure 3 - Force
Page |5

vs. Stroke Length for 1-1/4" Solenoid show the force capabilities for various positions of the
solenoid.

Figure 3 - Force vs. Stroke Length for 1-1/4" Solenoid

The solenoid applies a force to the mechanism, which revolves around a pin joint, causing the
drumstick to rotate around the pin joint as well. The force applied by the solenoid causes the
end of the drumstick to accelerate until it strikes the drum, at which point the velocity of the
drumstick decreases and applies a force to the drum.

The spring, which repositions the drumstick between actuation, also applies a moment about
the pin joint counter to that of the solenoid. Since this reduces the force at which the drumstick
strikes the drum the spring constant for the spring used is small. Calculations for determining
the spring used are shown below.

Based on the data obtained in determining the force requirements for the preliminary design
report, the range of forces required to produce audible sounds are:
Table 1: Force Calculations

Voltage (V) Force(N) Force (oz)


Soft Hit 0.044 0.45 1.6
Hard Hit 2.233 22.66 81.5

The following calculations show the spring constant required for a 1-1/4” compression of the
solenoid, with the drumstick applying a “soft hit” as defined in the preliminary report. The
lengths for the solenoid, spring and drumstick moment arms are defined by the geometry of
the 466Bot.
Page |6

Design
Since one of the preliminary requirements of the project was that the robot be easy to
manufacture, the mechanical parts have been designed for simplicity. This will aid in future
replication of the design for educational purposes and demonstrations. The following figure
shows the assembly model, produced using SolidWorks 2008. Individual part drawings are
included in Appendix C along with a labelled assembly drawing of the robot. Assembly
procedures will be included in the final report along with estimated machining times.

Figure 4 - 466Bot Located Near Sample 50 cm Drum

Not shown in the image is the stand for positioning and orienting the robot, as these stands
contain universal mounts. This makes the robot capable of mounting to any standard drum kit
stand.
Page |7

Electrical Design

Overall Design
The overall system design is shown below in Figure 5. The control of the system is based around
the microcontroller. The microcontroller receives MIDI encoded serial data from a computer or
from a MIDI enabled musical instrument. It then uses these MIDI data to control the solenoid
and stepper motors through the transistor driver and stepper motor driver respectively.

Musical Transistor
Solenoid
Instrument Driver

MIDI
Stepper Stepper
Interface MCU Drum Stick
Motor Driver Motor
Circuitry

Computer

Figure 5 - System Block Diagram

Electrical Components

Power Supply
The power supply used was the GLC75DG power supply from Condor electronics. It features
four set DC output lines of +5.1V at up to 8A, +24V at up to 2A, -12V at up to 1A, and +12V at up
to 1A. The configuration of the input and output pins is shown below in Figure 6 and the
function and connection of each pin is listed in Table 2.

5 3 1 13 12 11 10 9 8 7 6 5 4 3 2 1

Figure 6 - Power Supply Pin Configuration


Page |8

The power supply documentation states that it is necessary that a constant current draw of
0.2A be present on the 5.1V output and 0.25A be present on the 24V output for proper
rectification of all outputs. This would correspond to a 25.5Ω, 1.02W minimum resistor on the
5.1V output and a 96Ω, 6W minimum resistor on the 24V output. Through testing though, it
was determined that a single 50Ω resistor across the 5.1V output is enough to result in proper
rectification. A 51Ω, 1W resistor was selected and placed between pins 3 and 6 of the power
supply output.

Table 2 - Power Supply Pin Connections

Pin Input Pin Output Connection


1 AC Ground 1 +5.1V NC
3 AC Neutral 2 +5.1V MIDI interface board
5 AC Line 3 +5.1V Power resistor
4 GRND NC
5 GRND MIDI interface board
6 GRND Power resistor
7 GRND Solenoid
8 +24V Solenoid
9 +24V NC
10 POWERFAIL NC
11 -12V NC
12 GRND Stepper motor driver/MCU
13 +12V Stepper motor driver/MCU

Microcontroller
The microcontroller development board used was the Arduino Duemilanove board shown
below in Figure 7. The microcontroller is powered using the Vin and Gnd input pins from the
12V output of the power supply. The board needs 7-12V to be input into the internal regulator
to produce the necessary 5V for the rest of the board. Through testing it was found that the
Arduino operated at 3.3V when powered by the external power input, and 5 V when powered
by the USB connector. At 3.3V the PWM signal generated by the Arduino was insufficient to
switch the transistor on and off; the 5V levels were needed. 5.1V was not enough to power the
board and it was necessary in this case for the USB connector to be connected for the
microcontroller to be able to output enough voltage on the PWM line to switch the transistor
on and off.
Page |9

Figure 7 - Arduino Duemilanove Microcontroller Board

The pin connections for the microcontroller are listed below in Table 3. During testing, a
problem arose where code could not be properly loaded onto the microcontroller from the USB
connector. Upon further investigation this was determined to be because the MIDI input (see
MIDI section) is normally high and is connected to the same serial receive line as the USB input.
To correct this, a single-pole-double-throw slider switch was connected between the MIDI input
signal and the serial receive pin (Rx) to be able to disconnect two lines when loading code
through the USB connector.

The outputs to the stepper motor driver are connected to the Arduino’s analog input port. The
Arduino’s analog pins are really general I/O pins that happen to be connected to the ATmega’s
analog to digital converter, so they can function as digital outputs. As digital pins they are
referred to in software as pins 14-19, even though on the board they are labelled analog 0-5.
Table 3 - Microcontroller Pin Connections

Pin Connection
0 (Rx) MIDI interface board output
5 Transistor gate – pin 1
(PWM)
14 K CMD L298 - EN0
15 K CMD L298 - EN1
16 K CMD L298 - L1
17 K CMD L298 - L2
18 K CMD L298 - L3
19 K CMD L298 - L4
P a g e | 10

Solenoid and Transistor Driver

The activation of the drumstick is achieved through the use of the L90 series solenoid F0464A.
The solenoid is switched through the use of the IRFZ40 power MOSFET which is connected as
shown in Figure 8. The transistor is driven by a pulse width modulation (PWM) signal from the
microcontroller. The use of PWM allows for easy variation of the amount of power transferred
to the solenoid through the modification of the signal duty cycle and reduction of the overall
power consumption.

24V

F0464A

IRFZ40

1
PWM5
10k

Figure 8 - Power MOSFET and Solenoid Circuit

The solenoid was mounted directly to the custom aluminum chassis. A high-power diode was
placed between 24V and the MOSFET drain to prevent damage to the transistor and power
supply due to reverse currents that can occur when the solenoid is switched. The transistor was
mounted on a small piece of prototyping board to create a simple stand for the transistor and
to allow for easier soldering of the three lines connected to the transistor. A 10kΩ resistor was
placed between the PWM5 output of the MCU and the gate of the transistor to ensure current
limiting.
P a g e | 11

Stepper Motor and Driver

To rotate the drum stick to hit different parts of the drumhead, the 42M100B2U unipolar
stepper motor was implemented. The stepper motor was driven by the K CMD L298 stepper
motor driver board. The board came in a kit and had to be soldered together using easy to
follow instructions included with the kit. The K CMD L298 board accepts four control lines and
two enable lines from the MCU and converts those signals to the necessary 12V signals to drive
the stepper motor. The board has four LEDs on it that indicate the current line that is active
(M1+, M1-, M2+, or M2-) and alternate flashing when the stepper motor coils are actuated in
the correct order. The K CMD L298 board was connected to the stepper motor and the MCU as
shown in Figure 9.

12V
K CMD L298
19 M1-
6
18 5
I4
I3 M1+
4
15 EN1

17
3
2
I2
I1
M2- M 42M100B2U
1
M2+
16 EN0

14 */*

Figure 9 - Stepper Motor Driver Circuit

The wiring for the stepper motor is described below in Table 4. The wires embedded into the
stepper motor were soldered and shrink-wrapped onto longer wires that ran down to the base
(see wiring section).
Table 4 - Stepper Motor Wiring

Line Colour
M1+ yellow
M1- orange
M2+ brown
M2- black
Ground red
Ground green
P a g e | 12

The stepper motor originally selected for this project ended up not being able to produce
enough torque to rotate the solenoid and drumstick chassis. The current stepper motor only
drew approximately 0.16A of the total 1A available to it which limits the amount of torque it
can produce. Stepper motors that draw more current and can produce more torque are
currently being investigated to replace the existing stepper motor.

MIDI Connector and Interface Board


The MIDI protocol is a normally-high serial protocol based on a current loop that exists between
pins 4 and 5 of the MIDI connector. The current loop runs through the input pins (1 and 2) of
the PC900 opto-isolator as shown below in Figure 10 and switches the input LED on and off. Pin
4 is held at a constant 5V in the MIDI out device while pin 5 is 5V when a 0 is being sent and
switches to 0V when a 1 is being sent. Thus, a current of 5 mA corresponds to logic 0 and a
current of 0 mA corresponds to logic 1.

5V

220 300
1 6 4
2 Rx
5 4 PC900
3 1

2 5

Figure 10 - MIDI Interface Circuitry and Connector

The opto-isolator converts the current signal to a voltage signal. As described in the
Microcontroller section, a single-pole-double-throw slider switch was connected between the
output of the opto-isolator and the serial receive pin of the microcontroller to be able to
disconnect the MIDI input line from the receive pin when loading code through the USB
connector. The downwards (towards the middle of the layout) position of the switch enables
MIDI input while the upwards (towards the edge of the layout) position enables code loading
through USB.
P a g e | 13

Circuit Layout, Mounting, and Wiring


To accommodate the desire to have an open design, the various control circuitry and interface
circuitry was mounted on top of the power supply. To allow for proper air flow out of the
power supply and to prevent short circuits, the various components were mounted on plastic
or metal spacers or washers. The circuit layout was based around having any external ports on
the opposite side of the power supply as the power pins. The approximate circuit layout is
shown below in Figure 11. This layout was implemented as shown in Figure 12.

MCU Stepper Motor


Switch
Driver

Power Pins

MIDI Power
MIDI Interface Circuitry MOSFET
Connector

Figure 11 - Approximate Circuitry Layout

Figure 12 - Picture of Circuitry Layout


P a g e | 14

Long lengths of wire were run from the top unit (chassis with solenoid and stepper motor)
down to the base unit (power supply and control boards) which were long enough to run from
the top of a standard cymbal stand down to the bottom with extra slack. In total, seven lines
were run: two for the solenoid and five for the stepper motor. Closer to the base unit, a
connector was inserted so that the base unit could be detached from the top unit. The pin
configuration for the connector is shown below in Figure 13 and the functions and colours of
the wires run into and out of the connector are listed in Table 5.

1 2 3

4 5 6

7 8 9

Figure 13 - Base Unit to Top Unit Wiring Connector

Table 5 - Base Unit to Top Unit Wiring

Pin Base Unit Device Top Unit Device Line Wire Colour
1 power supply solenoid +24V red
2 MOSFET solenoid MOSFET drain grey
3 K CMD L298 stepper motor M1+ orange
4 K CMD L298 stepper motor M1- green
5 K CMD L298 stepper motor M2+ blue
6 K CMD L298 stepper motor M2- white
7 K CMD L298 stepper motor ground black
8 NC NC - -
9 NC NC - -
P a g e | 15

Software Implementation

The MIDI Protocol


The robot is controlled using the Musical Instrument Digital Interface (MIDI) protocol. MIDI is a
common digital music format that represents music as a sequence of commands, as opposed to
a sequence of analog data samples. Each MIDI command is an 8-bit byte, and can be followed
by several 8-bit data bytes. Command bytes always have a 1 as their most significant bit, and
data bytes always have a 0 as their most significant bit. As such, data bytes can actually only
hold 7 bits of information, for a maximum range of 0 to 127. The following musical commands
are defined by the MIDI protocol:

Table 6 - MIDI Commands

Byte Command Meaning


0x80 Note Off Stop playing a note.
0x90 Note On Start playing a note.
0xA0 Aftertouch Change a note’s velocity while it’s playing.
0xB0 Control Change Change the value of a controller (e.g. a button or
slider).
0xC0 Patch Change Change the instrument that the MIDI device is
simulating.
0xD0 Channel Adjust the velocity of all notes on the given channel.
Pressure
0xE0 Pitch Bend Adjust a note pitch by small increments.

The command bytes in the table all have their lower nibbles set to 0. In MIDI, these four bits
contain the number of the channel to which the command is addressed. There are also several
non-musical system messages represented by bytes 0xF0 to 0xFF.

The robot’s firmware implements a subset of this protocol. Specifically, it responds to the
“note on” and “pitch bend” commands. It ignores all other commands, and ignores channel
numbers.

Note On Command
The “note on” command indicates that a MIDI device should start playing a note. It has two
parameter bytes. The note byte selects which of 128 notes to start playing, with middle C
defined as note 60. The velocity byte determines the note’s velocity, which usually corresponds
to the volume at which the note is played.
P a g e | 16

Note On note velocity


(0x90) (0 – 127) (0 – 127)

0 8 16 24

Figure 14 - The "Note On" Command

The drummer bot interprets the “note on” command as an instruction to strike a drum. The
robot can control up to three different drums, so the note parameter determines which drum
to strike: if all three drums are being used, then every third note controls the same drum. For
example, notes 0, 3, 6, and so on all control the bass drum. Notes 1, 4, 7, ... control the main
drum, notes 2, 5, 8, ... control the hi-hat cymbal, etc. If there are fewer drums, then each drum
can listen to more notes. For example, if there is one drum then the drum can listen for every
note. Currently this setting must be hard-coded into the firmware. The “note on” command’s
velocity parameter controls how hard the drum is struck.

Normally a “note on” command must be followed by a “note off” command on the same note.
With drums, it does not make sense for a note to be held on for an arbitrarily long period of
time; rather, the drummer bot itself should control how long it takes to strike the drum, since it
is a property of the robot and its position relative to the drum. The strike time is not something
that the MIDI controller has enough information to determine. In fact, the robot uses a
hardcoded strike time of 50 milliseconds. Another option is to allow the user to adjust the
strike time, but variations in the strike velocity have a greater impact in the drum stick’s
accuracy than the strike time, so there is not much value in allowing the user to adjust the
strike time.

Most MIDI controllers will still send “note off” commands to the drummer, for example when
the user releases a key on a MIDI keyboard. The robot ignores “note off” commands. Some
controllers send “note on” commands with the velocity set to 0 as a substitute for “note off”
commands. The robot will ignore “note on” commands with 0 velocity so as to behave
consistently between different MIDI controllers. Another option is for the robot to handle
“note off” commands as well as “note on” commands with 0 velocity, and to terminate the
strike when either of those are received. That would create an extra variable that the user
would need to control though, which would make the robot more difficult to use.

Pitch Bend Command


The “pitch bend” command controls fractional steps in a note’s pitch. “Pitch bend” commands
are generated when the user moves the pitch wheel, a common component on MIDI
controllers. For example, if an instrument is playing a D note, the “pitch bend” command can
P a g e | 17

be used to transpose the note to any of about 16,000 points between C and E. The pitch bend
is applied to all notes being played by the instrument.

Pitch Bend pitch_msb pitch_lsb


(0xE0) (0 – 127) (0 – 127)

0 8 16 24

Figure 15 - The "Pitch Bend" Command

“Pitch bend” has one 14-bit argument to set the pitch. The argument is sent as two 8-bit bytes
with each byte’s most significant bit set to 0 (because they are data bytes). To get the final
pitch value, the bytes must be converted to 7-bit bytes and concatenated. The following C++
code concatenates the 8-bit bytes into a 14-bit integer (stored as a 16-bit integer, since 14-bit
integers are not usually supported natively in firmware). It assumes that the “pitch_msb”
variable is the most significant byte, and “pitch_lsb” is the least significant byte:
uint16_t pitch = pitch_msb;
pitch = (pitch << 7) | pitch_lsb;

A pitch value of 0x2000 is considered the centre value, and does not transpose the note. Pitch
values between 0x0000 and 0x2000 transpose the note down, and pitch values between
0x2000 and 0x4000 transpose the note up.

The robot interprets the pitch bend command as an instruction to change its lateral position on
the drum by rotating its stepper motor. The pitches between 0x2000 and 0x4000 are divided
into 4 ranges, each of which correspond to a position up to 4 steps to the right of centre on the
drum. The firmware keeps track of the value of the latest “pitch bend” command, and if the
robot’s lateral position does not match the position corresponding to the pitch value, it cycles
the stepper motor until the positions match.

There is a dead zone on the border between each range, to keep the stepper motor from
oscillating when the pitch value is near the border. For example, the transition from pitch
0x27FF to 0x2800 is a border: the drum will be in its centre position up to 0x27FF, and then
when the pitch becomes 0x2800 the drum will move one position to its right. If the user is
holding the pitch wheel such that the pitch value is switching back and forth between 0x27FF
and 0x2800 quickly, then the stepper motor will move back and forth erratically, which is
undesirable behaviour. Instead, the robot will not move a step right until the pitch value
reaches 0x2840. Once it is in that position, it will not move back until the pitch value goes
down to 0x27C0.
P a g e | 18

Serial Protocol
The MIDI protocol is a simplex Universal Asynchronous Receive/Transmit (UART), or serial,
protocol that operates at 31250 bits per second. The optoisolator described in the Electrical
Design section above allows the MIDI receiver to receive serial data at whatever levels it can
use most easily—in this case, 0 to 5 volt levels.

The serial baud rate is configured on the ATmega328P by setting the UART Baud Rate Register
(UBRR) to a certain scaling value. The value in UBRR scales the AVR’s oscillator frequency down
to the clock rate needed to generate the serial waveform. One problem that one can
encounter is that for baud rates that do not divide into the oscillator frequency evenly, the
UART module cannot generate a precise sampling frequency. Another way to look at it is that if
the ideal scaling factor is a rational number and not an integer, then it cannot be represented
precisely in the 8-bit UBRR register. This approximation of the scaling factor leads to framing
errors when the transmitter and receiver go out of synch.

MIDI’s baud rate of 31250 is an unusual speed in typical UART applications, so it is worth
examining how accurately the Arduino can receive serial data at that speed. Atmel’s datasheet
for the ATmega328P gives the following equation for determining the UBRR value in terms of
the oscillator frequency and the desired baud rate:

The Arduino uses a 16 MHz clock, so the UBRR value for MIDI is calculated like this:

Since the UBRR value is precisely 31, the Arduino can receive a 31250 baud signal with no risk of
framing errors. In fact, any microcontroller whose oscillator frequency is a multiple of 500,000
can receive the MIDI signal with perfect precision, which might be why such a strange baud rate
was selected for the protocol.

Command Stream
A MIDI controller sends a stream of commands over its serial connection to the receiver. Each
command consists of a command byte followed by some data bytes. Both of the commands
that the robot listens for have two-byte arguments, so the relevant command packets are each
3 bytes. The firmware polls its UART receiver and copies any bytes it receives into a 3-byte ring
buffer. When the buffer is full and the buffer head is a command byte, then in general the
buffer contains a full command packet, which can be removed from the buffer and processed
by the appropriate command handler. There are two exceptions to this rule.
P a g e | 19

Implicit Commands
Often, when a MIDI controller sends a command multiples times in a row, it will send the
command byte only for the first command packet and leave the command implicit for the
remaining packets. This is done to reduce the amount of traffic on the serial connection, which
can be an important factor in the responsiveness of the instruments that the device is
controlling. For example when the user turns on a note while turning the pitch wheel, the
following sequence of data might be sent over the serial connection:

0xE0 // “pitch bend”


0x2000 // “set pitch to middle position”
0x2001 // “increment pitch”
0x2002 // “increment pitch”
0x906040 // “note on for note 0x60 with velocity 0x40”
0xE0 // “pitch bend”
0x2003 // “increment pitch”
0x2004 // “increment pitch”

...

In this example, the command packets that set the pitch to 0x2001, 0x2002, and 0x2004 all lack
explicit command bytes. The command is implied to the receiver by the fact that the last
command byte it received was the “pitch bend” command.

The firmware solves this problem by checking the ring buffer’s size every time it receives a byte.
If the buffer contains exactly two bytes and both of them are data (in other words, neither is an
explicit command), then the firmware treats them as arguments to an implicit command.
Recall that data bytes have their most significant bits set to 0, whereas explicit command bytes
have their most significant bit set to 1. This is safe because both of the commands that the
robot can handle have arguments that are exactly two bytes. All the other commands are
ignored, so it doesn’t matter if their arguments are treated incorrectly as implicit commands.

Odd-Sized Arguments
Most MIDI commands are followed by exactly 2 argument bytes, but not all of them. If the
firmware receives a MIDI command that doesn’t have exactly 2 argument bytes, then following
the rule described above might cause a problem. For example, the “patch change” command
only has one 1-byte argument. Consider the case when the firmware receives a “patch change”
command followed by a “note on” command. Once the “note on” is received, the ring buffer
looks like this:
P a g e | 20

0xC0 // “patch change”


0x40 // “change to instrument 0x40 (argument byte)”
0x90 // “note on”

In this case, the general rule will result in the three bytes being removed from the buffer,
packaged into a “patch change” message, and then ignored since the firmware does not handle
the “patch change” command. This results in the “note on” command, which should be
handled by the firmware, being dropped. Even worse, further “note on” commands will likely
be implicit, but will be interpreted as “patch change” commands since the initial explicit “note
on” command byte was dropped.

The firmware solves this problem by making sure that what it assumes to be arguments are
really data bytes and not command bytes. If either of the argument bytes is a command, then
it adds both arguments back to the ring buffer so that the command will be processed once its
full set of arguments has been received.

Firmware Implementation
The firmware is written in C++ targeting the Arduino platform. C++ was chosen because the
Arduino core library is partially written in C++, so components such as the HardwareSerial class
are unavailable in C.

C is usually used in favour of the object oriented C++ for embedded system development. This
is because C programs have lower program memory requirements, it is easier to avoid dynamic
memory allocation in C, and object-oriented programming often leads to overengineering in
firmware development.

The ATmega processors used in the Arduino have plenty of program memory available, though:
as of this writing, the firmware compiles to a 10 KB binary, which fills about 30% of the
ATmega328P’s program memory.

Avoiding dynamic memory allocation is important for several reasons. The main reason is for
memory safety: allocating objects on the heap creates a risk of memory leaks, which is very
dangerous in a low-memory environment (the ATmega328P provides 2 KB of data memory).
Even if there are no memory leaks, it is still easy to allocate too much memory on the heap
through normal program operation, especially if memory is allocated in loops. Accessing the
heap is also slower than allocating objects statically or on the stack. The firmware avoids the
heap by instantiating objects statically, and by making extensive use of the singleton design
pattern to reduce the number of objects that can be instantiated. Accessing a statically
instantiated object in C++ is not significantly slower than accessing a static structure in C. Thus,
the firmware is small enough and runs fast enough despite being written in C++.
P a g e | 21

Activity Diagram
The activity diagram below shows an overview of the algorithm that the firmware executes.
There are three main subsystems: the Midi class, the Stepper class, and the Solenoid class. The
program’s main loop repeatedly checks the MIDI subsystem, the stepper motor subsystem, and
the solenoid subsystems. The only asynchronous input to the system is the MIDI UART
connection, which is handled by the Arduino core library and polled during the CheckMidi state.

[Command
Ready]
CheckMidi Assemble Packet

[No
Command]

Send Command to Listener

[Strike
Timed Out]
CheckSolenoid Deactivate PWM

[Otherwise] [NoteOn
Command
Received]

Activate PWM Start Timer

[Turn
Complete]
CheckStepper Stop Turning

[Otherwise] [PitchBend
Command
Received]

Start Turning Calculate Turn

Figure 16 - Firmware Activity Diagram


P a g e | 22

The check states are designed for minimal latency so that each iteration of the main loop is
executed as quickly as possible. All timing is done using the Arduino core library’s millis()
function, which returns the number of milliseconds that have elapsed since the timer started. A
delay can be inserted into a subsystem by storing its start time, and continually checking the
result of the millis() function until the desired number of milliseconds have elapsed. This
technique allows the firmware to continue to check all of its subsystems while one of the
subsystems is executing a delay, which makes the system highly concurrent.

The millis() function returns a 32-bit value, but the firmware uses 8- and 16-bit variables. To
avoid direct comparisons between the return value of millis() and stored times, the firmware
defines a function called millis16(), which just samples the millis() function and returns the least
significant 16 bits of that value.
P a g e | 23

Object Design
The class diagram below shows the objects that are defined in the firmware (not including
those defined in the Arduino core library) and how they relate to one another.

Midi
-listeners : MidiListener* [NUM_COMMANDS]
-buffer : ThreeByteRing «union»
-last_command : uint8_t midi_param_t
-last_channel : uint8_t ThreeByteRing +note_on : note_on_t
-Midi() -ring : uint8_t[3] +pitch_bend : pitch_bend_t
+GetInstance() : Midi* -size : uint8_t = 0
«uses»
+CheckMidi() -head_index : int8_t = -1
+RegisterListener(in listener : MidiListener*) -tail_index : int8_t = -1 «struct»
+ThreeByteRing() note_on_t
«uses»
+Add(in value : uint8_t) +note : uint8_t
+Peek() : uint8_t +velocity : uint8_t
+Remove() : uint8_t
+Size() : uint8_t

MidiListener «struct»
-command : MIDI_COMMAND pitch_bend_t
#MidiListener(in command : MIDI_COMMAND) +pitch : uint16_t
+GetCommandType() : MIDI_COMMAND
+ProcessCommand(in param : midi_param_t*)

«enumeration»
MIDI_COMMAND
MidiNoteOnListener MidiPitchBendListener +NOTE_OFF = 0
+NOTE_ON = 1
#MidiNoteOnListener() #MidiPitchBendListener()
+AFTERTOUCH = 2
+DoNoteOn(in note : uint8_t, in velocity : uint8_t) +DoPitchBend(in pitch : int16_t)
+CONTROLLER = 3
+PATCH_CHANGE = 4
+CHANNEL_PRESSURE = 5
+PITCH_BEND = 6
Stepper
Solenoid -current_cycle_start_time : uint16_t
-current_pitch : uint16_t
-type : DRUM_TYPE
-current_position : uint8_t «enumeration»
-max_duty_cycle : uint8_t
-destination_position : uint8_t STEPPER_STATE
-duty_cycle : uint8_t
-max_positions : uint8_t +CYCLE1 = 0x05
-pwm_pin : uint8_t
-state : STEPPER_STATE +CYCLE2 = 0x09
-note_divisor : uint8_t
-strike_start_time : uint16_t -Stepper() +CYCLE3 = 0x12
-strike_timeout : uint8_t +GetInstance() : Stepper* +CYCLE4 = 0x22
-CycleCCW()
-Solenoid(in type : DRUM_TYPE)
-CycleCW()
+GetInstance(in type : DRUM_TYPE) : Solenoid*
-OutputCycleCode()
#DoNoteOn(in note : uint8_t, in velocity : uint8_t)
-TransferToState(in new_state : STEPPER_STATE) «enumeration»
+CheckSolenoid()
#DoPitchBend(in pitch : int16_t) DRUM_TYPE
+CheckStepper() +MAIN = 0
+BASS = 1
+HIHAT = 2

Figure 17 - Class Diagram


P a g e | 24

Sequence Diagrams
The following diagrams show the operation sequence for the CheckMidi task when the robot
receives explicit and implicit commands.

:Midi :ThreeByteRing :MidiListener :MidiNoteOnListener :MidiPitchBendListener

CheckMidi()

Add(byte)

Peek()

buffer_head

Remove() x3

new_command, byte1, byte2


{buffer_head is
a command
byte}
<<create(byte1, byte2)>>
param:midi_param_t

ProcessCommand(param)

DoNoteOn

{new_command
is NOTE_ON}
DoPitchBend

{new_command
is PITCH_BEND}

Figure 18 - Sequence Diagram for Explicit Commands


P a g e | 25

:Midi :ThreeByteRing :MidiListener :MidiNoteOnListener :MidiPitchBendListener

CheckMidi()

Add(byte)

implicit_command = BufferContainsImplicitCommand()

Remove() x2

byte1, byte2

{implicit_command
is true}
<<create(byte1, byte2)>>
param:midi_param_t

ProcessCommand(param)

DoNoteOn

{last_command
is NOTE_ON}
DoPitchBend

{last_command
is PITCH_BEND}

Figure 19 - Sequence Diagram for Implicit Commands

The diagram in Figure 20 shows the operation sequence for the


BufferContainsImplicitCommand function used in Figure 19.
P a g e | 26

:Midi :ThreeByteRing

BufferContainsImplicitCommand()

Size()
{size is not 2}

size

false

Remove()

byte1

Remove()

byte2

Add(byte1)

Add(byte2)
{byte1 is data AND
byte2 is data}

true

{byte1 is command OR
byte2 is command}

false

Figure 20 - Sequence Diagram for Determining if the Ring Buffer Contains an Implicit Command
P a g e | 27

Workstation Software
The robot can accept MIDI input over the Arduino’s built-in USB port, not just its MIDI
connector. The Arduino includes an FTDI FT232R USB-UART converter chip, which allows the
device to appear as a serial port on the host computer. This allows programs running on the
host computer to send data to the robot over USB without having to configure the robot as a
USB client. In fact, the USB and MIDI inputs are seamlessly interchangeable, since they are both
UART protocols and are both connected to the same serial receive pin on the microcontroller.
As long as the program running on the workstation generates a valid MIDI command stream,
the firmware cannot tell the difference between USB input and MIDI input.

One problem is that the USB host can introduce significant transmission latency. USB does not
use interrupts, so the USB host must poll for data to send or receive. It can take up to 16 ms for
information to be transmitted over USB, which may have a negative impact on the robot’s
responsiveness. This problem will be solved if it appears.

This project includes two programs that can send MIDI data to the robot over USB.

DrummerBot Test
This program is a simple interface that generates a MIDI command stream in response to
control input, and provides a serial console to display text output from the firmware. It has
three buttons to generate three different notes: one for the bass drum, one for the main drum,
and one for the hi-hat cymbal, of which only the main drum is implemented in this project. The
program also has a slider to simulate pitch bend commands. DrummerBot Test produces
explicit and implicit MIDI commands correctly.

The pitch bend is only transmitted once every 10 ms even if it changes more often, which may
not simulate a MIDI instrument correctly. This is necessary because if data are transmitted too
quickly then the robot cannot handle them correctly, and the MIDI stream gets corrupted. The
actual MIDI keyboard used to test the robot also limits how often it sends pitch bend
commands, and this is likely not a problem that can be fixed.

The program includes a thread that polls the serial port for incoming data, and prints any data it
receives to the serial console. This is useful for doing print debugging in the firmware. The
robot can even receive MIDI data from its MIDI port while sending debug messages to its USB
port, since the MIDI port is receive-only. Normally the firmware does not include print
debugging statements, since the inclusion of such code adds about 2 KB to the program
memory requirements, requires a considerable amount of stack space, and is very slow.
P a g e | 28

Figure 21 - The DrummerBot Test Console After Sending a Note On and Pitch Bend Command

Music Analysis
The music analyser program, called Accompany, analyses musical data, determines when a
drum should be struck in time with the music, and outputs the appropriate MIDI data over the
serial port. This program uses the client’s Marsyas music analysis library to read a source of
music such as a recording or live input.

A simple approach to the music analysis problem has been completed as of this report.
Marsyas comes with a program called mudbox, which is used for experimenting with Marsyas’
functionality. Mudbox now includes an experiment called robot_peak_onset that takes an
audio input, processes it through several systems and passes it on to the computer’s primary
audio output (e.g. the sound card). The processing systems detect peaks in the audio data, and
output in three different ways at each peak:

1. A short sequence of noise is sent to the right speaker.


2. A MIDI command is sent to the MIDI port specified on the mudbox command line (this
does not currently work on Windows).
3. A string (“Peak!!!”) is sent to mudbox’s standard output.

The robot is not able to handle the MIDI command stream that the mudbox experiment
generates. Often it misses beats that other MIDI devices respond to correctly when they are
P a g e | 29

connected to the command stream. This problem was avoided by parsing the standard output
from mudbox and using the code developed for sending MIDI commands from the
DrummerBot Test program to send appropriate MIDI commands each time mudbox outputs a
newline. This makes the robot behave as expected, but it is not currently clear why the robot is
unable to respond correctly to mudbox’s MIDI output.

Marsyas is unable to have MP3 data as a direct input; compressed audio must be converted to
raw WAV data before being input to mudbox. This can be accomplished using LAME, which
includes an audio decoder. Figure 22 shows the data flow from an MP3 file to the MIDI output
to the serial port:

Input.mp3 LAME Temp.wav

Mudbox
Mudbox
Accompany robot_peak_on
stdout
set

Serial
Port

Figure 22 - Accompany Data Flow Diagram


P a g e | 30

References
A. Kapur, Trimpin, E. Singer, A. Suleman, G. Tzanetakis, “A COMPARISON OF SOLENOID-BASED
STRATEGIES FOR ROBOTIC DRUMMING”, 2007. *Online+.Available:
http://www.mistic.ece.uvic.ca/ people/ajay/karmetik/pubs/
2007_icmc_mahadevibot.pdf.[Accessed: May 22,2009]

“Tama Imperialstar 5-Piece Standard Drum Set with Cymbals”. *Online+.Available:


http://www.musiciansfriend.com/document?base_pid=491127&
cpd=0OEY&doc_id=99371&index=1. [Accessed: May22, 2009]

“Bass Drum”, May 2009. [Online].Available: http://en.wikipedia.org/wiki/Kick_drum. [Accessed:


May 22, 2009]

“Shaker (percussion)”, May 2009. *Online+.Available: http://en.wikipedia.org/wiki/Shaker_


(percussion). [Accessed: May 22, 2009]

“Products: Tripod Heads |TheCameraStore.com”, 2009. *Online+.Available:


http://www.thecamera store.com/ search/products/results/taxonomy%253A31.393.
[Accessed: May 22, 2009]

“http://media.digikey.com/pdf/Data%20Sheets/Pontiac%20Coil%20Inc%20PDFs/L-90.pdf”,
2009. [Online].Available:
http://media.digikey.com/pdf/Data%20Sheets/Pontiac%20Coil%20Inc%20PDFs/L-90.pdf.
[Accessed: May 22, 2009]

“http://www.harmony-central.com/ProductImages/Large/000026707.jpg”, 2009. *Online+.


Available: http://www.harmony-central.com/ProductImages/Large/000026707.jpg.
[Accessed May 22, 2009]

“http://media.digikey.com/pdf/Data%20Sheets/Portescap%20Danaher%20PDFs/42M100B2U.p
df”, 2009.
[Online].Available:http://media.digikey.com/pdf/Data%20Sheets/Portescap%20Danaher
%20PDFs /42M100B2U.pdf. [Accessed: June 22, 2009]

“Multi-track recording for musicians”, 2009. *Online+. Available:


http://books.google.ca/books?id
=ByJG1iwUHBAC&pg=PA48&lpg=PA48&dq=smallest+delay+capable+of+hearing+between
+sounds&source=bl&ots=PkjwHLvDGz&sig=lPYU7sdsQbBKD_--
P a g e | 31

xfXHs9D0v4U&hl=en&ei=OMlCSpyCLIz8sgOgpcTLDw&sa
=X&oi=book_result&ct=result&resnum=2. pp 49. [Accessed: June 24, 2009]

“http://biorobotics.harvard.edu/pubs/drumroll.pdf”, 1997. *Online+. Available:


http://biorobotics.harvard.edu/pubs/drumroll.pdf. [Accessed June 21, 2009]

“MIDI SPEC”. *Online+. Available: http://www.popeye-x.com/tech/midi_spec.htm [Accessed


June 10, 2009]

“ATmega48PA/ATmega88PA/ATmega168PA/ATmega328P Hardware Specification”, May, 2009.


[Online]. Available:
http://www.atmel.com/dyn/resources/prod_documents/doc8161.pdf. [Accessed July 7,
2009]

“Effect of USB buffer size and the Latency on Data Throughput”, 2006. *Online+. Available:
http://www.ftdichip.com/Support/Knowledgebase/index.html?an232beffectbuffsizeandl
atency.htm. [Accessed July 20, 2009]

“LAME MP3 Encoder”, 2008. [Online]. Available: http://lame.sourceforge.net/ [Accessed


August 4, 2009]
P a g e | 32

Appendix A: Pontiac Coil Solenoid

Figure 23 - Solenoid Data Sheet


P a g e | 33

Appendix B: Stepper Motor Torque vs. Speed Specification

Figure 24 - Torque vs. Speed specifications from Digikey


P a g e | 34

Appendix C: Part Drawings


P a g e | 35
P a g e | 36
P a g e | 37
P a g e | 38
P a g e | 39
P a g e | 40
P a g e | 41
P a g e | 42
P a g e | 43
P a g e | 44

Appendix D: Code Listings


/*
* main.c
*
* Created on: 4-Jun-2009
* Author: nrqm
*/

#include "arduino/WProgram.h"

#include "midi/midi.h"
#include "stepper/stepper.h"
#include "solenoid/solenoid.h"

/**
* Function for printing debug statements to the serial port.
*/
void print(const char* str)
{
uint8_t i;
for (i=0; str[i] != 0; i++)
{
Serial.write(str[i]);
}
}

/**
* The Arduino millis function returns a 32-bit integer for some reason, and that
compares unfavourably
* with the 16-bit integers we're using, so we want to use a 16-bit timer.
*/
uint16_t millis16()
{
return millis() & 0xFFFF;
}

// This is invoked when a pure virtual function is called. See this:


// http://ccgi.rowley.co.uk/support/faq.php?do=article&articleid=127
extern "C" void __cxa_pure_virtual()
{
digitalWrite(13, HIGH); // turn the LED on
cli();
while (1); // infinite uninterruptible loop
}

void setup()
{
sei();
}

void loop()
{
for (;;)
{
Midi::GetInstance()->CheckMidi();
Solenoid::GetInstance(MAIN)->CheckSolenoid();
Solenoid::GetInstance(BASS)->CheckSolenoid();
Solenoid::GetInstance(HI_HAT)->CheckSolenoid();
Stepper::GetInstance()->CheckStepper();
}
}
P a g e | 45

// This is the default main function used for Arduino sketches.


int main(void)
{
init();

setup();

for (;;)
loop();

return 0;
}
P a g e | 46

/*
* midi.h
*
* Created on: 27-Jun-2009
* Author: nrqm
*/

#ifndef MIDI_H_
#define MIDI_H_

#include <stdlib.h>
#include <avr/io.h>
#include "ThreeByteRing.h"

/**
* The commands defined explicitly in the MIDI protocol.
* There are a bunch of other status bytes (system common and system real time
messages) that are not
* covered here. Currently this program only handles the NOTE_ON and PITCH_BEND
messages.
*/
typedef enum _mcmd
{
NOTE_OFF, /// turn a note off.
NOTE_ON, /// turn a note on.
AFTERTOUCH, /// apply aftertouch to a note (e.g. changes
in velocity after it is turned on).
CONTROLLER, /// set the value for some controller (switch,
knob, etc.).
PATCH_CHANGE, /// change the program/patch that the device is
running (e.g. change instrument).
CHANNEL_PRESSURE, /// similar to aftertouch, but changes velocity for
all notes on channel.
PITCH_BEND, /// change a note's pitch with fine
resolution, e.g. in response to a pitch wheel.
} MIDI_COMMAND;

/// The total number of MIDI commands.


static const uint8_t num_commands = 7;
/// The maximum number of objects that can listen for a given MIDI command.
static const uint8_t max_listeners = 3;

/**
* The parameters to a note on command. See MidiNoteOnListener::DoNoteOn.
*/
typedef struct _no
{
uint8_t note;
uint8_t velocity;
} note_on_t;

/**
* The parameter to a pitch bend command. See MidiPitchBendListener::DoPitchBend.
*/
typedef struct _pb
{
uint16_t pitch;
} pitch_bend_t;

/**
* General 2-byte parameter to a MIDI note on or pitch bend command.
P a g e | 47

*/
typedef union _param
{
note_on_t note_on;
pitch_bend_t pitch_bend;
} midi_param_t;

class MidiListener;

/**
* A class to handle a subset of the MIDI protocol. To use this class, register
objects derived from the
* listener classes defined below for the note on (MidiNoteOnListener) and pitch bend
(MidiPitchBendListener)
* commands. Then repeatedly call Midi::CheckMidi to check for commands and pass them
on to the listeners.
*/
class Midi
{
private:
/// Array of command listeners; has room for three listeners for each of the
commands.
MidiListener* listeners[num_commands][max_listeners];
/// The last command byte that was received, masked with 0xF0 (command nibble).
uint8_t last_command;
/// The last command byte that was received, masked with 0x0F (channel nibble).
uint8_t last_channel;
/// A 3-byte ring buffer to store the last three bytes received over serial.
ThreeByteRing buffer;
/// Constructor; do not instantiate this class, use GetInstance instead.
Midi();
/**
* If there are precisely two data bytes in the ring buffer, then this returns
1; otherwise, it returns 0.
* A MIDI data byte is an 8-bit value with the MSb cleared (in contrast, MIDI
command bytes have the MSb set).
* This is useful because MIDI commands are often sent without repeating the
command byte. For example,
* if a controller sends the PITCH_BEND message several times in a row with no
other messages interrupting
* the stream, it might send the PITCH_BEND command byte once followed by
several pairs of data bytes.
* Controllers do this to reduce the amount of data that need to be
transmitted. Some controllers send the
* command byte explicitly in every message.
*
* If this function returns 1, then the buffer contains a new command message
with the command byte left
* implied by the command byte that was received most recently. Some commands
don't have two-byte
* arguments, so this isn't a general method, but both the commands that this
class handles (note on and pitch
* bend) have two-byte arguments.
*
* If the ring buffer has size 2, this function empties the buffer temporarily,
but it will return the buffer
* to its original apparent state before returning (the buffer's internal state
may be changed).
*/
uint8_t BufferContainsImplicitCommand();

public:
/**
P a g e | 48

* Retrieve the singleton instance of the Midi class.


*/
static Midi* GetInstance();
/**
* Register a command listener object. The object's appropriate method will be
called when the
* Midi class receives the object's corresponding command. For example, if a
note on message is
* received, then the note on listeners' DoNoteOn method is called (if at least
one
* MidiNoteOnListener object has been registered). Up to max_listeners
listeners of each command
* type can be registered.
*/
void RegisterListener(MidiListener* listener);
/**
* Build the command packet from the argument bytes and pass it on to the
command's listeners
* \param byte1 The first argument byte that was received.
* \param byte2 The second argument byte that was received.
*/
void NotifyListeners(uint8_t byte1, uint8_t byte2);
/**
* Read in a single byte from the serial buffer and store it. Once a 3-byte
MIDI message has been
* completely received, pass it on to the appropriate listener. This should be
called from the
* program's main loop.
*/
uint8_t CheckMidi();
};

/**
* Abstract class representing an object that can listen for a MIDI command. A class
derived from
* MidiListener can be registered with the Midi object as a command listener using
RegisterListener.
*/
class MidiListener
{
private:
MIDI_COMMAND command;
protected:
/**
* Constructor.
*/
MidiListener(MIDI_COMMAND cmd);
public:
/**
* Get the type of MIDI command that this object is listening to.
*/
MIDI_COMMAND GetCommandType();
/**
* Pass on a command to the listener. This should only be called by the Midi
class in response to
* a 3-byte command message being received. This function will call the
appropriate function in
* the concrete listener class (e.g. DoNoteOn, DoPitchBend).
*/
void ProcessCommand(midi_param_t* param);
};

/**
P a g e | 49

* Abstract class representing an object that can listen for MIDI note on commands. A
class derived
* from MidiNoteOnListener can be registered with the Midi object as a listener for
note on commands.
*/
class MidiNoteOnListener : public MidiListener
{
protected:
/**
* Constructor.
*/
MidiNoteOnListener();
public:
/**
* This method is called by the Midi class when it receives a note on message.
* \param note The note byte that was sent as an argument with the MIDI note on
message (0x00 - 0x7F).
* \param velocity The velocity byte that was sent as an argument with the MIDI
note on message (0x00 - 0x7F).
*/
virtual void DoNoteOn(uint8_t note, uint8_t velocity) = 0;
};

/**
* Abstract class representing an object that can listen for MIDI pitch bend commands.
A class derived
* from MidiPitchBendListener can be registered with the Midi object as a listener for
pitch bend commands.
*/
class MidiPitchBendListener : public MidiListener
{
protected:
/**
* Constructor.
*/
MidiPitchBendListener();
public:
/**
* This method is called by the Midi class when it receives a pitch bend
message.
* \param pitch The pitch bend value that was sent as an argument with the MIDI
pitch bend message
* (0x0000 - 0x4000).
*/
virtual void DoPitchBend(int16_t pitch) = 0;
};

#endif
P a g e | 50

/*
* midi.c
*
* Created on: 28-Jun-2009
* Author: nrqm
*/

#include "midi.h"

#include "../arduino/WProgram.h"
#include "../arduino/HardwareSerial.h"

#define IS_COMMAND_MSK 0x80 // if this bit is set, then the byte is a command
#define COMMAND_MSK 0xF0 // masks out the command nibble of the command byte
#define CHANNEL_MSK 0x0F // masks out the channel nibble of the command byte

#define NOTE_OFF_MSK 0x80


#define NOTE_ON_MSK 0x90
#define AFTERTOUCH_MSK 0xA0
#define CONTROLLER_MSK 0xB0
#define PATCH_CHNG_MSK 0xC0
#define CHAN_PRESS_MSK 0xD0
#define PITCH_BEND_MSK 0xE0

extern void print(const char* str);

Midi::Midi() :
last_command(NOTE_OFF_MSK)
{
uint8_t i;
uint8_t j;
for (i = 0; i < num_commands; i++)
{
for (j = 0; j < max_listeners; j++)
{
listeners[i][j] = NULL;
}
}
Serial.begin(31250);
}

Midi* Midi::GetInstance()
{
static Midi instance = Midi();
return &instance;
}

void Midi::RegisterListener(MidiListener* listener)


{
uint8_t i;

// num_commands is the number of unique MIDI commands that the protocol


defines; defined in midi.h
if (listener->GetCommandType() >= 0 && listener->GetCommandType() <
num_commands)
{
// Find a free space in the listener's command chain.
for (i = 0; i < max_listeners; i++)
{
if (this->listeners[listener->GetCommandType()][i] == NULL)
{
P a g e | 51

break;
}
}
// Make sure that the space is really free (and not just the last space),
and set it to the listener object.
if (this->listeners[listener->GetCommandType()][i] == NULL)
{
this->listeners[listener->GetCommandType()][i] = listener;
}
}
}

uint8_t Midi::BufferContainsImplicitCommand()
{
uint8_t byte1 = 0;
uint8_t byte2 = 0;
uint8_t result = 0;
if (buffer.Size() != 2)
{
return 0;
}
byte1 = buffer.Remove();
byte2 = buffer.Remove();
if ((byte1 & IS_COMMAND_MSK) != 0 || (byte2 & IS_COMMAND_MSK) != 0)
{
// Then one of the bytes was a command, and we don't have a valid
argument.
result = 0;
}
else
{
// Then both the bytes are data bytes, sent without a command byte (i.e.
the last command,
// if it was NOTE_ON or PITCH_BEND, is implied).
result = 1;
}
buffer.Add(byte1);
buffer.Add(byte2);
return result;
}

void Midi::NotifyListeners(uint8_t byte1, uint8_t byte2)


{
midi_param_t param;
uint8_t i = 0;

switch (last_command)
{
case NOTE_ON_MSK:
param.note_on.note = byte1;
param.note_on.velocity = byte2;
for (i = 0; i < max_listeners; i++)
{
if (listeners[NOTE_ON][i] != NULL)
{
listeners[NOTE_ON][i]->ProcessCommand(&param);
}
}
break;
case PITCH_BEND_MSK:
param.pitch_bend.pitch = byte1; // put byte1 into the 16 bit int
so that it can be shifted
P a g e | 52

param.pitch_bend.pitch = (param.pitch_bend.pitch << 7) | byte2;


// arguments are 7 bits!
for (i = 0; i < max_listeners; i++)
{
if (listeners[PITCH_BEND][i] != NULL)
{
listeners[PITCH_BEND][i]->ProcessCommand(&param);
}
}
break;
default:
break;
}
}

uint8_t Midi::CheckMidi()
{
uint8_t byte1 = 0;
uint8_t byte2 = 0;
uint8_t command_is_implicit = 0;

if (Serial.available() == 0)
{
return 0;
}

buffer.Add(Serial.read());

command_is_implicit = BufferContainsImplicitCommand();

if (!command_is_implicit && (buffer.Size() != 3 || (buffer.Peek() &


IS_COMMAND_MSK) == 0))
{
// If the buffer doesn't contain a complete implicit command, and the
head of the ring isn't an
// explicit command byte (or it is an explicit command byte and the ring
isn't full yet) then do nothing.
return 0;
}

// At this point the buffer probably contains a complete command message. If


the command byte is explicitly
// included in the message, then it has to be removed from the buffer before
the argument bytes can
// be accessed. If the command is implicit, then the argument bytes are at the
head of the ring.
if (!command_is_implicit)
{
last_command = buffer.Peek() & COMMAND_MSK; // extract the
command nibble without losing the byte
last_channel = buffer.Remove() & CHANNEL_MSK; // extract the channel
nibble and remove the byte
}
byte1 = buffer.Remove(); // remove the next
two bytes, which will be arguments
byte2 = buffer.Remove(); // buffer should
now be empty

// make sure that the argument bytes aren't commands (could happen if a command
with 0 or 1 parameter bytes
// is received)
if ((byte1 & IS_COMMAND_MSK) != 0 || (byte2 & IS_COMMAND_MSK) != 0)
{
P a g e | 53

buffer.Add(byte1);
buffer.Add(byte2);
return 0;
}

NotifyListeners(byte1, byte2);

return 1;
}

MidiListener::MidiListener(MIDI_COMMAND cmd) :
command(cmd)
{
Midi::GetInstance()->RegisterListener(this);
}

MIDI_COMMAND MidiListener::GetCommandType()
{
return command;
}

void MidiListener::ProcessCommand(midi_param_t* param)


{
switch (command)
{
case NOTE_ON:
((MidiNoteOnListener*)this)->DoNoteOn(param->note_on.note, param-
>note_on.velocity);
break;
case PITCH_BEND:
((MidiPitchBendListener*)this)->DoPitchBend(param->pitch_bend.pitch);
break;
default:
break;
}
}

MidiNoteOnListener::MidiNoteOnListener() :
MidiListener(NOTE_ON)
{}

MidiPitchBendListener::MidiPitchBendListener() :
MidiListener(PITCH_BEND)
{}
P a g e | 54

/*
* ThreeByteRing.h
*
* Created on: 29-Jun-2009
* Author: nrqm
*/

#ifndef THREEBYTERING_H_
#define THREEBYTERING_H_

#include <avr/io.h>

class ThreeByteRing
{
private:
uint8_t ring[3];
uint8_t size;
int8_t head_index;
int8_t tail_index;
public:
ThreeByteRing();

/**
* Add an integer to the ring buffer. If the buffer is full, then the oldest
byte is dropped from the
* buffer head.
*/
void Add(uint8_t value);

/**
* Remove the byte from the buffer head.
*/
uint8_t Remove();

/**
* Retrieve the byte at the buffer head without removing it.
*/
uint8_t Peek();

/**
* Get the number of bytes in the buffer (0 to 3).
*/
uint8_t Size();
};

#endif /* THREEBYTERING_H_ */
P a g e | 55

/*
* ThreeByteRing.cpp
*
* Created on: 29-Jun-2009
* Author: nrqm
*/

#include "ThreeByteRing.h"
#include <avr/io.h>

ThreeByteRing::ThreeByteRing() :
size(0),
head_index(-1),
tail_index(-1)
{

void ThreeByteRing::Add(uint8_t value)


{
if (head_index == -1 || tail_index == -1)
{
// The ring is empty
head_index = 0;
tail_index = 0;
}
else
{
// Increment the tail. If it exceeds the bounds of the ring, wrap the
tail around to the front.
// If it hits the head, increment the head (and wrap it around if
necessary).
tail_index++;
if (tail_index == 3)
{
tail_index = 0;
}
if (tail_index == head_index)
{
head_index++;
if (head_index == 3)
{
head_index = 0;
}
}
}
ring[tail_index] = value;
// update the size
if (size == 3)
{
size = 1;
}
else
{
size++;
}
}

uint8_t ThreeByteRing::Remove()
{
uint8_t retval;
P a g e | 56

if (head_index == -1 || tail_index == -1)


{
// The ring is empty. TODO: error?
return 0;
}
retval = ring[head_index];
if (head_index == tail_index)
{
// There was just one item in the ring, so removing it will leave an
empty ring.
head_index = -1;
tail_index = -1;
}
// Increment the head, and wrap it around if it exceeds the bounds of the ring.
head_index++;
if (head_index == 3)
{
head_index = 0;
}
// update the size
size--;

return retval;
}

uint8_t ThreeByteRing::Peek()
{
if (head_index == -1 || tail_index == -1)
{
// TODO: error?
return 0;
}
return ring[head_index];
}

uint8_t ThreeByteRing::Size()
{
return size;
}
P a g e | 57

/*
* solenoid.h
*
* Created on: 8-Jul-2009
* Author: nrqm
*/

#ifndef SOLENOID_H_
#define SOLENOID_H_

#include "../midi/midi.h"

/**
* The type of drum that a solenoid is to strike. Currently only the main drum is
used.
*/
typedef enum _dt
{
MAIN=0,
BASS=1,
HI_HAT=2,
} DRUM_TYPE;

/**
* Represents the solenoid that, when activated with a PWM signal, strikes a drum
(directly or with a drum stick).
*/
class Solenoid : public MidiNoteOnListener
{
private:

/// The type of drum this solenoid is to strike (bass, main, or hi-hat)
DRUM_TYPE type;
/// The maximum duty cycle that can be applied to the solenoid to avoid
damaging the drum.
uint8_t max_duty_cycle;
/// The current PWM duty cycle being applied to the solenoid.
uint8_t duty_cycle;
/// The Arduino pin on which the solenoid's PWM signal is output.
uint8_t pwm_pin;
/// The modulus (mod 3) of the note value (the parameter to DoNoteOn) to which
this solenoid responds.
/// For example, note % 3 == note_divisor ---> this solenoid is triggered.
uint8_t note_divisor;
/// Stores the timer value when a solenoid strike starts (in ms).
uint16_t strike_start_time;
/// The maximum number of ms that a strike can last (the actual strike time is
controlled by the pot).
/// Must be less than 0xFF.
const uint8_t strike_timeout;

/**
* Constructor.
* \param type The type of drum that this solenoid is to strike (bass, main, or
hi-hat). This determines
* the maximum duty cycle that can be applied to the solenoid, the
Arduino pins used for I/O, and
* the range of MIDI notes that the solenoid responds to.
*/
Solenoid(DRUM_TYPE type);
P a g e | 58

protected:
/**
* Respond to a MIDI note on command by activating this solenoid to strike its
drum.
* \param note The note (0-127) that was sent over MIDI. This determines which
drum is to be struck.
* \param velocity The force (0-127) with which to strike the drum. This is
proportional to the PWM
* duty cycle that is sent to the solenoid.
*/
void DoNoteOn(uint8_t note, uint8_t velocity);
public:
/**
* Get the Solenoid instance for a type of drum.
*/
static Solenoid* GetInstance(DRUM_TYPE type);

/**
* Check to see if PWM needs to be turned off. This should be called from the
application main loop
* as often as possible.
*/
void CheckSolenoid();
};

#endif /* SOLENOID_H_ */
P a g e | 59

/*
* solenoid.cpp
*
* Created on: 8-Jul-2009
* Author: nrqm
*/

#include "../arduino/WProgram.h"
#include "solenoid.h"

#define BASS_PWM_PIN 3 // PWM output pin for the bass drum.


#define MAIN_PWM_PIN 5 // PWM output pin for the main drum.
#define HIHAT_PWM_PIN 6 // PWM output pin for the hi-hat cymbal.

#define BASS_POT_PIN 0
#define MAIN_POT_PIN 2
#define HIHAT_POT_PIN 4

#define MAX_DUTY_CYCLE 0xFF

extern void print(const char* str);

extern uint16_t millis16();

Solenoid::Solenoid(DRUM_TYPE type) :
type(type),
duty_cycle(0),
strike_start_time(0),
strike_timeout(50)
{
switch (type)
{
case BASS:
max_duty_cycle = MAX_DUTY_CYCLE;
note_divisor=0;
pwm_pin = BASS_PWM_PIN;
break;
case MAIN:
max_duty_cycle = MAX_DUTY_CYCLE;
note_divisor=1;
pwm_pin = MAIN_PWM_PIN;
break;
case HI_HAT:
max_duty_cycle = MAX_DUTY_CYCLE;
note_divisor=2;
pwm_pin = HIHAT_PWM_PIN;
break;
default:
break;
}
pinMode(pwm_pin, OUTPUT);
analogWrite(pwm_pin, 0);
}

Solenoid* Solenoid::GetInstance(DRUM_TYPE type)


{
static Solenoid bass = Solenoid(BASS);
static Solenoid main = Solenoid(MAIN);
static Solenoid hihat = Solenoid(HI_HAT);
switch (type)
{
P a g e | 60

case BASS:
return &bass;
case MAIN:
return &main;
case HI_HAT:
return &hihat;
default:
return NULL;
}
}

void Solenoid::DoNoteOn(uint8_t note, uint8_t velocity)


{
static const uint8_t scale_factor = 0xFF; // this multiplies the velocity
so that we can do precise integer division
uint16_t velocity_ratio = 0;
uint16_t scaled_duty_cycle = 0;
if (note % 3 != note_divisor)
{
// This solenoid isn't listening for the note, so ignore the message.
return;
}
if (velocity == 0)
{
// Some controllers send "note on" commands with velocity 0 in place of
of "note off" commands. We want to
// ignore those so as not to terminate the strike prematurely when the
note is release on such a device.
return;
}
// Calculate the duty cycle that corresponds to the given velocity, by taking
the ratio of the velocity
// to the maximum velocity, and multiplying that by the maximum duty cycle.
The operations are done on scaled
// 16-bit integers so that data aren't aliased by integer division.
velocity_ratio = (velocity * scale_factor) / 0x7F; // max velocity is
0x7F
scaled_duty_cycle = (velocity_ratio * max_duty_cycle);
duty_cycle = scaled_duty_cycle / scale_factor;

// start timer for timeout


strike_start_time = millis16();

analogWrite(pwm_pin, duty_cycle);
}

void Solenoid::CheckSolenoid()
{
// check to see if the PWM needs to be turned off.
if (duty_cycle != 0 && (millis16() - strike_start_time > strike_timeout))
{
duty_cycle = 0;
analogWrite(pwm_pin, duty_cycle);
}
}
P a g e | 61

/*
* stepper.h
*
* Created on: 7-Jul-2009
* Author: nrqm
*/

#ifndef STEPPER_H_
#define STEPPER_H_

#include "avr/io.h"
#include "../arduino/WProgram.h"
#include "../midi/midi.h"

/**
* The stepper motor object.
*/
class Stepper : public MidiPitchBendListener
{
//private:
public:

/**
* The members of this enumeration have values matching the pin outputs for
each cycle.
*/
/*typedef enum _sts {
CYCLE1=EN0 | L1,
CYCLE2=EN0 | L2,
CYCLE3=EN1 | L3,
CYCLE4=EN1 | L4,
} STEPPER_STATE;*/
typedef enum _sts {
CYCLE1=0,
CYCLE2=1,
CYCLE3=2,
CYCLE4=3,
} STEPPER_STATE;

/// The cycle that the stepper currently has charged. There are four cycles to
a step.
STEPPER_STATE state;
/// The maximum number of positions that the stepper can traverse (positions
are one step apart).
uint8_t max_positions;
/// The drumstick's current position. 0 is the centre (default), max_positions
is the rightmost edge.
uint8_t current_position;
/// The position where the drumstick is supposed to be. If this differs from
current_position, the stepper turns.
uint8_t destination_position;
/// The Timer::Now value when the current stepper cycle started (if the stepper
is turning).
uint16_t current_cycle_start_time;
/// The pitch bend value that matches the current destination position.
int16_t current_pitch;
/**
* Constructor. This is a singleton, so get the instance using
Stepper::GetInstance.
*/
Stepper();
P a g e | 62

/**
* Switch the stepper output to a new cycle. This doesn't enforce the state
diagram.
*/
void TransitionToState(STEPPER_STATE new_state);
/**
* Output the appropriate pattern for the current cycle to the output pins.
*/
void OutputCycleCode();
/**
* Traverse through the stepper state diagram such that the stepper moves a
step counterclockwise.
*/
void CycleCCW();
/**
* Traverse through the stepper state diagram such that the stepper moves a
step clockwise.
*/
void CycleCW();
protected:
/**
* Handle a MIDI pitch bend command. This is called through the MidiListener
base class when a new pitch
* bend message comes in.
*/
void DoPitchBend(int16_t new_pitch); // TODO: will this actually override
the public version?
public:
/**
* Retrieve the instance of this singleton class.
*/
static Stepper* GetInstance();
/**
* Check to see if the stepper state needs to be changed. This should be
called from the program's main loop
* as often as possible.
*/
void CheckStepper();
};

#endif /* STEPPER_H_ */
P a g e | 63

/*
* stepper.cpp
*
* Created on: 7-Jul-2009
* Author: nrqm
*/

#include "../arduino/WProgram.h"
#include "stepper.h"

/*
* The Arduino pins on which output patterns are sent to the stepper motor driver
*/

#define EN0 14 // The documentation says to combine the enable lines


on one output
#define EN1 15 // pin, but I've had trouble doing that with other
3.3V boards.
#define L1 16
#define L2 17
#define L3 18
#define L4 19

#define CYCLE_DELAY_MS 20 // The delay between cycle transitions, in


milliseconds
#define PITCH_STEP 0x800 // The difference in pitch bend value between
positions
#define DEAD_ZONE 0x40

extern void print(const char* str);

extern uint16_t millis16();

Stepper::Stepper() :
MidiPitchBendListener(),
state(CYCLE1),
max_positions(4),
current_position(0),
destination_position(0),
current_cycle_start_time(0),
current_pitch(0x2000)
{
pinMode(EN0, OUTPUT);
pinMode(EN1, OUTPUT);
pinMode(L1, OUTPUT);
pinMode(L2, OUTPUT);
pinMode(L3, OUTPUT);
pinMode(L4, OUTPUT);
OutputCycleCode();
}

Stepper* Stepper::GetInstance()
{
static Stepper instance = Stepper();
return &instance;
}

void Stepper::TransitionToState(STEPPER_STATE new_state)


{
// Output the bit pattern for the new cycle to the stepper motor driver. The 6
least-significant bits
P a g e | 64

// are set to 0 so that the new state can be masked in (the MSbs are left
unchanged).
state = new_state;
OutputCycleCode();
current_cycle_start_time = millis16();
}

void Stepper::OutputCycleCode()
{
switch (state)
{
case CYCLE1:
digitalWrite(EN0, HIGH);
digitalWrite(EN1, LOW);
digitalWrite(L1, HIGH);
digitalWrite(L2, LOW);
digitalWrite(L3, LOW);
digitalWrite(L4, LOW);
break;
case CYCLE2:
digitalWrite(EN0, HIGH);
digitalWrite(EN1, LOW);
digitalWrite(L1, LOW);
digitalWrite(L2, HIGH);
digitalWrite(L3, LOW);
digitalWrite(L4, LOW);
break;
case CYCLE3:
digitalWrite(EN0, LOW);
digitalWrite(EN1, HIGH);
digitalWrite(L1, LOW);
digitalWrite(L2, LOW);
digitalWrite(L3, HIGH);
digitalWrite(L4, LOW);
break;
case CYCLE4:
digitalWrite(EN0, LOW);
digitalWrite(EN1, HIGH);
digitalWrite(L1, LOW);
digitalWrite(L2, LOW);
digitalWrite(L3, LOW);
digitalWrite(L4, HIGH);
break;
}
}

void Stepper::CycleCCW()
{
switch (state)
{
case CYCLE4:
TransitionToState(CYCLE2);
break;
case CYCLE2:
TransitionToState(CYCLE3);
break;
case CYCLE3:
TransitionToState(CYCLE1);
break;
case CYCLE1:
TransitionToState(CYCLE4);
current_position--; // Step has completed.
break;
P a g e | 65

default:
break;
}
}

void Stepper::CycleCW()
{
switch (state)
{
case CYCLE1:
TransitionToState(CYCLE3);
break;
case CYCLE3:
TransitionToState(CYCLE2);
break;
case CYCLE2:
TransitionToState(CYCLE4);
break;
case CYCLE4:
TransitionToState(CYCLE1);
current_position++; // Step has completed
break;
default:
break;
}
}

void Stepper::DoPitchBend(int16_t new_pitch)


{
int16_t old_pitch = current_pitch;
int8_t pitch_diff = 0;

if (new_pitch < current_pitch - DEAD_ZONE)


{
// move a step or more to the left
current_pitch -= ((current_pitch - new_pitch)/PITCH_STEP + 1) *
PITCH_STEP;
}
else if (new_pitch > current_pitch + PITCH_STEP + DEAD_ZONE)
{
// move a step or more to the right
// multiplying by PITCH_STEP/PITCH_STEP masks off some bits to make
current_pitch an integer
// multiple of PITCH_STEP (in this case 1 is not the multiplicative
identity!!!?!?!?!)
current_pitch += ((new_pitch - current_pitch) / PITCH_STEP) * PITCH_STEP;
}
else
{
return;
}

if (current_pitch < 0x2000)


{
// don't allow it to go left past the centre position.
current_pitch = 0x2000;
destination_position = 0;
return;
}

if (old_pitch != current_pitch)
{
// calculate the new position.
P a g e | 66

pitch_diff = (current_pitch - old_pitch) / PITCH_STEP;


if (current_position + pitch_diff > max_positions)
{
// integer underflow case
destination_position = 0;
}
else
{
destination_position += pitch_diff;
}
}
}

void Stepper::CheckStepper()
{
uint16_t now = millis16();
if (now - current_cycle_start_time > CYCLE_DELAY_MS)
{
// If the cycle timer has expired, then move to the next cycle.
if (current_position < destination_position)
{
// Then the stick is to the left of its destination, and needs to
move clockwise.
CycleCW();
}
else if (current_position > destination_position)
{
// Then the stick is to the right of its destination, and needs to
move counterclockwise.
CycleCCW();
}
else
{
// It's all good, the stepper's already there!
}
}
}
P a g e | 67

/*
* MidiInterface.cs
*
* Created on: 12-Aug-2009
* Author: nrqm
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO.Ports;
using System.Threading;

namespace MidiInterface
{
public enum MidiCommand
{
NoteOn,
PitchBend,
}
/// <summary>
/// A class for sending MIDI commands to a serial port.
/// </summary>
public class Midi
{
//TODO: support writing MIDI data to a file?
private MidiCommand lastCommand;
private SerialPort port;
private Mutex serialMutex;

/// <summary>
/// Constructor.
/// </summary>
/// <param name="comPort">The COM port that is to be used as MIDI output (e.g.
"COM7")</param>
public Midi(string comPort)
{
lastCommand = MidiCommand.NoteOn;
port = new SerialPort(comPort, 31250, Parity.None, 8, StopBits.One);
serialMutex = new Mutex();
}

/// <summary>
/// Read the instantaneous contents of the serial receive buffer.
/// </summary>
/// <returns>A string containing the contents of the serial buffer. The
string is empty if there is
/// nothing in the buffer or the serial port is closed.</returns>
public string Receive()
{
string retval = "";
if (port.IsOpen)
{
serialMutex.WaitOne();

retval = port.ReadExisting();

serialMutex.ReleaseMutex();
}
return retval;
P a g e | 68

/// <summary>
/// Send some data to the serial port. If the serial port is closed, nothing
happens.
/// </summary>
/// <param name="bytes">The sequence of bytes to send over the serial
port.</param>
/// <param name="len">The number of bytes in the array.</param>
public void Send(byte[] bytes, int len)
{
serialMutex.WaitOne();

if (port.IsOpen)
{
port.Write(bytes, 0, len);
}

serialMutex.ReleaseMutex();
}

/// <summary>
/// Send a MIDI note on command to the serial port.
/// </summary>
/// <param name="note">The note to send. (0 to 127)</param>
/// <param name="velocity">The velocity with which to strike the note. (0 to
127)</param>
public void SendNote(byte note, byte velocity)
{
int i = 0;
byte[] bytes = new byte[3];
if (lastCommand != MidiCommand.NoteOn)
{
bytes[i] = 0x90; // note on
lastCommand = MidiCommand.NoteOn;
i++;
}
bytes[i] = note;
i++;
bytes[i] = velocity;
i++;

Send(bytes, i);
}

/// <summary>
/// Get or set the last command that was sent. This controls whether an
explicit command byte is sent.
/// </summary>
public MidiCommand LastCommand
{
get
{
return lastCommand;
}
set
{
lastCommand = value;
}
}

/// <summary>
/// Get whether the MIDI interface is connected.
P a g e | 69

/// </summary>
public bool IsConnected
{
get
{
return port.IsOpen;
}
}

/// <summary>
/// Disconnect the MIDI interface.
/// </summary>
public void Disconnect()
{
port.Close();
}

/// <summary>
/// Connect to the MIDI interface.
/// </summary>
public void Connect()
{
port.Open();
}
}
}

You might also like