teaching machines

Music Mouse, 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.

Music Mouse walks the horizontal axis in steps of one right now. If we were to interpret the x-position as a MIDI number, we’d be walking the chromatic scale, which is really just all the notes that can be found in Western tonal music. Let’s walk the major scale instead.

Scaling Up

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 its notes.

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

Declare a global array named jumpUps that holds these offsets. When right is pressed, we want to take one of these offsets and add it on to x. 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. Then when we change x, we can use scale_index_x to reach into the offsets array:

x += jumpUps[scale_index_x]; 

Also, we need to advance our position in the scale. Add 1 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?
Because scale_index_x 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 perform evil deeds.

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 the graphing calculator Desmos and follow these steps:

  • Click Start Graphing.
  • Click the plus button and insert a new table.
  • Change y1 to mod(x1, 7).
  • In the first row, set x1 to 0 and hit Enter. You should see the value in the right column get automatically calculated and the pair get plotted in the graph.
  • Continue entering values for x1, stepping by 1 each time, until you reach 25 or so.

This mod operator seems to wrap our progression just fine. In C++, we can apply this idea to our advancing scale_index_x:

scale_index_x = (scale_index_x + 1) % 7;

We read % 7 as “mod 7”. This operator attempts to divide the left operand by 7. But instead of yielding the quotient, it yields the remainder. Test your self on some modular arithmetic problems:

  • What’s 9 % 7?
    2. 7 goes into 9 once, and there’s 2 leftover.
  • What’s 11 % 7?
    4
  • What’s 14 % 7?
    0
  • What’s 5 % 7?
    5. 7 goes into 5 not at all, and there’s 5 leftover.
  • What’s 7 % 7?
    0

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

Scaling Down

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.

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 dx isn't 0
    if dx > 0
      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 mod 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 may seem that we can just add 7 to the middle column to get the values in the right column—but this is only true for the negatives. We don’t want to add 7 to the positives.

Instead, we can add 7 before we apply the mod operator. Let’s revise and examine the table to prove this to ourselves:

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 is this:

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

Challenges

Hey, let’s just keep going, okay?

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *