teaching machines

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. You would probably keep hearing it long after it stopped.

Instead, let’s program in a palette of sequences. The arpeggiator will play one of them at a time and switch to another when 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 = 6;
const int nsequences = 2;

int sequences[nsequences][nsteps] = {
{0, 4, 7, 12, 7, 4}, // major
{0, 3, 7, 12, 7, 3}  // minor
};


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 note_wait that has this shape:

void note_wait(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. You pick the number of milliseconds. 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) with analogRead. Assign the value (which is in the range [0, 1023]) to an int variable.
• Use map to scale the number to a reasonable range. You want this potentiometer to set the MIDI number of the arpeggio’s root.
• Write a for loop that walks from 0 to nsteps.
• On each iteration of the for loop, call note_wait to produce note i of sequence 0. The ID of the note is the root’s MIDI number plus sequences[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) with analogRead. Assign the value to a variable as before.
• Create a global arpeggio_id variable to hold the index of the currently selected arpeggio. Initialize it to 0.
• Use map to scale the number to one of the legal arpeggio identifiers. The major arpeggio is 0, and the minor arpeggio is 1. My first attempt was to write map(reading, 0, 1023, 0, nsequences - 1), but this doesn’t partition the range into buckets of equal size. Use this call instead: map(reading, 0, 1024, 0, nsequences).
• Alter your note_wait call to use the arppegio 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

Let’s write a Pure Data patch to make these arpeggio mountains audible. Follow these steps:

• Add the standard serial port messages and comport 9600 object.
• Add a float or number box for each note’s velocity. Set it to something non-zero.
• Add a number box for the notes’ duration. Set it to the same number of milliseconds that you delayed in the note_wait 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 the makenote 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 of noteout. This is the note’s MIDI number.
• Feed the second outlet of makenote into the second inlet of noteout. 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.

• 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 and nsteps to reflect your choices. What sequences did you choose? Be prepared to share a performance with the group.