Arpeggiator, Part III
This post is part of a series of notes and exercises for a summer camp on making musical instruments with Arduino and Pure Data.
The last step of our arpeggiator gives the musician the ability to control the tempo or timing of the notes. We’ll use our last potentiometer for this. Its reading will be used to set the delay between notes. This delay needs to be set in both the Arduino sketch and the Pure Data patch.
Arduino
The duration of each note is going to be needed in a few places, so let’s declare duration
as a global. Make it a short
rather than an int
.
In note_wait
, right after you write the note’s ID, read the value from A2
and assign it to duration
. We’ll use this number directly as the number of milliseconds that each note plays.
Recall that the analog pins report a number in the range [0, 1023]. We need two bytes to hold numbers in this range, and short
s are two bytes wide—on Arduino, at least. Adjust your call to delay
to use this duration
variable.
We must also send the duration out along the serial port so that Pure Data knows when to stop playing the note. But there’s a hitch. The comport
object can only receive bytes, and duration
is a two-byte quantity. We are going to need to do some bit twiddling.
Consider the number 1023. In binary, it has this form:
0000001111111111
Let’s separate the bytes so they are easier to see:
00000011 11111111
Maybe we could just send the number in pieces like this? Yes, that’s what we’ll do.
The byte on the left is called the high-order byte. It holds the digits of the most magnitude. The byte on the right is called the low-order byte. The Arduino functions highByte
and lowByte
will separate duration
into its two parts. Use them and write the high-order byte to the serial port. Then write the low-order byte.
That takes care of the Arduino side of things. Our message size is now 3 bytes. On to Pure Data!
Pure Data
The third inlet of makenote
expects the duration. To get the duration, we need to gather up the three bytes of each message sent in by the Arduino. The last two bytes must be reconstituted back into a two-byte quantity. There are a couple of operators we can use to achieve this. Suppose the high-order byte is 00000011, and the low-order byte is 11111111.
00000011 <- high 11111111 <- low
We can put these bytes back together with this set of operations: (high << 8) | low
. The <<
is called the left-shift operator. The << 8
tells high
to move left 8 bits, shifting it into its correct place and padding the lesser digits with zeroes. That leaves us with this situation:
00000011 00000000 <- high << 8 11111111 <- low
The |
is called a bitwise or. It looks at each pair of corresponding bits in the two numbers. If at least one of the bits is 1, the output bit is 1. Given these two numbers, |
gives us this result:
00000011 11111111
That’s our two-byte quantity!
Accomplish these operations in Pure Data in the following way:
- Delete the connection between
comport
andmakenote
. - Add a
repack 3
object. - Feed the outlet of
comport
intorepack
. - Add an
unpack float float float
object. - Feed the outlet of
repack
into the inlet ofunpack
. - Feed the first outlet of
unpack
into the first inlet ofmakenote
. - Add a
<< 8
object. - Feed the second outlet of
unpack
—the high-order byte—into the first inlet of<< 8
. - Add a
|
object. - Feed the outlet of
<< 8
into the left inlet of|
. - Feed the third outlet of
unpack
—the low-order byte—into the second inlet of|
. - Feed the outlet of
|
into the inlet of the duration’s number box.
There. Now you should be able to set the tempo and hear your arpeggiator speed up and slow down when you crank the third potentiometer!
Challenges
After you get your arpeggiator working, answer the following questions on a piece of scratch paper.
- When you crank the tempo potentiometer down to zero, the arpeggios play too quickly. How might you prevent this?
- Orchestrate in the Arduino sketch a sequence of arpeggios to play in succession. Perhaps you start on a major arpeggio rooted on C, then you switch to a minor arpeggio rooted on D, then you switch to… You might benefit from writing a helper function to play an given arpeggio for a given root note. Compose something that pleases you.