xevi/NuEVI.ino
2017-08-08 14:46:29 +02:00

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;
}