xevi/NuEVI/src/xEVI.cpp
2024-11-08 22:29:39 -06:00

1142 lines
43 KiB
C++

#include <Arduino.h>
#include <Wire.h>
#include <SPI.h>
#include <EEPROM.h>
#include <array>
#include "globals.h"
#include "hardware.h"
#include "midi.h"
#include "menu.h"
#include "config.h"
#include "settings.h"
#include "led.h"
#include "test.h"
#include "FilterOnePole.h"
#include "icm.h"
/*
NAME: xEVI
WRITTEN BY: BRIAN HREBEC
BASED ON: NuEVI by JOHAN BERGLUND
DATE: 2023-8-23
FOR: PJRC Teensy 4.0 and 2x MPR121 capactive touch sensor board.
Uses an SSD1306 controlled OLED display communicating over I2C and a NeoPixel LED strip for status display
ICM20948 for intertial measurement.
FUNCTION: EVI Wind Controller using MPRLS pressure sensors and capacitive touch keys. Output to USB MIDI and CV.
*/
#if !defined(USB_MIDI) && !defined(USB_MIDI_SERIAL)
#error "USB MIDI not enabled. Please set USB type to 'MIDI' or 'Serial + MIDI'."
#endif
preset_t presets[PRESET_COUNT];
instrument_state_t instrument;
calibration_t calibration;
state_t state = {
NOTE_OFF,
&instrument,
&presets[0],
&calibration,
0,
};
static const int pbDepthList[13] = { 8192, 8192, 4096, 2731, 2048, 1638, 1365, 1170, 1024, 910, 819, 744, 683 };
static const float vibDepth[10] = { 0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.40, 0.45 }; // max pitch bend values (+/-) for the vibrato settings
static const short vibMaxBiteList[16] = { 1400, 1200, 1000, 900, 800, 700, 600, 500, 400, 300, 250, 200, 150, 100, 50, 25 };
static const short vibMaxList[12] = { 300, 275, 250, 225, 200, 175, 150, 125, 100, 75, 50, 25 };
static const int timeDividerList[9] = { 0, 222, 200, 181, 167, 152, 143, 130, 125 }; // For CV vibrato - 222 is 4.5Hz, 200 is 5Hz, 181 is 5.5Hz 167 is 6Hz, 152 is 6.5Hz, 143 is 7Hz, 130 is 7.5Hz, 125 is 8Hz
static const unsigned short curveM4[] = { 0, 4300, 7000, 8700, 9900, 10950, 11900, 12600, 13300, 13900, 14500, 15000, 15450, 15700, 16000, 16250, 16383 };
static const unsigned short curveM3[] = { 0, 2900, 5100, 6650, 8200, 9500, 10550, 11500, 12300, 13100, 13800, 14450, 14950, 15350, 15750, 16150, 16383 };
static const unsigned short curveM2[] = { 0, 2000, 3600, 5000, 6450, 7850, 9000, 10100, 11100, 12100, 12900, 13700, 14400, 14950, 15500, 16000, 16383 };
static const unsigned short curveM1[] = { 0, 1400, 2850, 4100, 5300, 6450, 7600, 8700, 9800, 10750, 11650, 12600, 13350, 14150, 14950, 15650, 16383 };
const unsigned short curveIn[] = { 0, 1023, 2047, 3071, 4095, 5119, 6143, 7167, 8191, 9215, 10239, 11263, 12287, 13311, 14335, 15359, 16383 };
static const unsigned short curveP1[] = { 0, 600, 1350, 2150, 2900, 3800, 4700, 5600, 6650, 7700, 8800, 9900, 11100, 12300, 13500, 14850, 16383 };
static const unsigned short curveP2[] = { 0, 400, 800, 1300, 2000, 2650, 3500, 4300, 5300, 6250, 7400, 8500, 9600, 11050, 12400, 14100, 16383 };
static const unsigned short curveP3[] = { 0, 200, 500, 900, 1300, 1800, 2350, 3100, 3800, 4600, 5550, 6550, 8000, 9500, 11250, 13400, 16383 };
static const unsigned short curveP4[] = { 0, 100, 200, 400, 700, 1050, 1500, 1950, 2550, 3200, 4000, 4900, 6050, 7500, 9300, 12100, 16383 };
static const unsigned short curveS1[] = { 0, 600, 1350, 2150, 2900, 3800, 4700, 6000, 8700, 11000, 12400, 13400, 14300, 14950, 15500, 16000, 16383 };
static const unsigned short curveS2[] = { 0, 600, 1350, 2150, 2900, 4000, 6100, 9000, 11000, 12100, 12900, 13700, 14400, 14950, 15500, 16000, 16383 };
// static const unsigned short curveS3[] = {0,600,1350,2300,3800,6200,8700,10200,11100,12100,12900,13700,14400,14950,15500,16000,16383};
// static const unsigned short curveS4[] = {0,600,1700,4000,6600,8550,9700,10550,11400,12200,12900,13700,14400,14950,15500,16000,16383};
static const unsigned short curveZ1[] = { 0, 1400, 2100, 2900, 3200, 3900, 4700, 5600, 6650, 7700, 8800, 9900, 11100, 12300, 13500, 14850, 16383 };
static const unsigned short curveZ2[] = { 0, 2000, 3200, 3800, 4096, 4800, 5100, 5900, 6650, 7700, 8800, 9900, 11100, 12300, 13500, 14850, 16383 };
const std::array<const unsigned short*, 13> curves = {
curveM4, curveM3, curveM2, curveM1, curveIn, curveP1, curveP2,
curveP3, curveP4, curveS1, curveS2, curveZ1, curveZ2 };
static int waveformsTable[maxSamplesNum] = {
// Sine wave
0x7ff, 0x86a, 0x8d5, 0x93f, 0x9a9, 0xa11, 0xa78, 0xadd, 0xb40, 0xba1,
0xbff, 0xc5a, 0xcb2, 0xd08, 0xd59, 0xda7, 0xdf1, 0xe36, 0xe77, 0xeb4,
0xeec, 0xf1f, 0xf4d, 0xf77, 0xf9a, 0xfb9, 0xfd2, 0xfe5, 0xff3, 0xffc,
0xfff, 0xffc, 0xff3, 0xfe5, 0xfd2, 0xfb9, 0xf9a, 0xf77, 0xf4d, 0xf1f,
0xeec, 0xeb4, 0xe77, 0xe36, 0xdf1, 0xda7, 0xd59, 0xd08, 0xcb2, 0xc5a,
0xbff, 0xba1, 0xb40, 0xadd, 0xa78, 0xa11, 0x9a9, 0x93f, 0x8d5, 0x86a,
0x7ff, 0x794, 0x729, 0x6bf, 0x655, 0x5ed, 0x586, 0x521, 0x4be, 0x45d,
0x3ff, 0x3a4, 0x34c, 0x2f6, 0x2a5, 0x257, 0x20d, 0x1c8, 0x187, 0x14a,
0x112, 0xdf, 0xb1, 0x87, 0x64, 0x45, 0x2c, 0x19, 0xb, 0x2,
0x0, 0x2, 0xb, 0x19, 0x2c, 0x45, 0x64, 0x87, 0xb1, 0xdf,
0x112, 0x14a, 0x187, 0x1c8, 0x20d, 0x257, 0x2a5, 0x2f6, 0x34c, 0x3a4,
0x3ff, 0x45d, 0x4be, 0x521, 0x586, 0x5ed, 0x655, 0x6bf, 0x729, 0x794 };
const int rollerHarmonic[2][7] = { {0, 7, 12, 16, 19, 24, 26}, // F horn 2,3,4,5,6,8,9 hrm
{7, 12, 16, 19, 24, 26, 31} }; // Bb horn 3,4,5,6,8,9,12 hrm
const int trumpetHarmonic[2][7] = { {0, 7, 12, 16, 19, 26, 31}, //! K4: hrm 8->9, 10->12
{0, 7, 12, 16, 19, 24, 28} }; // trumpet 2,3,4,5,6,8,10 hrm
FilterOnePole breathCCFilter;
FilterOnePole breathBaselineFilter;
//_______________________________________________________________________________________________ SETUP
// MIDI note value check with out of range octave repeat
inline int noteValueCheck(int note) {
if (note > 127) {
note = 115 + (note - 127) % 12;
} else if (note < 0) {
note = 12 - abs(note) % 12;
}
return note;
}
//***********************************************************
void port(int portCC) {
if (portCC == instrument.portamentoVal) {
return;
}
if (state.currentPreset->portamentoMode == PortamentoMode::PON || state.currentPreset->portamentoMode == PortamentoMode::PGLIDE_ONLY) {
if (instrument.portamentoVal > 0 && portCC == 0) {
midiSendControlChange(CCN_PortOnOff, 0);
} else if (instrument.portamentoVal == 0 && portCC > 0) {
midiSendControlChange(CCN_PortOnOff, 127);
}
}
midiSendControlChange(CCN_Port, portCC);
instrument.portamentoVal = portCC;
}
// Update CV output pin, run from timer.
void cvUpdate() {
static byte cvPortaTuneCount = 0;
uint32_t currentTime = millis();
int cvPressure = readPressure();
analogWrite(cvBreathPin, cvPressure);
instrument.targetPitch = (instrument.activeNote - 24) * 42;
instrument.targetPitch += map(instrument.pitchBend, 0, 16383, -84, 84);
instrument.targetPitch -= instrument.quarterToneTrigger * 21;
if (instrument.portamentoVal > 0) {
if (instrument.targetPitch > instrument.cvPitch) {
if (!cvPortaTuneCount) {
instrument.cvPitch += 1 + (127 - instrument.portamentoVal) / 4;
} else {
cvPortaTuneCount++;
if (cvPortaTuneCount > CVPORTATUNE)
cvPortaTuneCount = 0;
}
if (instrument.cvPitch > instrument.targetPitch)
instrument.cvPitch = instrument.targetPitch;
} else if (instrument.targetPitch < instrument.cvPitch) {
if (!cvPortaTuneCount) {
instrument.cvPitch -= 1 + (127 - instrument.portamentoVal) / 4;
} else {
cvPortaTuneCount++;
if (cvPortaTuneCount > CVPORTATUNE)
cvPortaTuneCount = 0;
}
if (instrument.cvPitch < instrument.targetPitch)
instrument.cvPitch = instrument.targetPitch;
} else {
instrument.cvPitch = instrument.targetPitch;
}
} else {
instrument.cvPitch = instrument.targetPitch;
}
if (state.currentPreset->cvVibRate) {
int timeDivider = timeDividerList[state.currentPreset->cvVibRate];
int cvVib = map(((waveformsTable[map(currentTime % timeDivider, 0, timeDivider, 0, maxSamplesNum - 1)] - 2047)), -259968, 259969, -11, 11);
instrument.cvPitch += cvVib;
}
int cvPitchTuned = 2 * (state.currentPreset->cvTune) + map(instrument.cvPitch, 0, 4032, 0, 4032 + 2 * (state.currentPreset->cvScale));
analogWrite(cvPitchPin, constrain(cvPitchTuned, 0, 4095));
}
//**************************************************************
// non linear mapping function (http://playground.arduino.cc/Main/MultiMap)
// note: the _in array should have increasing values
unsigned int multiMap(unsigned short val, const unsigned short* _in, const unsigned short* _out, uint8_t size) {
// take care the value is within range
// val = constrain(val, _in[0], _in[size-1]);
if (val <= _in[0])
return _out[0];
if (val >= _in[size - 1])
return _out[size - 1];
// search right interval
uint8_t pos = 1; // _in[0] allready tested
while (val > _in[pos])
pos++;
// this will handle all exact "points" in the _in array
if (val == _in[pos])
return _out[pos];
// interpolate in the right segment for the rest
return (val - _in[pos - 1]) * (_out[pos] - _out[pos - 1]) / (_in[pos] - _in[pos - 1]) + _out[pos - 1];
}
//**************************************************************
// map breath values to selected curve
unsigned int breathCurve(unsigned int inputVal) {
if (state.currentPreset->breathCurve >= curves.size()) {
return inputVal;
}
return multiMap(inputVal, curveIn, curves[state.currentPreset->breathCurve], 17);
}
//**************************************************************
int patchLimit(int value) {
return constrain(value, 1, 128);
}
//**************************************************************
int breath() {
static int oldbreath = 0;
static int oldbreathhires = 0;
int breathCCval, breathCCvalFine;
int breathCCvalHires;
if (state.currentPreset->breathMode == BREATH_ACC || state.currentPreset->breathMode == BREATH_ACC_AT) {
int diff = instrument.breathSignal - instrument.breathZero;
if (instrument.mode == MODE_DEBUG) {
Serial.print(">accdiff:");
Serial.println(diff);
}
if (abs(diff) > calibration.breathThrValOffset) {
if (diff > 0) {
breathCCvalHires = breathCurve(mapConstrain(diff, calibration.breathThrValOffset, calibration.breathMaxValOffset, 0, 16383));
} else {
breathCCvalHires = -breathCurve(mapConstrain(-diff, calibration.breathThrValOffset, calibration.breathMaxValOffset, 0, 16383));
}
// breathCCvalHires = breathCCFilter.input(breathCCvalHires);
if (instrument.mode == MODE_DEBUG) {
Serial.print(">accdelta:");
Serial.println(breathCCvalHires);
}
breathCCvalHires = constrain(oldbreathhires + breathCCvalHires / 4, 0, 16383);
} else {
breathCCvalHires = oldbreathhires;
}
} else {
breathCCvalHires = breathCurve(mapConstrain(instrument.breathSignal, instrument.breathBaseline, instrument.breathMaxVal, 0, 16383));
breathCCvalHires = breathCCFilter.input(breathCCvalHires);
}
breathCCval = (breathCCvalHires >> 7) & 0x007F;
breathCCvalFine = breathCCvalHires & 0x007F;
if (breathCCval != oldbreath) { // only send midi data if breath has changed from previous value
if (state.currentPreset->breathCC) {
// send midi cc
midiSendControlChange(state.currentPreset->breathCC, breathCCval);
}
if (state.currentPreset->breathMode == BreathMode::BREATH_AT
|| state.currentPreset->breathMode == BreathMode::BREATH_LSB_AT
|| state.currentPreset->breathMode == BreathMode::BREATH_ACC_AT) {
// send aftertouch
midiSendAfterTouch(breathCCval);
}
oldbreath = breathCCval;
}
if (breathCCvalHires != oldbreathhires &&
(state.currentPreset->breathMode == BreathMode::BREATH_LSB || state.currentPreset->breathMode == BreathMode::BREATH_LSB_AT|| state.currentPreset->breathMode == BreathMode::BREATH_ACC)) {
midiSendControlChange(state.currentPreset->breathCC + 32, breathCCvalFine);
}
oldbreathhires = breathCCvalHires;
state.instrument->breathCCVal = breathCCval;
return breathCCval;
}
//**************************************************************
void vibrato(int calculatedPBdepth) {
int vibMax;
int vibRead = 0;
float calculatedDepth = 0;
if (state.currentPreset->vibratoMode == VibratoMode::VSTART_DOWN) {
calculatedDepth = calculatedPBdepth * vibDepth[state.currentPreset->vibratoDepth];
} else {
calculatedDepth = (0 - calculatedPBdepth * vibDepth[state.currentPreset->vibratoDepth]);
}
vibMax = vibMaxList[state.currentPreset->vibSens - 1];
if (ExtraControl::VIBRATO == state.currentPreset->biteControl) { // bite vibrato
vibRead = instrument.biteSignal;
} else if (ExtraControl::VIBRATO == state.currentPreset->leverControl) { // lever vibrato
vibRead = instrument.leverSignal;
} else if (ExtraControl::VIBRATO == state.currentPreset->extraControl) { // lever vibrato
vibRead = instrument.extraSignal;
} else if (ExtraControl::VIBRATO == state.currentPreset->pbControl) { // lever vibrato
vibRead = instrument.pbSignal;
} else if (ExtraControl::VIB_BEND == state.currentPreset->pbControl && !instrument.pbActive) { // lever vibrato
vibRead = instrument.pbSignal;
}
if (vibRead < instrument.vibThrLo) {
instrument.vibSignal = (instrument.vibSignal + mapConstrain(
vibRead, (instrument.vibZero + vibMax), instrument.vibThr, calculatedDepth, 0)
) / 2;
} else {
instrument.vibSignal = instrument.vibSignal / 2;
}
switch (state.currentPreset->vibRetn) { // moving baseline
case 0:
// keep vibZero value
break;
case 1:
instrument.vibZero = instrument.vibZero * 0.95 + vibRead * 0.05;
break;
case 2:
instrument.vibZero = instrument.vibZero * 0.9 + vibRead * 0.1;
break;
case 3:
instrument.vibZero = instrument.vibZero * 0.8 + vibRead * 0.2;
break;
case 4:
instrument.vibZero = instrument.vibZero * 0.6 + vibRead * 0.4;
}
instrument.vibThr = instrument.vibZero + state.currentPreset->vibSquelch;
instrument.vibThrLo = instrument.vibZero - state.currentPreset->vibSquelch;
}
void pitch_bend() {
// handle input from pitchbend touchpads and
// on-pcb variable capacitor for vibrato.
static int oldpb = 0;
int calculatedPBdepth;
bool halfPitchBendKey = (state.currentPreset->pinkySetting == PBD) && instrument.pinkyKey; // hold pinky key for 1/2 pitchbend value
instrument.quarterToneTrigger = (state.currentPreset->pinkySetting == QTN) && instrument.pinkyKey; // pinky key for a quarter tone down using pitch bend (assuming PB range on synth is set to 2 semitones)
calculatedPBdepth = pbDepthList[state.currentPreset->PBdepth];
if (halfPitchBendKey)
calculatedPBdepth = calculatedPBdepth * 0.5;
instrument.pbActive = false;
if (ExtraControl::BEND == state.currentPreset->pbControl || ExtraControl::VIB_BEND == state.currentPreset->pbControl) {
// Only activate PB if we're outside the deadzone
if (
(instrument.pbSignal > state.calibration->pbCenterVal + state.calibration->pbDeadzone ||
instrument.pbSignal < state.calibration->pbCenterVal - state.calibration->pbDeadzone)
&& instrument.pbSignal != INT16_MIN) {
instrument.pbActive = true;
}
}
vibrato(calculatedPBdepth);
// PB calculation
int pbPos = mapConstrain(instrument.pbSignal, calibration.pbCenterVal + calibration.pbDeadzone, calibration.pbMaxVal, 0, calculatedPBdepth);
int pbNeg = mapConstrain(instrument.pbSignal, calibration.pbMinVal, calibration.pbCenterVal - calibration.pbDeadzone, calculatedPBdepth, 0);
int pbSum = 8193 + pbPos - pbNeg;
int pbDif = abs(pbPos - pbNeg);
if (instrument.pbActive) {
if (pbDif < 10) {
instrument.pitchBend = 8192;
} else {
instrument.pitchBend = instrument.pitchBend * 0.6 + 0.4 * pbSum;
}
} else {
instrument.pitchBend = instrument.pitchBend * 0.6 + 8192 * 0.4; // released, so smooth your way back to zero
if ((instrument.pitchBend > 8187) && (instrument.pitchBend < 8197))
instrument.pitchBend = 8192; // 8192 is 0 pitch bend, don't miss it bc of smoothing
}
instrument.pitchBend = instrument.pitchBend + instrument.vibSignal;
instrument.pitchBend = constrain(instrument.pitchBend, 0, 16383);
instrument.pbSend = instrument.pitchBend - instrument.quarterToneTrigger * calculatedPBdepth * 0.25;
instrument.pbSend = constrain(instrument.pbSend, 0, 16383);
if (instrument.pbSend != oldpb) { // only send midi data if pitch bend has changed from previous value
midiSendPitchBend(instrument.pbSend);
oldpb = instrument.pbSend;
}
}
//***********************************************************
void portamento_() {
if (state.currentPreset->portamentoMode == PortamentoMode::POFF) {
port(0); // ensure it's off
return;
}
int portSumCC = 0;
if (state.currentPreset->pinkySetting == GLD) {
if (instrument.pinkyKey) {
portSumCC += state.currentPreset->portamentoLimit;
}
}
if (ExtraControl::GLIDE == state.currentPreset->biteControl) {
// Portamento is controlled with the bite sensor in the mouthpiece
if (instrument.biteSignal >= calibration.biteThrVal) { // if we are enabled and over the threshold, send portamento
portSumCC += mapConstrain(instrument.biteSignal, calibration.biteThrVal, calibration.biteMaxVal, 0, state.currentPreset->portamentoLimit);
}
}
if (ExtraControl::GLIDE == state.currentPreset->leverControl) {
// Portamento is controlled with thumb lever
if (instrument.leverSignal >= calibration.leverMinVal) { // if we are enabled and over the threshold, send portamento
portSumCC += mapConstrain(instrument.leverSignal, calibration.leverMinVal, calibration.leverMaxVal, 0, state.currentPreset->portamentoLimit);
}
}
port(constrain(portSumCC, 0, state.currentPreset->portamentoLimit)); // Total output glide rate limited to glide max setting
}
//***********************************************************
void sendCC() {
if (ExtraControl::CC == state.currentPreset->biteControl) {
int biteVal = mapConstrain(instrument.biteSignal, calibration.biteThrVal, calibration.biteMaxVal, 127, 0);
if (biteVal != instrument.biteVal) {
midiSendControlChange(state.currentPreset->biteCC, biteVal);
}
instrument.biteVal = biteVal;
}
if (ExtraControl::CC == state.currentPreset->extraControl) {
int extraVal = mapConstrain(instrument.extraSignal, calibration.extraMinVal, calibration.extraMaxVal, 0, 127);
if (extraVal != instrument.extraVal) {
midiSendControlChange(state.currentPreset->extraCC, extraVal);
}
instrument.extraVal = extraVal;
}
int leverVal = mapConstrain(instrument.leverSignal, calibration.leverMinVal, calibration.leverMaxVal, 0, 127);
if (ExtraControl::CC == state.currentPreset->leverControl) {
if (leverVal != instrument.leverVal) {
midiSendControlChange(state.currentPreset->leverCC, leverVal);
}
}
instrument.leverVal = leverVal;
if (ExtraControl::CC == state.currentPreset->pbControl) {
int pbVal = mapConstrain(instrument.pbSignal, calibration.pbMinVal, calibration.pbMaxVal, 0, 127);
if (pbVal != instrument.pbVal) {
midiSendControlChange(state.currentPreset->pbCC, pbVal);
}
instrument.pbVal = pbVal;
}
if (state.currentPreset->pbYCC) {
int pbYVal = mapConstrain(instrument.pbYSignal, calibration.pbYMinVal, calibration.pbYMaxVal, 0, 127);
if (pbYVal != instrument.pbYVal) {
midiSendControlChange(state.currentPreset->pbYCC, pbYVal);
}
instrument.pbYVal = pbYVal;
}
if (state.currentPreset->stickXCC) {
int stickXVal = mapConstrain(instrument.stickXSignal, calibration.stickXMinVal, calibration.stickXMaxVal, 0, 127);
if (stickXVal != instrument.stickXVal) {
midiSendControlChange(state.currentPreset->stickXCC, stickXVal);
}
instrument.stickXVal = stickXVal;
}
if (state.currentPreset->stickYCC) {
int stickYVal = mapConstrain(instrument.stickYSignal, calibration.stickYMinVal, calibration.stickYMaxVal, 0, 127);
if (stickYVal != instrument.stickYVal) {
midiSendControlChange(state.currentPreset->stickYCC, stickYVal);
}
instrument.stickYVal = stickYVal;
}
if (!inMenu()) {
for (int i = 0; i < 4; i++) {
byte val = constrain((int)state.instrument->knobVals[i] + readKnob(i), 0, 127);
if (state.instrument->knobVals[i] != val) {
switch (i) {
case 0:
midiSendControlChange(state.currentPreset->knob1CC, val);
break;
case 1:
midiSendControlChange(state.currentPreset->knob2CC, val);
break;
case 2:
midiSendControlChange(state.currentPreset->knob3CC, val);
break;
case 3:
midiSendControlChange(state.currentPreset->knob4CC, val);
break;
}
state.instrument->knobVals[i] = val;
state.instrument->lastKnobVal = val;
state.instrument->lastKnobTime = millis();
}
}
}
}
// Re-zero floating calibration values
void rezero() {
instrument.vibZero = 0;
instrument.vibThr = instrument.vibZero + state.currentPreset->vibSquelch;
instrument.vibThrLo = instrument.vibZero - state.currentPreset->vibSquelch;
instrument.breathThrVal = instrument.breathZero + calibration.breathThrValOffset;
instrument.breathBaseline = instrument.breathZero + calibration.breathThrValOffset;
instrument.breathThrOffVal = instrument.breathZero + calibration.breathThrValOffset / 2;
instrument.breathMaxVal = instrument.breathThrVal + calibration.breathMaxValOffset;
instrument.breathAltZeroOffset = instrument.breathZero - instrument.breathAltZero;
}
void autoCal() {
long int bZero = 0;
long int bAltZero = 0;
long int bLeverTouchZero = 0, bExtraTouchZero = 0, bPBTouchZero = 0;
for (int i = 1; i <= CALIBRATE_SAMPLE_COUNT; ++i) {
bZero += readPressure();
bAltZero += readAltPressure();
}
instrument.breathZero = bZero / CALIBRATE_SAMPLE_COUNT;
instrument.breathAltZero = (bAltZero / CALIBRATE_SAMPLE_COUNT);
instrument.sliderPBThr = bPBTouchZero /= CALIBRATE_SAMPLE_COUNT;
instrument.sliderExtraThr = bExtraTouchZero /= CALIBRATE_SAMPLE_COUNT;
instrument.sliderLeverThr = bLeverTouchZero /= CALIBRATE_SAMPLE_COUNT;
rezero();
}
void fullAutoCal() {
int calRead;
int calReadNext;
calibration.breathAltThrValOffset = 5;
calibration.breathAltMaxValOffset = 1500;
calibration.breathThrValOffset = 5;
calibration.breathMaxValOffset = 1500;
autoCal();
// Bite sensor
calRead = readTouchRoller(bitePin);
calibration.biteThrVal = constrain(calRead + 100, BITE_LO_LIMIT, BITE_HI_LIMIT);
calibration.biteMaxVal = constrain(calRead + 300, BITE_LO_LIMIT, BITE_HI_LIMIT);
// Touch sensors
calRead = CTOUCH_HI_LIMIT;
for (byte i = 0; i < 12; i++) {
calReadNext = readTouchKey(i);
if (calReadNext < calRead)
calRead = calReadNext; // use lowest value
}
calibration.ctouchThrVal = calRead + 50;
}
//***********************************************************
/*
* Read octave and return offset
*/
int readOctave() {
static byte lastOctaveR = 0;
static unsigned long lastDeglitchTime = 0; // The last time the fingering was changed
static unsigned long lastOctave = 0; // The last time the fingering was changed
static unsigned long fingeredOctave = 0; // The last time the fingering was changed
// Roller modes
// 1: Highest touched roller, release memory
// 2: Highest touched roller, extend range on top/bottom release
// 3: Touched roller pair
// 4: Touched roller pair, extend range
// 5: Touched roller, pair = 5th partial
// 6: Touched roller, pair = 5th partial, extend range
RollerMode rollerMode = state.currentPreset->rollerMode;
bool extend = rollerMode == RollerMode::HIGHEST_EXTEND || rollerMode == RollerMode::HIGHEST_PAIR_EXTEND || rollerMode == RollerMode::PARTIAL_EXTEND ? 1 : 0;
bool rollers[6];
uint16_t ctouchThrVal = calibration.ctouchThrVal;
rollers[0] = readTouchRoller(R1Pin) > ctouchThrVal;
rollers[1] = readTouchRoller(R2Pin) > ctouchThrVal;
rollers[2] = readTouchRoller(R3Pin) > ctouchThrVal;
rollers[3] = readTouchRoller(R4Pin) > ctouchThrVal;
rollers[4] = readTouchRoller(R5Pin) > ctouchThrVal;
rollers[5] = readTouchRoller(R6Pin) > ctouchThrVal;
int offset = 0;
int octaveR = 0;
if (rollerMode == RollerMode::HIGHEST || rollerMode == RollerMode::HIGHEST_EXTEND) {
for (int i = 5; i >= 0; i--) {
if (rollers[i]) {
octaveR = i + 1;
break;
}
}
} else if (rollerMode == RollerMode::HIGHEST_PAIR || rollerMode == RollerMode::HIGHEST_PAIR_EXTEND) {
for (int i = 5; i >= 1; i--) {
if (rollers[i] && rollers[i - 1]) {
octaveR = i + 1;
break;
}
}
// Allow top/bottom single rollers in extended mode
if (octaveR == 0 && extend) {
if (rollers[0]) {
octaveR = 1;
} else if (rollers[5]) {
octaveR = 7;
}
}
} else if (rollerMode == RollerMode::PARTIAL || rollerMode == RollerMode::PARTIAL_EXTEND) {
for (int i = 5; i >= 0; i--) {
if (rollers[i]) {
octaveR = i + 1;
if (i > 0 && rollers[i - 1]) {
offset = -5;
}
break;
}
}
// Go up/down a partial on top/bottom release
if (extend && octaveR == 0) {
if (lastOctaveR == 1) {
offset = -5;
} else if (lastOctaveR == 6) {
offset = 7;
}
octaveR = lastOctaveR;
}
}
// Handle extended release
if (extend && octaveR == 0) {
if (rollerMode == RollerMode::HIGHEST_EXTEND && lastOctaveR == 6) {
octaveR = 7;
} else if (rollerMode == RollerMode::HIGHEST_PAIR_EXTEND && lastOctaveR == 7) {
octaveR = 8;
} else if (rollerMode == RollerMode::PARTIAL_EXTEND && lastOctaveR == 7) {
octaveR = 8;
} else if (lastOctaveR == 1) {
octaveR = 0;
} else {
octaveR = lastOctaveR;
}
}
lastOctaveR = octaveR;
int octave = 0;
FingeringMode fingering = state.currentPreset->fingering;
byte K4 = readTouchKey(K4Pin) < calibration.ctouchThrVal;
if (FingeringMode::TPT == fingering) { // TPT fingering
octave = 24 + offset + trumpetHarmonic[K4][octaveR]; // roller harmonics
} else if (FingeringMode::HRN == fingering) { // HRN fingering
octave = 12 + offset + rollerHarmonic[K4][octaveR]; // roller harmonics
} else if (FingeringMode::EVR == fingering) { // HRN fingering
octave = 12 * (6 - octaveR) + offset + instrument.octave * 12;
} else { // EVI
octave = 12 * octaveR + offset + instrument.octave * 12;
}
return octave;
}
void readSwitches() {
// Keep the last fingering value for debouncing
static int lastFingering = 0;
static int lastFingering2 = 0;
static int lastOctave = 0;
static unsigned long lastDeglitchTime = 0; // The last time the fingering was changed
int fingeredNoteUntransposed = 0;
int fingeredNote2Untransposed = 0;
// Read touch pads (MPR121), compare against threshold value
bool touchKeys[12];
int16_t touchSum = 0;
for (byte i = 0; i < 12; i++) {
int16_t val = readTouchKey(i);
touchKeys[i] = val > calibration.ctouchThrVal;
touchSum += val;
}
instrument.avgCTouchSignal = touchSum / 12;
// Valves and trill keys, TRUE (1) for pressed, FALSE (0) for not pressed
byte K1 = touchKeys[K1Pin]; // Valve 1 (pitch change -2)
byte K2 = touchKeys[K2Pin]; // Valve 2 (pitch change -1)
byte K3 = touchKeys[K3Pin]; // Valve 3 (pitch change -3)
byte K4 = touchKeys[K4Pin]; // Value 4 (pitch change -5)
byte K5 = touchKeys[K5Pin]; // Trill key 1 (pitch change +2)
byte K6 = touchKeys[K6Pin]; // Trill key 2 (pitch change +1)
byte K7 = touchKeys[K7Pin]; // Trill key 3 (pitch change +4)
byte K8 = touchKeys[K8Pin]; // Pinky Key
byte K9 = touchKeys[K9Pin]; // LH Key 1
byte K10 = touchKeys[K10Pin]; // LH Key 2
byte K11 = touchKeys[K11Pin]; // LH Key 3
byte K12 = touchKeys[K12Pin]; // LH Key 4
instrument.pinkyKey = K8;
int qTransp = (K8 && (state.currentPreset->pinkySetting < 25)) ? state.currentPreset->pinkySetting - 12 : 0;
// Calculate midi note number from pressed keys
switch (state.currentPreset->fingering) {
case EVI:
case EVR:
fingeredNoteUntransposed = START_NOTE
- 2 * K1
- K2
- 3 * K3 //"Trumpet valves"
- 5 * K4 // Fifth key
+ 2 * K5
+ K6
+ state.currentPreset->trill3_interval * K7; // Trill keys. 3rd trill key interval controlled by setting
break;
case TPT:
fingeredNoteUntransposed = START_NOTE
- 2 * K1
- K2
- 3 * K3 //"Trumpet valves"
- 2 // Trumpet in B flat
+ 2 * K5
+ K6
+ state.currentPreset->trill3_interval * K7; // Trill keys. 3rd trill key interval controlled by setting
break;
case HRN:
fingeredNoteUntransposed = START_NOTE
- 2 * K1
- K2
- 3 * K3 //"Trumpet valves"
+ 5 * K4 // Switch to Bb horn
+ 5 // Horn in F
+ 2 * K5
+ K6
+ state.currentPreset->trill3_interval * K7; // Trill keys. 3rd trill key interval controlled by setting
break;
}
if (K3 && K7) {
if (4 == state.currentPreset->trill3_interval)
fingeredNoteUntransposed += 2;
else
fingeredNoteUntransposed += 4;
}
fingeredNote2Untransposed =
- 2 * K9
- K10
- 3 * K11
- 7 * K12;
switch (state.currentPreset->polyMode) {
case EHarmonizerOff: // LH affects main instrument
fingeredNoteUntransposed += fingeredNote2Untransposed;
fingeredNote2Untransposed = fingeredNoteUntransposed;
break;
case EDuo: // Duo (absolute) mode
fingeredNote2Untransposed = START_NOTE + fingeredNote2Untransposed;
break;
case EDouble: // Double-stop (relative) mode
fingeredNote2Untransposed = fingeredNoteUntransposed + fingeredNote2Untransposed;
break;
}
int fingeredNoteRead = fingeredNoteUntransposed + instrument.transpose - 12 + qTransp;
int fingeredNote2Read = fingeredNote2Untransposed + instrument.transpose - 12 + qTransp;
int fingeredOctave = readOctave();
if (fingeredOctave != lastOctave || fingeredNoteRead != lastFingering || fingeredNote2Read != lastFingering2) { //
// reset the debouncing timer
lastDeglitchTime = millis();
}
if ((millis() - lastDeglitchTime) > state.currentPreset->deglitch) {
// whatever the reading is at, it's been there for longer
// than the debounce delay, so take it as the actual current instrument
instrument.fingeredNote = noteValueCheck(fingeredNoteRead + fingeredOctave);
instrument.fingeredNote2 = noteValueCheck(fingeredNote2Read + fingeredOctave);
instrument.fingeredOctave = fingeredOctave;
}
lastFingering = fingeredNoteRead;
lastFingering2 = fingeredNote2Read;
lastOctave = fingeredOctave;
}
uint8_t controlVel() {
uint8_t velocity = 0;
if (ExtraControl::VELOCITY_RETRIGGER == state.currentPreset->biteControl) {
velocity = mapConstrain(instrument.biteSignal, calibration.biteThrVal, calibration.biteMaxVal, 127, 1);
}
if (ExtraControl::VELOCITY_RETRIGGER == state.currentPreset->leverControl) {
velocity = mapConstrain(instrument.leverSignal, calibration.leverMinVal, calibration.leverMaxVal, 1, 127);
}
if (ExtraControl::VELOCITY_RETRIGGER == state.currentPreset->pbControl) {
velocity = mapConstrain(instrument.pbSignal, calibration.pbMinVal, calibration.pbMaxVal, 1, 127);
}
return velocity;
}
uint8_t breathVel() {
// Set velocity based on current pressureSensor value unless fixed velocity is set
int velocitySend;
if (state.currentPreset->fixedVelocity) {
return state.currentPreset->fixedVelocity;
}
if (state.currentPreset->noteMode == LEVER_TRIGGER) {
return instrument.leverVal;
}
unsigned int breathValHires = breathCurve(mapConstrain(instrument.breathSignal, instrument.breathBaseline, instrument.breathMaxVal, 0, 16383));
velocitySend = (breathValHires >> 7) & 0x007F;
velocitySend = constrain(velocitySend + velocitySend * .1 * state.currentPreset->velBias, 1, 127);
return velocitySend;
}
void noteOn(int fingeredNote) {
midiSendNoteOn(fingeredNote, breathVel());
}
/**
* Send a new note message if it has changed
*/
void changeNote(int fingeredNote, int activeNote, int fingeredNote2, int activeNote2) {
if (fingeredNote == activeNote && fingeredNote2 == activeNote2) {
// No change
return;
}
int velocity = controlVel();
if (!velocity) {
velocity = breathVel();
}
// 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.
if (fingeredNote != activeNote && fingeredNote != activeNote2) {
midiSendNoteOn(fingeredNote, velocity);
}
if (fingeredNote2 != fingeredNote && fingeredNote2 != activeNote && fingeredNote2 != activeNote2) {
midiSendNoteOn(fingeredNote2, velocity);
}
delayMicroseconds(2000); // delay for midi recording fix
// send Note Off messages
if (activeNote != fingeredNote && activeNote != fingeredNote2) {
midiSendNoteOff(activeNote);
}
if (activeNote != activeNote2 && activeNote2 != fingeredNote && activeNote2 != fingeredNote2) {
midiSendNoteOff(activeNote2);
}
}
void handleOffStateActions() {
if (instrument.activeMIDIchannel != state.currentPreset->MIDIchannel) {
instrument.activeMIDIchannel = state.currentPreset->MIDIchannel; // only switch channel if no active note
midiSetChannel(instrument.activeMIDIchannel);
}
if ((instrument.activePatch != instrument.patch) && instrument.doPatchUpdate) {
instrument.activePatch = instrument.patch;
midiSendProgramChange(instrument.activePatch);
instrument.doPatchUpdate = 0;
}
}
/*
Initialize the main instrument state
*/
void initState() {
instrument.activePatch = 0;
state.mainState = NOTE_OFF; // initialize main state machine
instrument.activeMIDIchannel = state.currentPreset->MIDIchannel;
midiInitialize(state.currentPreset->MIDIchannel);
breathCCFilter.setFilter(LOWPASS, 3, 0.0);
breathBaselineFilter.setFilter(LOWPASS, 1, 0.0);
}
/**
* Read all utility sensors
*/
void readUtil() {
instrument.biteSignal = readRawControl(CONTROL_BITE);
instrument.pbSignal = readRawControl(CONTROL_PB);
instrument.pbYSignal = readRawControl(CONTROL_PB_Y);
instrument.extraSignal = readRawControl(CONTROL_EXTRA);
instrument.leverSignal = readRawControl(CONTROL_LEVER);
instrument.stickXSignal = readRawControl(CONTROL_STICK_X);
instrument.stickYSignal = readRawControl(CONTROL_STICK_Y);
}
/**
* Send CC data when needed
*/
void handleCCs() {
static unsigned long ccBreathSendTime = 0L; // The last time we sent breath CC values
static unsigned long ccSendTime = 0L; // The last time we sent CC values
static unsigned long ccSendTime2 = 0L; // The last time we sent CC values 2 (slower)
static unsigned long ccSendTime3 = 0L; // The last time we sent CC values 3 (and slower)
// Is it time to send more CC data?
unsigned long currentTime = millis();
if ((currentTime - ccBreathSendTime) > (state.currentPreset->breathInterval)) {
breath();
ccBreathSendTime = currentTime;
}
if (currentTime - ccSendTime > CC_INTERVAL_PRIMARY) {
// deal with Pitch Bend, Modulation, etc.
pitch_bend();
sendCC();
checkICM(state);
ccSendTime = currentTime;
}
if (currentTime - ccSendTime2 > CC_INTERVAL_PORT) {
portamento_();
ccSendTime2 = currentTime;
}
if (currentTime - ccSendTime3 > CC_INTERVAL_OTHER) {
updateSensorLEDs(*state.instrument);
ccSendTime3 = currentTime;
}
}
int adjustRetriggerVel(int initialVel) {
int vel = initialVel;
}
/**
* Read the breath sensors according to the active mode
*/
void readBreath() {
// Get the pressure sensor reading
int16_t breathSignal = constrain(readPressure(), BREATH_LO_LIMIT, BREATH_HI_LIMIT);
int16_t breathAltSignal = constrain(readAltPressure(), BREATH_LO_LIMIT, BREATH_HI_LIMIT) + instrument.breathAltZeroOffset;
int16_t spikeSignal = constrain(readSpikePressure(), -SPIKE_HI_LIMIT, SPIKE_HI_LIMIT);
int16_t diffSignal = breathAltSignal - breathSignal;
instrument.breathSignal = breathSignal + diffSignal + (spikeSignal > 0 ? spikeSignal * state.currentPreset->spikeOnFactor : spikeSignal * state.currentPreset->spikeOffFactor);
instrument.breathAltSignal = breathAltSignal;
int16_t movingThrAdj = instrument.breathBaseline + (instrument.breathSignal - instrument.breathBaseline) * .75;
if (state.mainState != NOTE_ON) {
// Don't allow the threshold to increase if we're not playing a note to make slow attacks consistent
movingThrAdj = min(instrument.breathMovingThrVal, movingThrAdj);
}
instrument.breathMovingThrVal = constrain(
breathBaselineFilter.input(movingThrAdj),
instrument.breathBaseline,
instrument.breathMaxVal - state.calibration->breathThrValOffset
);
int16_t halfOffset = state.calibration->breathThrValOffset >> 1;
instrument.breathThrVal = instrument.breathMovingThrVal + halfOffset;
instrument.breathThrOffVal = instrument.breathMovingThrVal - halfOffset;
if (instrument.mode == MODE_DEBUG) {
Serial.print(">breath:");
Serial.println(breathSignal);
Serial.print(">Diff:");
Serial.println(diffSignal);
Serial.print(">breathMovingThr:");
Serial.println(instrument.breathMovingThrVal);
Serial.print(">note:");
Serial.println(state.mainState);
Serial.print(">spike:");
Serial.println(spikeSignal);
Serial.print(">combo:");
Serial.println(instrument.breathSignal);
Serial.print(">zero:");
Serial.println(instrument.breathZero);
Serial.print(">thr:");
Serial.println(instrument.breathThrVal);
Serial.print(">off:");
Serial.println(instrument.breathThrOffVal);
Serial.print(">offset:");
Serial.println(state.calibration->breathThrValOffset);
}
}
/**
* Main instrument state machine
*/
void runStateMachine() {
static unsigned long breath_on_time = 0L; // Time when breath sensor value went over the ON threshold
bool lever_trigger = state.currentPreset->noteMode == LEVER_TRIGGER;
if (state.currentPreset->noteMode == NO_TRIGGER) {
return;
}
if (state.mainState == NOTE_OFF) {
handleOffStateActions();
bool crossed_threshold = lever_trigger ? instrument.leverVal > 0 : instrument.breathSignal > instrument.breathThrVal;
if (crossed_threshold && state.mainState == NOTE_OFF) {
// Value has risen above threshold. Move to the RISE_WAIT
// state. Record time and initial breath value.
breath_on_time = millis();
state.mainState = RISE_WAIT; // Go to next state
}
} else if (state.mainState == RISE_WAIT) {
if ((lever_trigger ? instrument.leverVal > 0 : instrument.breathSignal > instrument.breathMovingThrVal)) {
// Has enough time passed for us to collect our second sample?
if ((millis() - breath_on_time > state.currentPreset->velSmpDl) || (0 == state.currentPreset->velSmpDl) || state.currentPreset->fixedVelocity) {
noteOn(instrument.fingeredNote);
if (instrument.fingeredNote != instrument.fingeredNote2) {
noteOn(instrument.fingeredNote2);
}
breath(); // send breath data
state.mainState = NOTE_ON;
instrument.activeNote = instrument.fingeredNote;
instrument.activeNote2 = instrument.fingeredNote2;
}
} else {
// Value fell below threshold before velocity sample delay time passed. Return to NOTE_OFF state
state.mainState = NOTE_OFF;
}
} else if (state.mainState == NOTE_ON) {
if (lever_trigger ? instrument.leverVal <= 0 : instrument.breathSignal < instrument.breathThrOffVal) {
// Value has fallen below threshold - turn the note off
midiSendNoteOff(instrument.activeNote); // send Note Off message
if (instrument.activeNote != instrument.activeNote2) {
midiSendNoteOff(instrument.activeNote2); // send Note Off message
}
instrument.breathSignal = 0;
state.mainState = NOTE_OFF;
} else {
changeNote(instrument.fingeredNote, instrument.activeNote, instrument.fingeredNote2, instrument.activeNote2);
instrument.activeNote = instrument.fingeredNote;
instrument.activeNote2 = instrument.fingeredNote2;
}
}
}
void setup() {
if (CrashReport) {
Serial.begin(9600); // debug
Serial.println("Debug Startup");
while (!Serial); // wait for serial monitor open
Serial.print(CrashReport);
}
initHardware();
delay(100); // Make sure the inputs settle
bool factoryReset = checkButtonState(STARTUP_FACTORY_RESET);
initDisplay(); // Start up display and show logo
if (CrashReport) {
displayError("CRASH WARNING");
}
// Read eeprom data into global vars
readEEPROM(factoryReset, *state.calibration);
updateFilters(*state.currentPreset);
statusLedFlash(500);
statusLedOff();
if (factoryReset) {
// Full calibration
fullAutoCal();
} else {
// Minimal startup calibration (atmo pressure)
autoCal();
}
showVersion();
delay(1000);
initState(); // Set up midi/etc
statusLedOn(); // Switch on the onboard LED to indicate power on/ready
displayOff(state);
if (checkButtonState(STARTUP_TEST)) {
instrument.mode = MODE_TEST;
}
}
//_______________________________________________________________________________________________ MAIN LOOP
void loop() {
static unsigned long pixelUpdateTime = 0;
static const unsigned long pixelUpdateInterval = 80;
// If in config mgmt loop, do that and nothing else
if (instrument.mode == MODE_CONFIG) {
configModeLoop(state);
return;
}
if (instrument.mode == MODE_TEST) {
handleTestMode(state);
return;
}
readBreath();
readSwitches();
readUtil();
runStateMachine();
handleCCs();
// cvUpdate();
midiDiscardInput();
if (millis() - pixelUpdateTime > pixelUpdateInterval) {
// even if we just alter a pixel, the whole display is redrawn (35ms of MPU lockup) and we can't do that all the time
// this is one of the big reasons the display is for setup use only
// TODO: is this still true on teensy 4?
pixelUpdateTime = millis();
handleMenu(state, true);
} else {
handleMenu(state, false);
}
}