Thursday, February 25, 2016

Shout-Outs

Here are some links to some great wind controller projects, found by Onyx Ashanti:

Open Horn MIDI System (OHMS)

Gwen's MIDI Controller


Note Selection Basics


In my previous posts, I've focused on detecting note-on and note-off transitions from the breath controller, and on mapping the breath intensity to a MIDI continuous controller to control expressive parameters. But the circuits and code have never been able to play more than one note. In this post, I'll discuss how to read a set of finger inputs, like the keys on a woodwind instrument, or the valves of a trumpet.

To keep things simple, I'll start with 3 finger inputs, like found on a trumpet. For a B-flat trumpet, the 3 fingers can be used to play 7 different notes:


Index
Middle
Ring
Note Produced
Up
Up
Up
B-flat
Up
Down
Up
A
Down
Up
Up
A-flat
Down
Down
Up
G
Up
Down
Down
G-flat
Down
Up
Down
F
Down
Down
Down
E




Readers familiar with binary numbers may be saying, "wait, with 3 binary digits, you should be able to produce 2^3, or 8 distinct finger combinations." And you would be right, except that the fingering combination [Index=up, Middle=Up, Ring=Down] produces an "in-between-pitch" note in the lower registers of the trumpet, and is not used.

A Real Instrument


For those of you unfamiliar with how a trumpet works, most have 3 piston valves which re-route the flow of air through the instrument to change the effective length, and therefore the resonant frequency, of the instrument.

(Photo: Wikimedia Commons: https://upload.wikimedia.org/wikipedia/commons/9/9d/Trumpet_1.jpg)

A natural way to simulate the operation of a piston valve like this is to use a momentary switch, so let's wire something up. I've used a small tactile switch like the following:

(Photo: Wikimedia Commons: https://upload.wikimedia.org/wikipedia/commons/3/35/BUTA-06-X-STAN-01.jpg)

These tactile switches are widely used in consumer electronics, and are very inexpensive.e.g. $0.20 each. They are available from all the major parts suppliers.

I've wired up three of these on a breadboard, spaced approximately finger-width apart:



Now, to be clear, no actual trumpet player would find this thing pleasant to play, as the "feel" of the tactile switches is very different from the feel of an actual piston valve. But it will let us start to experiment with how we can simulate real-world musical instrument properties like trumpet valves and woodwind keys.

To connect these switches to your microcontroller, first figure out which pins are connected when the switch is pressed. If you are using the tactile switches like I am, there are 4 pins on each one, arranged in two pairs. If you find that the switch appears to be always pressed, you may need pull the switch out of the breadboard, rotate it 90 degrees, and re-insert it.

Once the switches are attached to the breadboard, connect one side of each switch to a common wire (this will attach to the ground on your microcontroller). The other side of each switch should be brought out on a separate wire. In total, there will be 4 connections to the microcontroller, like this:


Once you have these wired up, attach Common to the Ground (GND) pin on your microcontroller, and the Index, Middle, and Ring (finger) switch wires to three digital input pins. I've attached mine to digital inputs 2, 3, and 4 on my Teensy microcontroller. If you have yours attached to different input pins, you'll need to modify the code examples below accordingly.

One thing to be aware of is that we're pulling the digital inputs low when the buttons are pressed. So the "valve" will read HIGH when the button is not pressed and LOW when pressed.

Mapping Inputs to MIDI Notes

Now that we have our "trumpet valves" plugged into our microcontroller, how do we get it to send different MIDI notes to our synthesizers according to which switches are pressed?

One approach might be to use a bunch of if/then statements to decide which note to select. It might look like:

if (switch one pressed) {
    if (switch two pressed) {
        if (switch three pressed) {
            send E
        } else {
            send G
        }
    } else {
        // switch two is NOT pressed
        if (switch three pressed) {
            send F
        } else {
            send A-flat
        }
etc etc

As you can see, this is pretty hard to read, and it's pretty easy to make a mistake. Plus, imagine how horrifying this code would be if you were trying to build a woodwind controller, which could have as many as 14 or 15 switches.

A better approach is to use binary numbers to map any combination of the switch on/off states to a number. For our trumpet, we only need 3 bits, so all the possible key combinations, and the corresponding MIDI note, can be represented in a new column in our fingering table

Index
Middle
Ring
Note Produced
Binary (Decimal) Value
Up
Up
Up
B-flat
000 (0)
Up
Down
Up
A
010 (2)
Down
Up
Up
A-flat
011 (3)
Down
Down
Up
G
001 (1)
Up
Down
Down
G-flat
100 (4)
Down
Up
Down
F
101 (5)
Down
Down
Down
E
111 (7)

Now that ugly code for deciding which note to play can be replaced with:

if (value = 0) {
    send B-flat
} else if (value == 2) {
    send A
} else if (value == 3) {
    send A-flat
} else if (value == 1) {
    send G
} else if (value == 4) {
    send G-flat
} else if (value == 5) {
    send F
} else if (alue == 7) {
    send E
}
We'll actually use a C-language case statement in the sample code, and we'll express the numbers in binary instead of decimal, like this:

switch (value) {
    case 0:
        send B-flat;
        break;
    case 2:
        send A;
        break

and so on.

So let's go ahead and write the sketch for our trumpet. To do that, we'll replace the get_note() function with a new one:

int get_note() {
  // This routine reads the three buttons that are our "trumpet
  // valves", and returns a MIDI note.
  int value = (digitalRead(2) << 2) +
      (digitalRead(3) << 1) +
      digitalRead(4);
  // Since the digital input pins are pulled low when the
  // button is pressed, the "all valves open" value will
  // be 111 (binary) or 7 (decimal). Full table:
  // v1     v2     v3      note             value (decimal)
  // open   open   open    C                7
  // open   closed open    B                5
  // closed open   open    B-flat/A-sharp   3
  // closed closed open    A                1
  // open   closed closed  A-flat/G-sharp   4
  // closed open   closed  G                2
  // closed closed closed  G-flat/F-sharp   0
  // open   open   close   not used         6
  // 
#define BASE 60  // The base MIDI note value
  switch (value) {
    case 7:
      return BASE;
      break;
    case 5:
      return BASE - 1;
      break;
    case 3:
      return BASE - 2;
      break;
    case 1:
      return BASE - 3;
      break;
    case 4:
      return BASE - 4;
      break;
    case 2:
      return BASE - 5;
      break;
    case 0:
      return BASE - 6;
      break;
    default:
      // If invalid fingering, ignore and return the
      // currently sounding note.  
      return noteSounding;
  }
}

What's going on here?


int value = (digitalRead(2) << 2) +
      (digitalRead(3) << 1) +
      digitalRead(4);

Here, we're reading the values of the three buttons into a single integer value, using bit shift operators. Let's break it down:

First, we declare a variable named "value". For all practical purposes, that starts off as zero, and in binary that is:

00000000

which is 8 bits of binary zeros. 

(Yes, nerds, it's actually a 32-bit signed integer, but that's not important to this discussion)

When we do digitalRead(2) << 2

we're saying "read digital input two and shift the whole value to the left two bits. So if we read 00000001, we'd shift that two bits to the left, so we end up with:

00000100

Then we read digital input three. Suppose we also read a HIGH, and shift left one bit, that ends up being:

00000010

Finally we read input 4, but don't shift it at all. So if we get a HIGH on pins 4, we have:

00000001

Now, we add the following three binary numbers:

00000100
00000010
00000001

and we get:

00000111

So that means the three digital I/O pin we're using are giving the value 1. As I mentioned before, we're taking the digital I/O pins low with button presses, so this binary value 00000111 means no buttons are pressed.

In order to make pulling I/O pins low to detect button pressed, we also need to add pullups to the digitnal I/O pins. The Teensy has support for that built-in (otherwise we'd need to attach some resistors between the digital pin and the power rail). We'll add the following code (in blue) to the setup() function:

void setup() {
  state = NOTE_OFF;  // initialize state machine
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  pinMode(4, INPUT_PULLUP);
}

We'll also need to change the main loop a little bit to handle the situation where the player chooses a new fingering but keeps blowing. In that case we need to turn off the old note and turn on the new one. So we'll add the following code in the block where we're handing the case where a note is on (new code in blue):

void loop() {
  // read the input on analog pin 0
  sensorValue = analogRead(A0);
  if (state == NOTE_OFF) {
    if (sensorValue > NOTE_ON_THRESHOLD) {
      // Value has risen above threshold. Move to the RISE_TIME
      // state. Record time and initial breath value.
      breath_on_time = millis();
      initial_breath_value = sensorValue;
      state = RISE_WAIT;  // Go to next state
    }
  } else if (state == RISE_WAIT) {
    if (sensorValue > NOTE_ON_THRESHOLD) {
      // Has enough time passed for us to collect our second
      // sample?
      if (millis() - breath_on_time > RISE_TIME) {
        // Yes, so calculate MIDI note and velocity, then send a note on event
        noteSounding = get_note();
        int velocity = get_velocity(initial_breath_value, sensorValue, RISE_TIME);
        usbMIDI.sendNoteOn(noteSounding, velocity, MIDI_CHANNEL);
        state = NOTE_ON;
      }
    } else {
      // Value fell below threshold before RISE_TIME passed. Return to
      // NOTE_OFF state (e.g. we're ignoring a short blip of breath)
      state = NOTE_OFF;
    }
  } else if (state == NOTE_ON) {
    if (sensorValue < NOTE_ON_THRESHOLD) {
      // Value has fallen below threshold - turn the note off
      usbMIDI.sendNoteOff(noteSounding, 100, MIDI_CHANNEL);  
      state = NOTE_OFF;
    } else {
      // Is it time to send more aftertouch data?
      if (millis() - atSendTime > AT_INTERVAL) {
        // Map the sensor value to the aftertouch range 0-127
        atVal = map(sensorValue, NOTE_ON_THRESHOLD, 1023, 0, 127);
        usbMIDI.sendAfterTouch(atVal, MIDI_CHANNEL);
        atSendTime = millis();
      }
    }
    int newNote = get_note();
    if (newNote != noteSounding) {
      // Player has moved to a new fingering while still blowing.
      // Send a note off for the current node and a note on for
      // the new note.
      usbMIDI.sendNoteOff(noteSounding, 100, MIDI_CHANNEL);
      noteSounding = newNote;
      int velocity = get_velocity(initial_breath_value, sensorValue, RISE_TIME);
      usbMIDI.sendNoteOn(noteSounding, velocity, MIDI_CHANNEL);
    }
  }
}

Here's a short video of the sketch in action:



Here's the entire sketch, available at https://github.com/ggood/NoteSelectionTutorial


Thanks for reading. If there's anything you would like me to address in future posts, leave a comment. Next up: how to play more than the seven notes this instrument is capable of playing.