1142 lines
43 KiB
C++
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);
|
|
}
|
|
|
|
}
|