Arpeggiator, Part II
This post is part of a series of notes and exercises for a summer camp on making musical instruments with Arduino and Pure Data.
Our first “draft” of the arpeggiator will only use two of the potentiometers. One will decide the root note, and the other will decide which of many possible sequences to walk through.
Sequences
We could make our arpeggiator just play the same sequence over and over again. Imagine listening to the C major scale go up and down for several hours on end. We don’t want that. Not only is it repetitive, it doesn’t really need a musician.
Instead, let’s program in a palette of sequences. The arpeggiator will play one of them at a time, and then maybe switch to another if the musician so chooses. We’ll accomplish this using a 2D array.
Create a new Arduino sketch and add in this code at the top of the file:
const int nsteps = 12;
const int nsequences = 3;
int sequences[nsequences][nsteps] = {
{0, 2, 4, 5, 7, 9, 11, 9, 7, 5, 4, 2}, // major scale
{0, 2, 3, 5, 7, 8, 10, 8, 7, 5, 3, 2}, // minor scale
{0, 3, 5, 6, 7, 10, 12, 10, 7, 6, 5, 3}, // blues scale
};
Those first two variables specify the dimensions of the array. It’s nsequences
tall and nsteps
wide.
Setup
In your setup
function, prepare pins A0
and A1
for INPUT
. We won’t need A2
yet. Also, initialize the serial port.
Note
Remember how we made note
and note2
abstractions in Pure Data? We can make abstractions in C++ as well. In this case, we want an abstraction that writes a note and then delays a bit, allowing the note to finish. Add a new function named notewait
that has this shape:
void notewait(int id) {
// play the note
// wait
}
Play the note by writing its ID to the serial port with Serial.println
. Wait using the delay
function. Remember the number you use; you’ll need it later.
Looping Sequence 0
Most of the work will happen in loop
. Follow these steps to get just sequence 0 arpeggiating:
- Read from the first potentiometer (wired to
A0
) withanalogRead
. Assign the value (which is in the range [0, 1023]) to anint
variable. - Use
map
to scale the number to a more reasonable range. You want this potentiometer to control the root’s MIDI number. - Write a
for
loop that walks from 0 tonsteps
. - On each iteration of the
for
loop, callnotewait
to produce note i of sequence 0. The ID of the note is the root’s MIDI number plussequences[0][i]
. - After the loop, print a blank line to the serial port.
Upload and test your instrument. You should see little arpeggiating mountains going up and down, and turning the potentiometer should alter their root.
Looping Sequence i
Now we’ll add in our second potentiometer to switch to a different sequence. Follow these steps to get all the sequences arpeggiating:
- Read from the second potentiometer (wired to
A1
) withanalogRead
. Assign the value to a variable as before. - Use
map
to scale the number to one of the legal sequence identifiers. In the given sequences above, the major scale is 0, the minor scale is 1, and the blues scale is 2. My first instinct was to writemap(reading, 0, 1023, 0, nsequences - 1)
, but this didn’t chop up the range [0, 1023] into equally sized buckets. Use this call instead:map(reading, 0, 1024, 0, nsequences)
. - Alter your
notewait
call to use the sequence identifier as the first index instead of 0.
Upload and test. You should be able to switch to the different sequences!
When you are confident that the code is working, switch your Serial.println
to Serial.write
.
Pure Data
Watching the arpeggio mountains is much less fun than listening to them. Let’s write a quick Pure Data patch. Follow these steps:
- Add the standard messages and
comport 9600
object. - Add a number box for each note’s velocity. Set it to something non-zero.
- Add a number box for each note’s duration. Set it to the same number of milliseconds that you delayed in the
notewait
function. - Add a
makenote
object. This object starts playing a MIDI note and also schedules it to stop playing. - Feed the MIDI numbers that leave the outlet of
comport
into the first inlet of themakenote
object. - Feed the velocity into the second inlet of
makenote
. - Feed the duration into the third inlet of
makenote
. - Add a
noteout
object. - Feed the first outlet of
makenote
into the first inlet ofnoteout
. This is the note’s MIDI number. - Feed the second outlet of
makenote
into the second inlet ofnoteout
. This is the note’s velocity.
Leave edit mode and open the serial connection. You should now be able to hear your arpeggiator!
Challenges
After you get your arpeggiator working, answer the following questions on a piece of scratch paper.
- Contrast the scales. Write a word that describes the minor scale. Write a word that describes the blues scale.
- Replace the given sequences with sequences of your own. They don’t have to go just up and down. They can be any list of offsets that you choose. They can be short or long. Just remember to update
nsequences
andnsteps
to reflect your choices. What sequences did you choose? Be prepared to share a performance with the group.