339 lines
13 KiB
C++
339 lines
13 KiB
C++
#include <Wire.h>
|
|
#include <Adafruit_MPR121.h>
|
|
|
|
/*
|
|
NAME: NuEVI
|
|
WRITTEN BY: JOHAN BERGLUND
|
|
DATE: 2017-08-08
|
|
FILE SAVED AS: NuEVI.ino
|
|
FOR: PJRC Teensy LC or 3.2 and a MPR121 capactive touch sensor board
|
|
PROGRAMME FUNCTION: EVI Wind Controller using the Freescale MP3V5004GP breath sensor
|
|
and capacitive touch keys. Output to both USB MIDI and DIN MIDI.
|
|
|
|
*/
|
|
|
|
//_______________________________________________________________________________________________ DECLARATIONS
|
|
|
|
#define ON_Thr 370 // 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 1023 // Maximum breath level
|
|
#define PB_sens 8191 // Pitch Bend sensitivity 0 to 8191 where 8191 is full pb range, 4095 half range
|
|
#define VIB_depth 1023 // Vibrato depth 0 to 8191
|
|
#define touch_Thr 1800 // sensitivity for Teensy touch sensors
|
|
#define CCN_Port 5 // Controller number for portamento level
|
|
#define CCN_PortOnOff 65// Controller number for portamento on/off
|
|
|
|
|
|
// Send CC data no more than every CC_INTERVAL
|
|
// milliseconds
|
|
#define CC_INTERVAL 5
|
|
|
|
// 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
|
|
|
|
|
|
//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
|
|
|
|
unsigned long lastDebounceTime = 0;// The last time the fingering was changed
|
|
unsigned long debounceDelay = 10; // The debounce time; increase if the output flickers
|
|
int lastFingering = 0; // Keep the last fingering value for debouncing
|
|
|
|
byte MIDIchannel=1; // MIDI channel 1
|
|
|
|
int breathLevel=0; // breath level (smoothed) not mapped to CC value
|
|
int oldbreath=0;
|
|
|
|
int pressureSensor; // pressure data from breath sensor, for midi breath cc and breath threshold checks
|
|
byte velocity; // remapped midi velocity from breath sensor
|
|
|
|
int biteSensor=0; // capacitance data from bite sensor, for midi cc and threshold checks
|
|
byte portIsOn=0; // keep track and make sure we send CC with 0 value when off threshold
|
|
int biteThr=1730; // Set threshold level before switching ON (value to use if no pots installed)
|
|
int biteMax=3300; // Upper limit for pressure (value to use if no pots installed)
|
|
|
|
int pitchBend=0;
|
|
int oldpb=8192;
|
|
int pbThr=1200;
|
|
int pbMax=2400;
|
|
|
|
int vibThr=1800;
|
|
int oldvibRead=0;
|
|
|
|
int fingeredNote; // note calculated from fingering (switches) and octave joystick position
|
|
byte activeNote; // note playing
|
|
byte startNote=36; // set startNote to C (change this value in steps of 12 to start in other octaves)
|
|
|
|
Adafruit_MPR121 touchSensor = Adafruit_MPR121(); // This is the 12-input touch sensor
|
|
|
|
|
|
//_______________________________________________________________________________________________ SETUP
|
|
|
|
void setup() {
|
|
|
|
state = NOTE_OFF; // initialize state machine
|
|
if (!touchSensor.begin(0x5A)) {
|
|
while (1); // Touch sensor initialization failed - stop doing stuff
|
|
}
|
|
Serial3.begin(31250); // start serial with midi baudrate 31250
|
|
Serial3.flush();
|
|
|
|
}
|
|
|
|
//_______________________________________________________________________________________________ MAIN LOOP
|
|
|
|
void loop() {
|
|
pressureSensor = analogRead(A0); // Get the pressure sensor reading from analog pin A0
|
|
|
|
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();
|
|
//fingeredNote = startNote + 24;
|
|
// 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 1 to 127
|
|
velocity = map(constrain(max(pressureSensor,initial_breath_value),ON_Thr,breath_max),ON_Thr,breath_max,1,127);
|
|
breathLevel=constrain(max(pressureSensor,initial_breath_value),ON_Thr,breath_max);
|
|
breath(); // send breath data
|
|
usbMIDI.sendNoteOn(fingeredNote, velocity, MIDIchannel); // send Note On message for new note
|
|
dinMIDIsendNoteOn(fingeredNote, velocity, MIDIchannel - 1);
|
|
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
|
|
usbMIDI.sendNoteOff(activeNote, velocity, MIDIchannel); // send Note Off message
|
|
dinMIDIsendNoteOff(activeNote, velocity, MIDIchannel - 1);
|
|
breathLevel=0;
|
|
state = NOTE_OFF;
|
|
} else {
|
|
readSwitches();
|
|
//fingeredNote = startNote + 24;
|
|
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.
|
|
velocity = map(constrain(pressureSensor,ON_Thr,breath_max),ON_Thr,breath_max,7,127); // set new velocity value based on current pressure sensor level
|
|
usbMIDI.sendNoteOn(fingeredNote, velocity, MIDIchannel); // send Note On message for new note
|
|
dinMIDIsendNoteOn(fingeredNote, velocity, MIDIchannel - 1);
|
|
usbMIDI.sendNoteOff(activeNote, 0, MIDIchannel); // send Note Off message for previous note (legato)
|
|
dinMIDIsendNoteOff(activeNote, 0, MIDIchannel - 1);
|
|
activeNote=fingeredNote;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Is it time to send more CC data?
|
|
if (millis() - ccSendTime > CC_INTERVAL) {
|
|
// deal with Breath, Pitch Bend and Modulation
|
|
breath();
|
|
pitch_bend();
|
|
portamento();
|
|
ccSendTime = millis();
|
|
}
|
|
lastFingering=fingeredNote;
|
|
}
|
|
//_______________________________________________________________________________________________ FUNCTIONS
|
|
|
|
// Send a three byte din midi message
|
|
void midiSend(byte midistatus, byte data1, byte data2) {
|
|
Serial3.write(midistatus);
|
|
Serial3.write(data1);
|
|
Serial3.write(data2);
|
|
}
|
|
|
|
//**************************************************************
|
|
|
|
// Send din pitchbend
|
|
void dinMIDIsendPitchBend(int pb, byte ch) {
|
|
int pitchLSB = pb & 0x007F;
|
|
int pitchMSB = (pb >>7) & 0x007F;
|
|
midiSend((0xE0 | ch), pitchLSB, pitchMSB);
|
|
}
|
|
|
|
//**************************************************************
|
|
|
|
// Send din control change
|
|
void dinMIDIsendControlChange(byte ccNumber, int cc, byte ch) {
|
|
midiSend((0xB0 | ch), ccNumber, cc);
|
|
}
|
|
|
|
//**************************************************************
|
|
|
|
// Send din note on
|
|
void dinMIDIsendNoteOn(byte note, int vel, byte ch) {
|
|
midiSend((0x90 | ch), note, vel);
|
|
}
|
|
|
|
//**************************************************************
|
|
|
|
// Send din note off
|
|
void dinMIDIsendNoteOff(byte note, int vel, byte ch) {
|
|
midiSend((0x80 | ch), note, vel);
|
|
}
|
|
|
|
//**************************************************************
|
|
|
|
void breath(){
|
|
int breathCC;
|
|
breathLevel = breathLevel*0.8+pressureSensor*0.2; // smoothing of breathLevel value
|
|
breathCC = map(constrain(breathLevel,ON_Thr,breath_max),ON_Thr,breath_max,0,127);
|
|
if (breathCC != oldbreath){ // only send midi data if breath has changed from previous value
|
|
usbMIDI.sendControlChange(2, breathCC, MIDIchannel);
|
|
dinMIDIsendControlChange(2, breathCC, MIDIchannel - 1);
|
|
oldbreath = breathCC;
|
|
}
|
|
}
|
|
|
|
//**************************************************************
|
|
|
|
void pitch_bend(){
|
|
int pbUp = touchRead(23);
|
|
int pbDn = touchRead(22);
|
|
int vibRead = touchRead(1);
|
|
if ((vibRead > vibThr)&&(vibRead > (oldvibRead+7))){
|
|
pitchBend=oldpb*0.7+0.3*(8192 + VIB_depth);
|
|
} else if ((vibRead > vibThr)&&(vibRead < (oldvibRead-7))){
|
|
pitchBend=oldpb*0.7+0.3*(8191 - VIB_depth);
|
|
} else {
|
|
pitchBend = oldpb*0.4+8192*0.6; // released, so smooth your way back to zero
|
|
if ((pitchBend > 8187) && (pitchBend < 8197)) pitchBend = 8192; // 8192 is 0 pitch bend, don't miss it bc of smoothing
|
|
}
|
|
oldvibRead = vibRead;
|
|
if (pbUp > pbThr){
|
|
pitchBend=pitchBend*0.8+0.2*map(constrain(pbUp,pbThr,pbMax),pbThr,pbMax,8192,(8192 + PB_sens));
|
|
} else if (pbDn > pbThr){
|
|
pitchBend=pitchBend*0.8+0.2*map(constrain(pbDn,pbThr,pbMax),pbThr,pbMax,8192,(8191 - PB_sens));
|
|
} else if (oldvibRead < vibThr){
|
|
pitchBend = pitchBend*0.8+8192*0.2; // released, so smooth your way back to zero
|
|
if ((pitchBend > 8187) && (pitchBend < 8197)) pitchBend = 8192; // 8192 is 0 pitch bend, don't miss it bc of smoothing
|
|
}
|
|
pitchBend=constrain(pitchBend, 0, 16383);
|
|
if (pitchBend != oldpb){// only send midi data if pitch bend has changed from previous value
|
|
usbMIDI.sendPitchBend(pitchBend, MIDIchannel);
|
|
dinMIDIsendPitchBend(pitchBend, MIDIchannel - 1);
|
|
oldpb=pitchBend;
|
|
}
|
|
}
|
|
|
|
//***********************************************************
|
|
|
|
void portamento(){
|
|
biteSensor=biteSensor*0.6+0.4*touchRead(0); // get sensor data, do some smoothing
|
|
if (biteSensor >= biteThr) { // if we are over the threshold, send portamento
|
|
if (!portIsOn) {
|
|
portOn();
|
|
}
|
|
port();
|
|
} else if (portIsOn) { // we have just gone below threshold, so send zero value
|
|
portOff();
|
|
}
|
|
}
|
|
|
|
//***********************************************************
|
|
|
|
void portOn(){
|
|
usbMIDI.sendControlChange(CCN_PortOnOff, 127, MIDIchannel);
|
|
dinMIDIsendControlChange(CCN_PortOnOff, 127, MIDIchannel - 1);
|
|
portIsOn=1;
|
|
}
|
|
|
|
//***********************************************************
|
|
|
|
void port(){
|
|
int portCC;
|
|
portCC = map(constrain(biteSensor,biteThr,biteMax),biteThr,biteMax,1,127);
|
|
usbMIDI.sendControlChange(CCN_Port, portCC, MIDIchannel);
|
|
dinMIDIsendControlChange(CCN_Port, portCC, MIDIchannel - 1);
|
|
}
|
|
|
|
//***********************************************************
|
|
|
|
void portOff(){
|
|
usbMIDI.sendControlChange(CCN_Port, 0, MIDIchannel);
|
|
dinMIDIsendControlChange(CCN_Port, 0, MIDIchannel - 1);
|
|
usbMIDI.sendControlChange(CCN_PortOnOff, 0, MIDIchannel);
|
|
dinMIDIsendControlChange(CCN_PortOnOff, 0, MIDIchannel - 1);
|
|
portIsOn=0;
|
|
}
|
|
|
|
//***********************************************************
|
|
|
|
void readSwitches(){
|
|
|
|
// Key variables, TRUE (1) for pressed, FALSE (0) for not pressed
|
|
byte K1; // Valve 1 (pitch change -2)
|
|
byte K2; // Valve 2 (pitch change -1)
|
|
byte K3; // Valve 3 (pitch change -3)
|
|
byte K4; // Left Hand index finger (pitch change -5)
|
|
byte K5; // Trill key 1 (pitch change +2)
|
|
byte K6; // Trill key 2 (pitch change +1)
|
|
byte K7; // Trill key 3 (pitch change +4)
|
|
|
|
byte octave = 0;
|
|
|
|
// Read touch pads (MPR121) and put value in variables
|
|
uint16_t touchValue = touchSensor.touched();
|
|
|
|
// Octave rollers
|
|
if ((touchValue >> 5) & 0x01) octave = 6;
|
|
else if ((touchValue >> 4) & 0x01) octave = 5;
|
|
else if ((touchValue >> 3) & 0x01) octave = 4;
|
|
else if ((touchValue >> 2) & 0x01) octave = 3;
|
|
else if ((touchValue >> 1) & 0x01) octave = 2;
|
|
else if ((touchValue >> 0) & 0x01) octave = 1;
|
|
|
|
// Valves and trill keys
|
|
K1=((touchValue >> 6) & 0x01);
|
|
K2=((touchValue >> 7) & 0x01);
|
|
K3=((touchValue >> 8) & 0x01);
|
|
K5=((touchValue >> 9) & 0x01);
|
|
K6=((touchValue >> 10) & 0x01);
|
|
K7=((touchValue >> 11) & 0x01);
|
|
// Read touch pads (Teensy built in) and put value in variables
|
|
K4=touchRead(15) > touch_Thr;
|
|
|
|
// Calculate midi note number from pressed keys
|
|
fingeredNote=startNote-2*K1-K2-3*K3-5*K4+2*K5+K6+4*K7+octave*12;
|
|
}
|
|
|
|
|
|
|