ME 708: Electronic Valve Instrument MIDI Controller Project

by Patrick_Le in Circuits > Microcontrollers

54 Views, 1 Favorites, 0 Comments

ME 708: Electronic Valve Instrument MIDI Controller Project

PatrickLe_ME708_Poster_page-0001.jpg
Adobe Express - file.png

Objective

The goal of this project is to design and build an electronic wind instrument (EWI) or electronic valve instrument (EVI) for Dr. Wilson's ME 708 Mechatronics class at the University of Kansas. The instrument will simulate traditional woodwind or brass instruments using sensors, microcontrollers, and sound synthesis.

Description

The device draws inspiration from commercial EWI/EVI's such as the Berglund Instruments NuRAD and NuEVI. The device works by utilizing a Teensy 4.0 Microcontroller connected to a capacitive touch sensor breakout board, breath pressure sensor, and soft potentiometer. The capacitive touch sensor breakout board reads note combinations, while the breath pressure sensor triggers note activation/deactivation and volume output, while the soft potentiometer bends the pitch up to and down to two semitones. The microcontroller interfaces with any MIDI capable software such as synthesizers or DAWs to generate music output.

Supplies

Electronic Components.png
Hardware.png

Materials

Electronics

1 x Teensy 4.0 Microcontroller

1 x Adafruit 12 Key Capacitive Touch Sensor Breakout - MPR121

1 x HX710 4 Pin Breath Pressure Sensor

1 x 100 mm Soft Potentiometer

1 x 63 row solderless Breadboard

1 x 10kohm Resistor


3D Printed Parts

1 x Baseplate

1 x Valve Plate

1 x Mouthpiece


Hardware

8 x 1/4"-3/4" Carriage Bolts

8 x 1/4"-20 Hex Nuts

1 x 6" Pitot Tube

Coding

Sample code.png
#include <Wire.h>
#include <Adafruit_MPR121.h>
#include <HX711.h>

// Pin definitions
#define MPR121_ADDR 0x5A
#define DOUT_PIN 12
#define SCK_PIN 13
#define SOFTPOT_PIN A1 // You can change this to your actual softpot pin

// Valve and octave pin mappings
const int VALVE_1_PIN = 0;
const int VALVE_2_PIN = 1;
const int VALVE_3_PIN = 2;
const int OCTAVE_1_PIN = 3;
const int OCTAVE_2_PIN = 4;
const int OCTAVE_3_PIN = 5;
const int OCTAVE_4_PIN = 6;
const int REGISTER_KEY_PIN = 7;

// Base note (Concert B2 = MIDI 47)
const int BASE_NOTE = 47;

// Pressure thresholds
const float pressureThreshold = 100; // Breath-on threshold
const float zeroPressureThreshold = 50; // Noise floor
const unsigned long debounceDuration = 65;

HX711 scale;
Adafruit_MPR121 capSensor = Adafruit_MPR121();

unsigned long lastActiveTime = 0;
float currentPressure = 0;

int currentNote = -1;
bool notePlaying = false;
int lastVelocity = 0;

void setup() {
Serial.begin(115200);

if (!capSensor.begin(MPR121_ADDR)) {
Serial.println("MPR121 not found. Check wiring!");
while (1);
}

scale.begin(DOUT_PIN, SCK_PIN);
scale.set_scale(2280.f);
scale.tare();

usbMIDI.begin();
}

bool isPressed(uint8_t touchPad) {
return capSensor.touched() & (1 << touchPad);
}

void loop() {
bool v1 = isPressed(VALVE_1_PIN);
bool v2 = isPressed(VALVE_2_PIN);
bool v3 = isPressed(VALVE_3_PIN);
bool o1 = isPressed(OCTAVE_1_PIN);
bool o2 = isPressed(OCTAVE_2_PIN);
bool o3 = isPressed(OCTAVE_3_PIN);
bool o4 = isPressed(OCTAVE_4_PIN);
bool reg = isPressed(REGISTER_KEY_PIN);

int octaveShift = 0;
if (o4) octaveShift = 4;
else if (o3) octaveShift = 3;
else if (o2) octaveShift = 2;
else if (o1) octaveShift = 1;

int noteOffset = getNoteOffset(v1, v2, v3, reg);
int targetNote = BASE_NOTE + noteOffset + (12 * octaveShift);

if (scale.is_ready()) {
currentPressure = scale.get_units(10);
if (currentPressure < zeroPressureThreshold) {
currentPressure = 0;
}
if (currentPressure >= pressureThreshold) {
lastActiveTime = millis();
}
}

int velocity = map(currentPressure, 0, 1000, 0, 127);
velocity = constrain(velocity, 0, 127);

if (currentPressure >= pressureThreshold) {
if (!notePlaying || targetNote != currentNote) {
if (notePlaying) {
usbMIDI.sendNoteOff(currentNote, 0, 1);
Serial.print("Note Off: ");
Serial.println(currentNote);
}
usbMIDI.sendNoteOn(targetNote, velocity, 1);
Serial.print("Note On: ");
Serial.println(targetNote);
currentNote = targetNote;
notePlaying = true;
lastVelocity = velocity;
} else if (abs(velocity - lastVelocity) > 3) {
usbMIDI.sendControlChange(11, velocity, 1);
lastVelocity = velocity;
Serial.print("Expression Update: ");
Serial.println(velocity);
}
} else if (millis() - lastActiveTime > debounceDuration && notePlaying) {
usbMIDI.sendNoteOff(currentNote, 0, 1);
Serial.print("Note Off (timeout): ");
Serial.println(currentNote);
notePlaying = false;
}

// Softpot pitch bend logic
int softpot = analogRead(SOFTPOT_PIN);
int pitchBendVal = 8192; // Center position = no bend

if (softpot >= 50 && softpot <= 375) {
// Bend up
pitchBendVal = map(softpot, 50, 375, 16383, 8192);
} else if (softpot >= 425 && softpot <= 950) {
// Bend down
pitchBendVal = map(softpot, 425, 950, 0, 8192);
}

usbMIDI.sendPitchBend(pitchBendVal, 1); // Channel 1


Serial.print("Pressure: ");
Serial.println(currentPressure);

while (usbMIDI.read()) {}
delay(50);
}

// Fingering logic
int getNoteOffset(bool v1, bool v2, bool v3, bool regKey) {
if (regKey) {
if (v1 && v2 && v3) return 1; // C#
if (v1 && v3) return 2; // D
if (v2 && v3) return 3; // D#
if (v1 && v2) return 4; // E
if (v1) return 5; // F
if (v2) return 6; // F#
if (!v1 && !v2 && !v3) return 7; // G
} else {
if (v1 && v2 && v3) return -6; // Lower C#
if (v1 && v3) return -5;
if (v2 && v3) return -4;
if (v1 && v2) return -3;
if (v1) return -2;
if (v2) return -1;
if (!v1 && !v2 && !v3) return 0;
}
return 0; // default if nothing matches
}

//noteoffset = - v1*2+v2+v1*3 + 12*(octaveshift)

Wind MIDI Controller – Code Walkthrough

This code powers a custom MIDI wind controller using:

- Capacitive touch buttons for fingering (MPR121)

- A load cell for breath pressure (HX711)

- A SoftPot (ribbon sensor) for pitch bending

- USB MIDI output to a synth or DAW


Setup Overview:

1. Libraries Used:

- Wire.h: for I2C communication

- Adafruit_MPR121.h: for capacitive touch input

- HX711.h: for load cell reading

- USB MIDI library (Teensy)

2. Pin Definitions:

- MPR121 address: 0x5A

- HX711: DOUT = 12, SCK = 13

- SoftPot analog input: A1

- MPR121 inputs:

- Valve buttons: pins 0–2

- Octave buttons: pins 3–6

- Register key: pin 7


Main Code Flow (Loop):

1. Read Fingering Buttons:

- Uses `isPressed(pin)` to check which capacitive pads are touched

- Assigns valve and octave logic:

- `v1`, `v2`, `v3`: simulate traditional brass instrument valve combinations

- `o1–o4`: select the octave (1 = 1 octave up, 4 = 4 octaves up)

- `reg`: register key (like a clarinet or sax thumb key)

2. Calculate Target MIDI Note:

- Starts from `BASE_NOTE` (Concert B2 = MIDI 47)

- Adds:

- `noteOffset` from fingering (based on valves + register key)

- `12 * octaveShift` from selected octave

- Example:

- BASE_NOTE = 47, register on, v1 + v3 pressed → offset = 2

- o2 pressed → shift up 2 octaves = +24

- MIDI note = 47 + 2 + 24 = 73

3. Read Breath Pressure:

- Uses the HX711 scale to read pressure

- Filters out noise (under `zeroPressureThreshold`)

- Triggers "active" status when above `pressureThreshold`

- Maps pressure to MIDI velocity (0–127)

4. Send MIDI Notes:

- If pressure is above threshold:

- If no note is playing or note changed → send Note On

- If same note is playing and pressure changed → send Expression (CC #11)

- If pressure stops for more than `debounceDuration` ms → send Note Off

5. Send Pitch Bend from SoftPot:

- Reads SoftPot value from analog pin

- Maps to MIDI pitch bend range:

- Middle = no bend (8192)

- Left (low value) = bend up

- Right (high value) = bend down

- Sends pitch bend message continuously

6. **Debug Output:**

- Serial monitor prints:

- Note on/off messages

- Pressure values

- Expression changes


Helper Functions:

- `isPressed(touchPad)`: Returns `true` if a specific MPR121 pad is touched

- `getNoteOffset(v1, v2, v3, regKey)`:

- Returns semitone offset from BASE_NOTE depending on valve/register configuration

- Different behavior when register key is pressed:

- e.g., with register key ON:

- v1+v2+v3 → C# (offset = +1)

- none pressed → G (offset = +7)

- register key OFF gives negative offsets (lower register)


Timing:

- The loop runs every ~50ms

- MIDI events are sent immediately based on changes in breath or touch

- Note off is delayed slightly (debounceDuration) to avoid premature cutoffs


Summary:

This sketch turns your physical interaction—valves, breath, and hand position—into dynamic, expressive MIDI data for use with a soft synth or sampler.

It supports:

- Note triggering based on breath

- Dynamic velocity/expression changes

- Octave shifting

- Real-time pitch bending

Downloads

Electronics

20250502_002659.jpg

The electronic components are the foundation for the overall design of the MIDI controller. The actual assembly of the electronic components is fairly straightforward.

Assembly Guidelines

  1. Place the Teensy 4.0 on the edge of the breadboard with the micro-usb port facing outward
  2. Place the Adafruit MPR121 Board slightly down from the Teensy (spacing is unimportant) but do not attach wires to the touch pins yet
  3. Place the HX710 breath pressure sensor slightly down from the MPR121 (again, spacing is unimportant)
  4. Place the resistor at any point towards the bottom of the breadboard and connect one side to the ground
  5. Connect the SoftPot wires to the breadboard through wire extensions as the SoftPot will be attached to the bottom of the baseplate but do not attach the SoftPot yet.

The wire/pin connections can all be found in the code walkthrough. It is important that all wiring is connected (accept for the SoftPot and Capacitive Touch pins) prior to physical assembly as the baseplate and valveplate will be sealed together.

Construction

Valveplate.png
Baseplate.png
Mouthpiece.png

Construction Guidelines

  1. Insert all bolts into the bolt holes
  2. Wrap the wire around each bolt and secure to the baseplate and valveplate using the hex nuts
  3. Attach the solderless breadboard to the baseplate using either superglue (or the attached adhesive strip if there is one) while leaving enough room from the edges for the valve plate to be attached. Make sure the Teensy is at the opposite end of the mouthpiece end
  4. Run the SoftPot wires through square hole in the baseplate
  5. Attach the SoftPot to the bottom of the valveplate in a location comfortable for you right thumb and attach the wires
  6. Secure the valveplate onto the baseplate using superglue or an alternative adhesive
  7. Insert the wires attached to the bolts into their designated capacitive touch pin slot
  8. Insert the mouthpiece into the mouthpiece slot
  9. Superglue the pitot tube onto the breath pressure sensor
  10. Feed the pitot tube through the side hole in the valveplate and through the hole in the mothhpiece

Demonstration

EVI Demonstration