Thursday, April 21, 2016

Building a Woodwind Controller Using the MPR121 Touch Sensor

The Akai EWI uses a set of touch-sensitive metal keys:


A key is actuated simply by touching it. The instrument detects the change in capacitance that results when your body touches the key. For more information on how this works, see https://en.wikipedia.org/wiki/Capacitive_sensing. And for some videos of people playing the EWI, look on YouTube. I especially like this one of the late Michael Brecker: https://www.youtube.com/watch?v=tPUBp9uTLIw

Can I Do This?


Is there a way to inexpensively build this into our own wind controllers? The answer is definitely yes.

NXP Semiconductors manufactures a very low-cost chip, the MPR121 ($1.95 in single quantities), that provides 12 separate touch inputs.
and Adafruit Industries has a nice breakout board that breaks out the tiny pins on the chip, and has some support circuitry that allows use with either 3.3v or 5v controllers.


Sparkfun also has a breakout board for the MPR121. It doesn't include the level shifters to allow 3.3/5v operation, but is less expensive.

I won't write a tutorial on how to wire up the sensor and use it, since the Adafruit tutorial does a great job of that. Instead, I'll show how we can incorporate it into a woodwind-style controller.

One thing that may occur to you is that there are only 12 inputs, but typical woodwinds have more than 12 keys. Is there a way to use more than one touch sensor chip in an instrument? Yes, there is. The MPR121 is an I2C device, which means you can attach more than one to the same two-wire communication bus, as long as they have different addresses. Both the Adafruit and Sparkfun breakouts include a way to set the address (up to 4 different addresses can be set), so it is possible to do touch-sensing on up to 48 keys. That should be enough.

Modifying get_note() For Touch Sensing

In my post, Note Selection Basics, I define a function named get_note() that read the switches that simulated our "trumpet valves" and returns the MIDI note to play. Let's replace that with a new function that can read the MPR121 and detect which of the 12 inputs is being touched.

First, a little background. Adafruit provides a library for the MPR121 that makes configuring and reading it very simple. To initialize the chip:

touchSensor.begin(0x5A);

(0x5A is the default I2C address of the Adafruit breakout)

Then, to read all of the pins:

uint16_t touchValue = touchSensor.touched();

This will return a 16 bit value where each of the lower 12 bits is a 1 if the key is touched, and a 0 if it is not. Which bit corresponds to which key depends on how I wired the brass washers to the touch sensor input pins. In my case, the octave keys are mapped to bits 0 and 1, and then the rest of the keys are:

LH index finger: bit 2
LH ring finger: bit 3
LH middle finger: bit 4
(bit 5 not used - it's an extra key on my instrument not currently used)
RH index finger: bit 6
RH ring finger: bit 7
RH middle finger: bit 8
RH pinkie: bit 9
(bits 10, 11 not used)

Table Lookup for Note Mapping

As I mentioned in the Note Selection Basics post, a C language case statement will start to get pretty ugly for an instrument with all these keys, so let's look into a table lookup approach. We'll build a table that has, in one column, the expected bit values read from the touched() function of the MPR121, and in the next column, the MIDI note to send.

In C, we can define a structure that holds one row of that table like this:

struct fmap_entry {
  uint16_t keys;
  uint8_t midi_note;
};


This is a chunk of memory that can hold a 16-bit value (named "keys", which will hold a bitmap of key values), and an 8-bit MIDI note value.

Next, we'll build an array of these:

#define FMAP_SIZE 33
struct fmap_entry fmap[FMAP_SIZE];

This defines an array (or table, if you prefer) of 33 rows of the structure we define above.

To map from a fingering to a MIDI note, we start at the beginning of this table and check to see if the fingering we just read from the sensors matches the value in the "keys" field. If it does, we've found the MIDI note and we're done. Otherwise, we skip to the next entry, and so on. In programming, this is called a linear search.

We actually need to initialize the "fmap" array with all of the key and note values. That's very verbose, so I'll omit it here and will just include it with the full code.

So here's the revised get_note() function. Since my instrument has an extra key I'm not using, there's a little code (in blue) that makes sure that even if the player touches that unused key, it won't change the value we read, so we don't need to have entries in the table for when that key is touched/not touched.

int get_note() {
  // This routine reads the touch-sensitive keys of the instrument and
  // maps the value read to a MIDI note. We use a lookup table that maps
  // valid combinations of keys to a note. If the lookup fails, this
  // routine returns -1 to indicate that the fingering was not valid.
  int ret = -1;  // Sentinel for unknown fingering
  uint16_t touchValue = touchSensor.touched();


  // Since we're not using the 4th finger of the left hand, mask off that key
  touchValue = touchValue & 0b1111111111011111;


  for (uint8_t i = 0; i < FMAP_SIZE; i++) {
    if (touchValue == fmap[i].keys) {
      ret = fmap[i].midi_note;

      break;
    }
  }

  return ret;
}


The for loop (in red) is where we do the linear search. We look at each keys value, and when we find a match, we stop looking and return the corresponding MIDI note.

If we don't find a match, then the player has their fingers in a non-supported position, and our function returns -1, which means to take no action. In other words, we ignore the glitch. That's not the only thing we could do. If, for example, we wanted to try to mimic how a real instrument behaves, we might send a pitch bend message to indicate that the pitch should be a little sharp or flat relative to the normal fingering. There are a lot of possibilities.

Ok, now that we've had a look at some code, is it possible to build something?

A Test Platform 

I've built a very hacky prototype woodwind-style controller as a platform to test these ideas. The body of the instrument is a 14" length of PVC plumbing pipe, 2" or so in diameter.  I was going for roughly the dimensions of an alto recorder, but chose a thicker tube to make running wires inside a little easier.

To make the touch-sensitive keys, I soldered lengths of wire to ten brass washers:



Then I drilled holes in the tube, ran the wires from the outside to the inside, and hot-glued the washers to the outside of the tube, about where my fingers would fall. I didn't arrange the keys to resemble any particular instrument, although they're somewhat like a recorder layout (albeit with one extra key for the left hand, oops). The Frankenstein-like contraption looks like this:


The white mouthpiece protruding from the top is something I made to use with my Blowchucks Controller for the Electro-Music Festival in 2014. It connects to a tube that runs down the length of the instrument and connects to the Freescale pressure sensor I've been using in all of my wind controller projects.

On the back, I added two octave keys, and strapped all of the electronics near the base. This means I only have a USB cable running from the instrument to my laptop.

In this close up of the electronics, you can see the MPR121 touch sensor breakout on the left. The green and white wires that attach to that board are the wires coming from the brass washers. The board to the right has the Teensy microcontroller mounted on the right, the the pressure sensor on the left. You can also see the power and ground (red/black) wires going to the touch sensor, as well as the two wires (yellow/white) that connect the touch sensor to the I2C bus on the microcontroller.


I'll do a proper video later, but for now, here's a quick audio demo of the instrument running the code below. You'll hear some glitches as I'm not a woodwind player and have a hard time getting multiple fingers to touch keys at exactly the same time. We can de-glitch that stuff in code, and I'll work on that for a future post.




The full code is posted below, and is also available at https://github.com/ggood/NoteSelectionTutorialRecorder


Wednesday, April 20, 2016

Note Selection - How fast do we need to be?

A Different Approach

As you probably noticed, I was lucky enough to have a guest post from Johan Berglund, who showed how to use a simple algorithm that reads all of the switches (keys) of the electronic woodwind, and adds or subtracts semitones from the "center" MIDI note that the instrument produces with no keys pressed.

After reading Johan's post, I thought some of you might be interested in how important it is to have a fast algorithm for map key events to MIDI note on/note off events. For the impatient: it doesn't matter very much. 

Johan's Algorithm

With no keys pressed, Johan's instrument will play a MIDI note 61, which is a C#4 (one half-step above middle C on a piano). Pressing the left-hand index finger switch will lower the pitch by 2 semitones, which means his instrument will produce a MIDI note 59, or a B3 (one half-step below middle C). When more than one key is pressed, all that's needed is to sum all of the effects of each key, although though there are a few exceptions where several keys interact, so some logical AND and NOT operations are needed.

My Lookup Table Approach

By contrast, my approach uses a lookup table. I scan all of the switches, and put the value of each switch (on/off) into a bit array. So if only the left-hand index finger of Johan's instrument is pressed, my bit value would look like 00100000 00000000. If all of the 14 keys in Johan's instrument are pressed, the bit value would look like 00111111 11111111. The leftmost 2 bits are always zero because we're putting these 14 key switch values into a 16-bit number, so 2 bits are always zero.

I maintain a table that has two columns: a fingering bitmask value, and a MIDI note, like this:

Bitmask            MIDI note
...
00100000 00000000  59
...

To figure out which note our instrument should be playing, we read all of the switches and pack them into a bitmask, then start at the beginning of this table and compare the current switch on/off values with the bitmask in the table. When we find a match, we know which MIDI note to produce.

Now, if we naively create a table that has every possible combination of those 14 switches, we'll end up with a table with 2^14 slots, or 16,384 slots. Since the bitmask is 2 bytes (16 bits) and the MIDI note is 1 byte (8 bits), my table will occupy at least 3 * 16,384 = 49,152 byes, or 48 kbytes. That's way more RAM memory than the Arduino Uno has (2 kbytes), so we need to be smarter here.

One trick is to realize that not all fingering combinations need to be considered valid. For example, no saxophone player will use a fingering that includes the ring finger of the left hand and the pinky of the right hand (unless they're doing some sort of weird extended technique). So we can get rid of the majority of the entries in our table because they'll never be used. So, really, we only need as many entries in the table as there are notes that our instrument can produce, plus any alternate fingerings (two ways of playing the same note). Since Johan's instrument produces all 12 semitones of the chromatic scale across two octaves, if we conservatively allow for 3 alternate fingerings for each of those 24 notes, we only need 72 table entries for a total size of 216 bytes. That's more like it!

Let's Race!

Now that we have a compact way to represent all the fingering-to-MIDI note mappings, let's think for a bit about efficiency. In a drag race to convert a fingering to a MIDI note, will Johan's code or my code be faster?

(Caveat: it's been a long time since I took my computer architecture class, so I may bork some of this!)

The Atmel AVR chip in the Arduino Uno has the following timing characteristics, according to https://en.wikipedia.org/wiki/Atmel_AVR_instruction_set:

  • Arithmetic operations take one clock cycle, except for multiplication, which takes two cycles
  • Reading data from memory takes two clock cycles
Armed with this knowledge we can get a rough idea of how many clock cycles each method takes:

Johan's statement:

fingeredNote=startNote-2*LH1-(LHb && !(LH1 && LH2))-LH2-(LH2 && LH1)-2*LH3+LHp1-LHp2+(RHs && !LHp1)-RH1-(RH1 && LH3)-RH2-2*RH3+RHp1-RHp2-2*RHp3+12*OCTup

 I count:
  • 22 memory accesses (44 clock cycles)
  • 5 multiplications (10 clock cycles)
  • 22 arithmetic or logical operations (22 clock cycles)
So Johan's code should take about 76 clock cycles

My code's performance will depend on which fingering is selected. If we get lucky and the fingering the player is using is the first entry in our lookup table, we'll only need to do one comparison. If we're unlucky, we'll need to look through all of the table. Let's consider the worst case, because no musician wants their instrument to slow down when she plays certain notes.

So let's assume we have to look through all 72 entries. Each table lookup involves:
  • 2 memory accesses, to bring the two bytes of the mask into registers (4 clock cycles total)
  • 2 clock cycles to compare those byes
And finally, when we have a hit, there's a memory access to retrieve the MIDI note (2 clock cycles) and one to store it (2 cycles).

So, worst case, the table lookup is 72 * (2 + 2) + (2 + 2)  = 292 clock cycles

Checkered flag to Johan!

So, clearly, Johan's code is more efficient. But how much does it really matter? Let's look at the clock frequency of the ATMega328 chip found in the Arduino Uno - 16 Mhz.

That means that each clock cycle takes 1 / (16 * 10^6) seconds, or about .1 microseconds. Both of our algorithms will execute in less than 3 microseconds (0.000003 seconds), which is really really fast, compared to the 0.1 second delay that a human can perceive.

Understandability Wins

The take-away here is to realize that, in many or most cases where you're reading a sensor in your electronic musical instrument project, and then doing some computation, you will almost never need to worry about the efficiency of your algorithm*. Use the algorithm that is the simplest to implement, and that makes sense to you.

Hope this was helpful! In my next post I'll show how to use a cool capacitive touch sensor you can buy for $8 to make an instrument like the Akai EWI.

- Gordon



 *Computer science nerds: Yes, I'm intentionally omitting algorithms that have quadratic behavior here. On microcontrollers, even if the algorithm is quadratic, it's hard to make n very big, given typical microcontroller memory sizes.
 

Friday, April 15, 2016

Guest Post: Alternative way of note selection (Johan Berglund)

One of the best things about the internet, is the way it brings all of us around the world together. Today, I'd like to bring your a guest post from Johan, with some insights on mapping key fingerings (via switches) to MIDI notes. 
The interesting thing to think about is - how efficient is the code that maps the fingered notes to a MIDI note value. Is it more efficient to take a value and do a bunch of additions/subtractions (Johan's code), or to do a linear search on an array (my code)? Is there a better way? Share your ideas!
Thank you, Johan, for sharing your work!
-Gordon
=============
 
Alternative way of note selection
guest post by Johan Berglund

My idea for a DIY wind controller was to build it small and light with cheap components, an Arduino Pro Mini, a DIN 5 MIDI output and the keys and fingering based on the Akai EWI. Going through the fingering chart in the EWI manual, I discovered that some of the fingerings that I use weren't even listed. How many unlisted fingerings could there be? When playing the EWI it seemed there was more of a logical pattern to the workings of each key than what could be expected if it was looking up combinations in a list. Looking for answers I found the blog of Bret Pimentel, and his post on flexible EWI fingerings.


He had it all figured out. Each key changes the note value a certain number of semitones up or down, some of them with conditions. Let's list them here as variable declarations for our Arduino sketch:

            // Key variables, TRUE (1) for pressed, FALSE (0) for not pressed
byte LH1;   // Left Hand key 1 (pitch change -2)
byte LHb;   // Left Hand bis key (pitch change -1 unless both LH1 and LH2 are pressed)
byte LH2;   // Left Hand key 2  (with LH1 also pressed pitch change is -2, otherwise -1)
byte LH3;   // Left Hand key 3 (pitch change -2)
byte LHp1;  // Left Hand pinky key 1 (pitch change +1)
byte LHp2;  // Left Hand pinky key 2 (pitch change -1)
byte RHs;   // Right Hand side key  (pitch change -2 unless LHp1 is pressed)
byte RH1;   // Right Hand key 1 (with LH3 also pressed pitch change is -2, otherwise -1)
byte RH2;   // Right Hand key 2 (pitch change -1)
byte RH3;   // Right Hand key 3 (pitch change -2)
byte RHp1;  // Right Hand pinky key 1 (pitch change +1)
byte RHp2;  // Right Hand pinky key 2 (pitch change -1)
byte RHp3;  // Right Hand pinky key 3 (pitch change -2)
byte OCTup; // Octave switch key (pitch change +12) 


Ok, so we have straightforward rules for how the note number should change when the keys are pressed. With no keys pressed, it should send a C#, so lets put that in there.

byte startNote=61; // set startNote to C# (change this value in steps of 12 to start in other octaves)

And also a variable for the calculated result.

int fingeredNote; // note calculated from fingering (switches)

To get the keys pressed into their dedicated variables we need to read them from the Arduino pins. In the setup part of our sketch, we set their input pins as digital inputs with pullups. That makes them active low. (To use this with other Arduinos or a Teensy, just change the input pins accordingly. Just make sure you have enough digital pins for the project.)

  pinMode(2, INPUT_PULLUP); // Set inputs with pull-up
  pinMode(3, INPUT_PULLUP); 
  pinMode(4, INPUT_PULLUP); 
  pinMode(5, INPUT_PULLUP); 
  pinMode(6, INPUT_PULLUP); 
  pinMode(7, INPUT_PULLUP); 
  pinMode(8, INPUT_PULLUP); 
  pinMode(9, INPUT_PULLUP); 
  pinMode(10, INPUT_PULLUP); 
  pinMode(11, INPUT_PULLUP); 
  pinMode(12, INPUT_PULLUP);  
  pinMode(14, INPUT_PULLUP); 
  pinMode(15, INPUT_PULLUP); 
  pinMode(16, INPUT_PULLUP);

Reading them will give us a HIGH (1) for the keys that are not pressed and a LOW (0) for the keys that are pressed. To get the value TRUE (1) into our variables for the LOW (0) inputs, and FALSE (0) for the HIGH (1) inputs, we just invert the value with the logical operator NOT. A logical NOT in C is simply a '!' in front of the value that is inverted, like this.

void readSwitches(){  
  // Read  switches and put inverted value in variables
  LH1=!digitalRead(2);
  LHb=!digitalRead(3);
  LH2=!digitalRead(4);
  LH3=!digitalRead(5);
  LHp1=!digitalRead(6);
  LHp2=!digitalRead(7);
  RHs=!digitalRead(8);
  RH1=!digitalRead(9);
  RH2=!digitalRead(10);
  RH3=!digitalRead(11);
  RHp1=!digitalRead(12);
  RHp2=!digitalRead(14);
  RHp3=!digitalRead(15);
  OCTup=!digitalRead(16);
}

Now we will have every variable for a pressed key set to a numerical '1' and the variables for keys not pressed to a numerical '0'. Here comes the tricky bit. We want to combine math and logic to get our fingeredNote right. We begin with our startNote.

fingeredNote=startNote;

Then we take the keys from the top down. First we have LH1. It changes the pitch with -2 semitones, so let's subtract two times the value of LH1 from the startNote.

fingeredNote=startNote-2*LH1;

Next up is the bis key, LHb, and already it gets more tricky. In our notes we stated it should do "pitch change -1 unless both LH1 and LH2 are pressed". How do we do that in C? Another logical operator, AND (&&), will help us do this. AND will make the condition true only if both operands are true, otherwise it's false. So, we subtract 1 (TRUE) only if LHb is pressed AND NOT (LH1 AND LH2). Let's add it to our calculation.

fingeredNote=startNote-2*LH1-(LHb && !(LH1 && LH2));

Ok, now we know how to do this, so the next one will be easy. If LH2 is pressed, subtract 1, and if both LH2 AND LH1 are pressed, subtract another 1.

fingeredNote=startNote-2*LH1-(LHb && !(LH1 && LH2))-LH2-(LH2 && LH1);

The following three keys are simple, just add or subtract each key multiplied with the number of semitones.

fingeredNote=startNote-2*LH1-(LHb && !(LH1 && LH2))-LH2-(LH2 && LH1)-2*LH3+LHp1-LHp2;

Next is the right hand side key, RHs. It should subtract two semitones unless LHp1 is pressed. Not too hard now that we know how to use ! and &&.

fingeredNote=startNote-2*LH1-(LHb && !(LH1 && LH2))-LH2-(LH2 && LH1)-2*LH3+LHp1-LHp2+(RHs && !LHp1);

Not many special cases left now. Just this last one. RH1 should subtract one semitone, but if LH3 is also pressed it should subtract two semitones.

fingeredNote=startNote-2*LH1-(LHb && !(LH1 && LH2))-LH2-(LH2 && LH1)-2*LH3+LHp1-LHp2+(RHs && !LHp1)-RH1-(RH1 && LH3);

After that it's easy as pie. Again just add or subtract each key multiplied with the number of semitones.

fingeredNote=startNote-2*LH1-(LHb && !(LH1 && LH2))-LH2-(LH2 && LH1)-2*LH3+LHp1-LHp2+(RHs && !LHp1)-RH1-(RH1 && LH3)-RH2-2*RH3+RHp1-RHp2-2*RHp3+12*OCTup;

Done! We now have one single line of code that deals with all possible fingerings, just like that.

In the context of my MiniWI Arduino sketch it looks like this (I'm removing the fancy stuff like the joysticks for octaves, modulation and pitch bend for now):

/*
NAME:                 MiniWI Lite Ver.
WRITTEN BY:           JOHAN BERGLUND
CREDITS:              State machine from the Gordophone blog by GORDON GOOD
DATE:                 2016-04-13
FILE SAVED AS:        MiniWI.ino
FOR:                  Arduino Pro Mini, ATmega328
CLOCK:                16.00 MHz CRYSTAL                                        
PROGRAMME FUNCTION:   Wind Controller with EWI style key setup, Freescale MPX5010GP breath sensor 
                      and output to 5-pin DIN MIDI 

HARDWARE NOTES:
* For the MIDI connection, attach a MIDI out Female 180 Degree 5-Pin DIN socket to Arduino.
* Socket is seen from solder tags at rear.
* DIN-5 pinout is:                                         _______ 
*    pin 2 - GND                                          /       \
*    pin 4 - 220 ohm resistor to +5V                     | 1     3 |  MIDI jack
*    pin 5 - Arduino Pin 1 (TX) via a 220 ohm resistor   |  4   5  |
*    all other pins - unconnected                         \___2___/
*
* The Freescale MPX5010GP pressure sensor output (V OUT) is connected to Arduino pin A3.
* Sensor pinout
* 1: V OUT (pin with indent)
* 2: GND
* 3: VCC (to 5V)    
* 4: n/c
* 5: n/c
* 6: n/c
*     
*     
* All key switches connect Ardino digital inputs (with internal pullups) to GND
*/

//_______________________________________________________________________________________________ DECLARATIONS

#define ON_Thr 40       // Set threshold level before switching ON
#define ON_Delay   20   // Set Delay after ON threshold before velocity is checked (wait for tounging peak)
#define breath_max 300  // Blowing as hard as you can
#define modsLo_Thr 411  // Low threshold for mod stick center
#define modsHi_Thr 611  // High threshold for mod stick center
#define octsLo_Thr 311  // Low threshold for octave stick center
#define octsHi_Thr 711  // High threshold for octave stick center

// The three states of our state machine

// No note is sounding
#define NOTE_OFF 1

// We've observed a transition from below to above the
// threshold value. We wait a while to see how fast the
// breath velocity is increasing
#define RISE_WAIT 2

// A note is sounding
#define NOTE_ON 3

// Send CC data no more than every CC_INTERVAL
// milliseconds
#define CC_INTERVAL 40 


//variables setup

int state;                         // The state of the state machine
unsigned long ccSendTime = 0L;     // The last time we sent CC values
unsigned long breath_on_time = 0L; // Time when breath sensor value went over the ON threshold
int initial_breath_value;          // The breath value at the time we observed the transition

long lastDebounceTime = 0;         // The last time the fingering was changed
long debounceDelay = 30;           // The debounce time; increase if the output flickers
int lastFingering = 0;             // Keep the last fingering value for debouncing

byte MIDIchannel=0;                // MIDI channel 1

int breathLevel;

int pressureSensor;  // pressure data from breath sensor, for midi breath cc and breath threshold checks
byte velocity;       // remapped midi velocity from breath sensor

int fingeredNote;    // note calculated from fingering (switches) and octave joystick position
byte activeNote;     // note playing
byte startNote=61;   // set startNote to C# (change this value in steps of 12 to start in other octaves)

byte midistatus=0;
byte x;
byte LedPin = 13;    // select the pin for the LED

            // Key variables, TRUE (1) for pressed, FALSE (0) for not pressed
byte LH1;   // Left Hand key 1 (pitch change -2)
byte LHb;   // Left Hand bis key (pitch change -1 unless both LH1 and LH2 are pressed)
byte LH2;   // Left Hand key 2  (with LH1 also pressed pitch change is -2, otherwise -1)
byte LH3;   // Left Hand key 3 (pitch change -2)
byte LHp1;  // Left Hand pinky key 1 (pitch change +1)
byte LHp2;  // Left Hand pinky key 2 (pitch change -1)
byte RHs;   // Right Hand side key  (pitch change -2 unless LHp1 is pressed)
byte RH1;   // Right Hand key 1 (with LH3 also pressed pitch change is -2, otherwise -1)
byte RH2;   // Right Hand key 2 (pitch change -1)
byte RH3;   // Right Hand key 3 (pitch change -2)
byte RHp1;  // Right Hand pinky key 1 (pitch change +1)
byte RHp2;  // Right Hand pinky key 2 (pitch change -1)
byte RHp3;  // Right Hand pinky key 3 (pitch change -2)
byte OCTup; // Octave switch key (pitch change +12)



//_______________________________________________________________________________________________ SETUP

void setup() {
  pinMode(2, INPUT_PULLUP); // Set inputs with pull-up
  pinMode(3, INPUT_PULLUP); 
  pinMode(4, INPUT_PULLUP); 
  pinMode(5, INPUT_PULLUP); 
  pinMode(6, INPUT_PULLUP); 
  pinMode(7, INPUT_PULLUP); 
  pinMode(8, INPUT_PULLUP); 
  pinMode(9, INPUT_PULLUP); 
  pinMode(10, INPUT_PULLUP); 
  pinMode(11, INPUT_PULLUP); 
  pinMode(12, INPUT_PULLUP);  
  pinMode(14, INPUT_PULLUP); 
  pinMode(15, INPUT_PULLUP); 
  pinMode(16, INPUT_PULLUP);
  
  state = NOTE_OFF;  // initialize state machine
  
  pinMode(LedPin,OUTPUT);   // declare the LED's pin as output

  for (x=1; x<=4; x++){  // Do the flashy-flashy to say we are up and running
    digitalWrite( LedPin, HIGH );
    delay(300);
    digitalWrite( LedPin, LOW );
    delay(300);
  }

  Serial.begin(31250);  // start serial with midi baudrate 31250
  Serial.flush();
}

//_______________________________________________________________________________________________ MAIN LOOP

void loop() {
  
  pressureSensor = analogRead(A3); // Get the pressure sensor reading from analog pin A3

  if (state == NOTE_OFF) {
    if (pressureSensor > ON_Thr) {
      // Value has risen above threshold. Move to the ON_Delay
      // state. Record time and initial breath value.
      breath_on_time = millis();
      initial_breath_value = pressureSensor;
      state = RISE_WAIT;  // Go to next state
    }
  } else if (state == RISE_WAIT) {
    if (pressureSensor > ON_Thr) {
      // Has enough time passed for us to collect our second
      // sample?
      if (millis() - breath_on_time > ON_Delay) {
        // Yes, so calculate MIDI note and velocity, then send a note on event
        readSwitches();
     
        //calculate midi note number from pressed keys and octave shifts
        fingeredNote=startNote-2*LH1-(LHb && !(LH1 && LH2))-LH2-(LH2 && LH1)-2*LH3+LHp1-LHp2+(RHs && !LHp1)-RH1-(RH1 && LH3)-RH2-2*RH3+RHp1-RHp2-2*RHp3+12*OCTup;

        // We should be at tonguing peak, so set velocity based on current pressureSensor value        
        // If initial value is greater than value after delay, go with initial value, constrain input to keep mapped output within 7 to 127
        velocity = map(constrain(max(pressureSensor,initial_breath_value),ON_Thr,breath_max),ON_Thr,breath_max,7,127);
        midiSend((0x90 | MIDIchannel), fingeredNote, velocity); // send Note On message for new note
        activeNote=fingeredNote;
        state = NOTE_ON;
      }
    } else {
      // Value fell below threshold before ON_Delay 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 (pressureSensor < ON_Thr) {
      // Value has fallen below threshold - turn the note off
      midiSend((0x80 | MIDIchannel), activeNote, velocity); //  send Note Off message 
      state = NOTE_OFF;
    } else {
      // Is it time to send more CC data?
      if (millis() - ccSendTime > CC_INTERVAL) {
         // deal with Breath
         breath();
         ccSendTime = millis();
      }
    }
    readSwitches();     
    //calculate midi note number from pressed keys
    fingeredNote=startNote-2*LH1-(LHb && !(LH1 && LH2))-LH2-(LH2 && LH1)-2*LH3+LHp1-LHp2+(RHs && !LHp1)-RH1-(RH1 && LH3)-RH2-2*RH3+RHp1-RHp2-2*RHp3+12*OCTup;

    if (fingeredNote != lastFingering){ //
      // reset the debouncing timer
      lastDebounceTime = millis();
    }
    if ((millis() - lastDebounceTime) > debounceDelay) {
    // whatever the reading is at, it's been there for longer
    // than the debounce delay, so take it as the actual current state
      if (fingeredNote != activeNote) {
        // Player has moved to a new fingering while still blowing.
        // Send a note off for the current note and a note on for
        // the new note.
        midiSend((0x80 | MIDIchannel), activeNote, velocity); // send Note Off message
        activeNote=fingeredNote;
        midiSend((0x90 | MIDIchannel), activeNote, velocity); // send Note On message
      }
    }
  }
  lastFingering=fingeredNote; 
}
//_______________________________________________________________________________________________ FUNCTIONS

//  Send a three byte midi message  
  void midiSend(byte midistatus, byte data1, byte data2) {
  digitalWrite(LedPin,HIGH);  // indicate we're sending MIDI data
  Serial.write(midistatus);
  Serial.write(data1);
  Serial.write(data2);
  digitalWrite(LedPin,LOW);  // indicate we're sending MIDI data
}

//***********************************************************

void breath(){
  breathLevel = analogRead(A3); // read voltage on analog pin A3
  breathLevel = map(constrain(breathLevel,ON_Thr,breath_max),ON_Thr,breath_max,0,127);
  midiSend((0xB0 | MIDIchannel), 2, breathLevel);
}

//***********************************************************

void readSwitches(){  
  // Read switches and put inverted value in variables
  LH1=!digitalRead(2);
  LHb=!digitalRead(3);
  LH2=!digitalRead(4);
  LH3=!digitalRead(5);
  LHp1=!digitalRead(6);
  LHp2=!digitalRead(7);
  RHs=!digitalRead(8);
  RH1=!digitalRead(9);
  RH2=!digitalRead(10);
  RH3=!digitalRead(11);
  RHp1=!digitalRead(12);
  RHp2=!digitalRead(14);
  RHp3=!digitalRead(15);
  OCTup=!digitalRead(16);
}



Supplementary studies

If you prefer brass valve instruments over woodwind, you can do the exact same thing but with the AKAI/Steiner EVI fingerings, only it's easier because there are no conditional value changes for any keys. Resources needed to do that are found here:


Direct links to the EVI 1000 fingering charts including the value changes for each key are here:

There's also info on the EVI fingering mode in the reference manual for the EWI4000s:


The code for my MiniWI project in its complete (and now and then updated) form is available at Github.