teaching machines

Music Mouse, Part III

June 16, 2019 by . Filed under electronics, music, public.

This post is part of a series of notes and exercises for a summer camp on making musical instruments with Arduino and Pure Data.

Music Mouse walks the horizontal axis in steps of one right now. If we were to interpret the x-position as a MIDI number and move along one halfstep at a time, we’d be walking the chromatic scale. It’s just like walking up a piano one key at a time.

We don’t normally play music in the chromatic scale. Let’s have Music Mouse walk the major scale instead.

Moving Right

When the joystick is pressed right, we want the Music Mouse to get to the next note in the major scale. We’re probably going to need a list of the offsets between the notes of the major scale.

What were those offsets again?
+2 +2 +1 +2 +2 +2 +1

Declare a global array named jumpUps that holds these offsets. When the joystick is pressed right, we want to apply one of these offsets the current x-position. But which offset?

We don’t know. We don’t have enough information to know where we’re at in the scale. So, let’s declare a global int named scale_index_x and assign it 0. We’ll use it to track our current position in the scale.

When we change the x-position, instead of adding dx, add on the offset for our current position in the scale:

x += jumpUps[scale_index_x]; 

Also, advance our position in the scale by adding 1 on to scale_index_x.

Upload and test your code. If you start at 60 and press right, you should advance to 62, 64, 65, 67, 69, 71, 72, and so on.

Wrap Around

When you advance past 72, you probably see some strange results.

Why?
The array has 7 elements in it, and they are identified by indices 0 through 6. The weird behavior starts happening when scale_index_x is 7, which is no longer a valid index into the array. It is accessing memory beyond the end of the array, and who knows what’s in that memory. This is called a buffer overflow error, and it is commonly exploited by ne’er-do-wells to hijack software.

To fix this, we need to have scale_index_x wrap back around to 0. We could implement this wraparound as a conditional statement:

if scale_index_x > 6
  scale_index_x = 0

But there’s an operator that can do this more succinctly. To get a feel for it, visit your instructor’s Mod Walk graph on Desmos, add a row and set $x_1$ to 1. Observe how $y$ is calculated and the pair is plotted. Keep adding rows for all $x_1$ in [0, 25] or so. (Hit return on the bottom row and Desmos will automatically increment.)

See how this mod function wraps our progression? Much like how a clock wraps around after it hits 12? In C++, we can apply this wrapping to scale_index_x using the modulus operator:

scale_index_x = (scale_index_x + 1) % 7;

We add 1 and then wrap back around to 0 if we’ve reached 7 or greater. We read % 7 as “mod 7”.

Add this wraparound and test your code. Does it effectively start the scale over as you advance past 72?

Moving Left

Okay, we’ve got the joystick’s right action working.

What happens when you press left?
The numbers still go up. We must fix this. We want them to go down.

Declare a global array named jumpDowns. Assign it a list of seven numbers that describe how many halfsteps we should jump down to get to the previous note in the major scale.

What are those numbers?
1 2 2 1 2 2 2

The assignment to x needs to get a bit more complicated. We have to choose between jumping up and jumping down. This sounds like a conditional statement, and we’ll use dx to arbitrate between our choices. Update your code to match this pseudocode:

if old_dx and dx are different
  if we're not idling
    if we're going right
      x = x + jumpUps[scale_index_x]
      increment scale_index_x with wraparound
    else
      x = x - jumpDowns[scale_index_x]
      decrement scale_index_x with wraparound
  old_dx = dx

For the decrementing, you might have written this:

scale_index_x = (scale_index_x - 1) % 7;

Sadly, the mod operator doesn’t quite do what we want for negative numbers. In particular, it yields negative numbers, which are inappropriate for indexing into an array. Consider the following table that shows what values we want and what % actually yields:

value value % 7 what we want
6 6 6
5 5 5
4 4 4
3 3 3
2 2 2
1 1 1
0 0 0
-1 -1 6
-2 -2 5
-3 -3 4
-4 -4 3
-5 -5 2
-6 -6 1

What expression takes us from the left column to the right column?
It looks we can just add 7 to the middle column to get the values in the right column. Let’s try it:

value value % 7 + 7 what we want
6 13 6
5 12 5
4 11 4
3 10 3
2 9 2
1 8 1
0 7 0
-1 6 6
-2 5 5
-3 4 4
-4 3 3
-5 2 2
-6 1 1

Bummer. Adding 7 worked for the negative numbers, but not the positive numbers. Here’s a little trick. Instead of adding 7 after the %, let’s add it before. Examine this table to prove to yourself that this works:

value value + 7 (value + 7) % 7 what we want
6 13 6 6
5 12 5 5
4 11 4 4
3 10 3 3
2 9 2 2
1 8 1 1
0 7 0 0
-1 6 6 6
-2 5 5 5
-3 4 4 4
-4 3 3 3
-5 2 2 2
-6 1 1 1

So our assignment statement for moving left is this:

scale_index_x = (scale_index_x - 1 + 7) % 7;