Menu system fixes; made state less global

This commit is contained in:
Brian Hrebec 2023-08-29 13:32:56 -05:00
parent cfc2390b8b
commit 209959e2de
14 changed files with 964 additions and 731 deletions

View file

@ -12,7 +12,7 @@
platform = teensy
board = teensy40
framework = arduino
build_flags = -D USB_MIDI_SERIAL -D TEENSY_OPT_FASTER
build_flags = -D USB_MIDI_SERIAL -g
board_build.f_cpu = 528000000L
lib_deps =
adafruit/Adafruit MPR121@^1.1.1

View file

@ -1,8 +1,11 @@
1. LED abstraction code
2. Encoder code
3. Menu refactor
4. Refactor note play behavior into module
5. Refactor CV behavior into module
6. 9dof sensor code
7. Alternate fingerings
8. Encoder midi
8. Encoder midi
- Lever mode: pb
- Breath mode: relative
- Breath suck control?
-

View file

@ -1,187 +0,0 @@
#include <array>
#include <Arduino.h>
#include <Adafruit_SSD1306.h>
#include "menu.h"
#include "globals.h"
#include "config.h"
#include "hardware.h"
#include "settings.h"
//***********************************************************
extern Adafruit_SSD1306 display;
extern Adafruit_MPR121 touchSensorUtil;
extern Adafruit_MPR121 touchSensorKeys;
extern Adafruit_MPRLS pressureSensorMain;
extern Adafruit_MPRLS pressureSensorAlt;
extern byte cursorNow;
int16_t ctouchVal = 0;
// Track pixels for faster redrawing
struct AdjustDrawing {
int row;
int thrX;
int maxX;
int valX;
};
struct AdjustValue {
const char *title;
const int16_t &value;
int16_t &thrVal;
int16_t &maxVal;
const int16_t limitLow;
const int16_t limitHigh;
// If not null, thr and max are relative to zeroPoint
const int16_t *zeroPoint;
};
template<size_t N>
class AdjustMenuScreen : public MenuScreen {
public:
AdjustMenuScreen(const char* title, std::array<AdjustValue, N> entries) : _title(title), _entries(entries) { }
void update(InputState input, bool redraw) {
bool redrawIndicators = false;
if (input.changed) {
if (input.knobMenu) {
_selectedEntry = _selectedEntry + input.knobMenu;
redraw = true;
}
AdjustValue value = _entries[_selectedEntry];
if (input.knobVal1) {
value.thrVal += input.knobVal1;
redrawIndicators = true;
}
if (input.knobVal2) {
value.maxVal += input.knobVal2;
redrawIndicators = true;
}
} else {
draw(redrawIndicators, redraw);
}
}
const char *title() {
return _title;
}
private:
void draw(bool redrawIndicators, bool redraw) {
size_t scrollPos = 0;
if (_entries.size() >= ADJUST_NUM_ROWS) {
if ((_selectedEntry - scrollPos) > (ADJUST_NUM_ROWS-2) ) {
scrollPos = _selectedEntry - (ADJUST_NUM_ROWS-2);
} else if( (_selectedEntry - scrollPos) < 1) {
scrollPos = _selectedEntry - 1;
}
scrollPos = constrain(scrollPos, 0, _entries.size() - ADJUST_NUM_ROWS);
}
int end = constrain(scrollPos + ADJUST_NUM_ROWS, 0, N);
for (size_t i = scrollPos; i < end; i++) {
if (redraw) {
drawAdjustRow(_entries[i], _rowDrawings[i], i == _selectedEntry);
} else if (redrawIndicators && i == _selectedEntry) {
drawAdjustIndicators(_entries[i], _rowDrawings[i]);
} else {
drawAdjustValues(_entries[i], _rowDrawings[i]);
}
}
}
const char* _title;
size_t _selectedEntry = 0;
std::array<AdjustValue, N> _entries;
std::array<AdjustDrawing, N> _rowDrawings;
};
std::array<AdjustValue, 8> adjustValues = {{
{"BREATH", state.breathSignal, calibration.breathThrValOffset, calibration.breathMaxValOffset,
BREATH_LO_LIMIT, BREATH_HI_LIMIT, &state.breathZero},
{"BR ALT", state.breathAltSignal, calibration.breathAltThrValOffset, calibration.breathAltMaxValOffset,
BREATH_LO_LIMIT, BREATH_HI_LIMIT, &state.breathAltZero},
{"BITE",state.biteSignal, calibration.biteThrVal, calibration.biteMaxVal, BITE_LO_LIMIT, BITE_HI_LIMIT, NULL},
{"PB DOWN",state.pbDnSignal, calibration.pbDnThrVal, calibration.pbDnMaxVal, PITCHB_LO_LIMIT, PITCHB_HI_LIMIT, NULL},
{"PB UP", state.pbUpSignal, calibration.pbUpThrVal, calibration.pbUpMaxVal, PITCHB_LO_LIMIT, PITCHB_HI_LIMIT, NULL},
{"EXTRA", state.extraSignal, calibration.extraThrVal, calibration.extraMaxVal, EXTRA_LO_LIMIT, EXTRA_HI_LIMIT, NULL},
{"LEVER", state.leverSignal, calibration.leverThrVal, calibration.leverMaxVal, LEVER_LO_LIMIT, LEVER_HI_LIMIT, NULL},
{"TOUCH", ctouchVal, calibration.ctouchThrVal, calibration.ctouchThrVal, CTOUCH_LO_LIMIT, CTOUCH_HI_LIMIT, NULL},
}};
const MenuScreen adjustMenu = AdjustMenuScreen<8>("ADJUST", adjustValues);
void autoCalSelected() {
}
//***********************************************************
static void drawIndicator(int x, int row, int color) {
display.fillTriangle(x-2, row+1, x+2, row+1, x, row+3, color);
display.fillTriangle(x-2, row+10, x+2, row+10, x, row+7, color);
}
static void drawAdjustIndicators(const AdjustValue &value, AdjustDrawing &drawing) {
const int thrX = mapConstrain(value.thrVal, value.limitLow, value.limitHigh, 1, 127);
const int maxX = mapConstrain(value.maxVal, value.limitLow, value.limitHigh, 1, 127);
if (drawing.maxX != maxX) {
drawIndicator(drawing.thrX, drawing.row, BLACK);
drawIndicator(thrX, drawing.row, WHITE);
drawing.maxX = maxX;
}
if (drawing.thrX != thrX) {
drawIndicator(drawing.thrX, drawing.row, BLACK);
drawIndicator(thrX, drawing.row, WHITE);
drawing.thrX = thrX;
}
}
static void drawAdjustTitle(const AdjustValue &value, AdjustDrawing &drawing, bool highlight) {
display.setTextSize(1);
if (highlight) {
display.setTextColor(BLACK, WHITE);
} else {
display.setTextColor(WHITE, BLACK);
}
display.setCursor(0, drawing.row);
display.println(value.title);
}
static void drawAdjustValues(const AdjustValue &value, AdjustDrawing &drawing) {
char buffer[13];
snprintf(buffer, 13, "%d>%d<%d", value.thrVal, value.value, value.maxVal);
display.setTextSize(1);
display.setCursor(128 - 6 * strlen(buffer), drawing.row);
display.println(buffer);
const int valX = mapConstrain(value.value, value.limitLow, value.limitHigh, 1, 127);
if (drawing.valX != valX) {
display.drawFastVLine(drawing.valX, drawing.row+4, 4, BLACK);
display.drawFastVLine(valX, drawing.row+4, 4, WHITE);
drawing.valX = valX;
}
}
static void drawAdjustFrame(int line) {
display.drawLine(25,line,120,line,WHITE); // Top line
display.drawLine(25,line+12,120,line+12,WHITE); // Bottom line
display.drawLine(25,line+1,25,line+2,WHITE);
display.drawLine(120,line+1,120,line+2,WHITE);
display.drawLine(120,line+10,120,line+11,WHITE);
display.drawLine(25,line+10,25,line+11,WHITE);
}
static void drawAdjustRow(const AdjustValue &value, AdjustDrawing &drawing, bool highlight) {
display.fillRect(0, drawing.row, 128, 21, BLACK);
drawAdjustFrame(drawing.row);
drawAdjustTitle(value, drawing, highlight);
drawAdjustValues(value, drawing);
drawAdjustIndicators(value, drawing);
}

View file

@ -15,6 +15,7 @@
#define CAP_SENS_ABSOLUTE_MAX 1000 // For inverting capacitive sensors
#define PRESSURE_SENS_MULTIPLIER 10 // Multiply pressure sens so it's not a float
#define CALIBRATE_SAMPLE_COUNT 4
#define MENU_AUTO_OFF_TIME 30000
// Statup buttons

View file

@ -74,14 +74,13 @@ enum PortamentoMode : uint8_t {
};
struct instrument_state_t {
int mainState; // The state of the main state machine
uint8_t patch; // 1-128
byte activeMIDIchannel = 1; // MIDI channel
byte activeNote = 0; // note playing
byte activePatch = 0;
byte doPatchUpdate = 0;
int8_t transpose = 0;
uint8_t octave = 0;
int8_t octave = 0;
PolySelect polyMode = PolySelect::EHarmonizerOff;
// Raw sensor signals
@ -93,6 +92,7 @@ struct instrument_state_t {
int16_t pbDnSignal = 0;
int16_t extraSignal = 0;
int16_t vibSignal = 0;
int16_t avgCTouchSignal = 0;
// MIDI values
int breathCCVal = 0;
@ -127,8 +127,6 @@ struct instrument_state_t {
int16_t vibThrBiteLo;
};
extern instrument_state_t state;
extern const std::array<const unsigned short*, 13> curves;
extern const unsigned short curveIn[];

View file

@ -103,8 +103,22 @@ void errorWait() {
}
}
/*
* Read the knob value accounting for 4x precision - NB this might not work on other kinds of encoder
*/
int readKnob(uint8_t n) {
return knobs[n].readAndReset();
int out = 0;
int32_t val = knobs[n].read();
if (val > 4) {
out = val / 4;
} else if (val < -4) {
out = val / 4;
}
if (out != 0) {
knobs[n].write(0);
}
return out;
}
int readTouchKey(uint8_t n) {

View file

@ -31,24 +31,24 @@ int readAltPressure();
// I2C
#define MainI2CBus Wire1
#define AuxI2CBus Wire
#define KeysI2CAddr 0x5B
#define UtilI2CAddr 0x5A
#define KeysI2CAddr 0x5A
#define UtilI2CAddr 0x5B
// Digital pins for encoder buttons
#define b1Pin 0
#define b2Pin 2
#define b3Pin 3
#define b4Pin 4
#define b1Pin 4
#define b2Pin 3
#define b3Pin 2
#define b4Pin 0
// Digital pins for encoder quadrature
#define e1aPin 5
#define e2aPin 6
#define e1aPin 6
#define e2aPin 8
#define e3aPin 7
#define e4aPin 8
#define e4aPin 5
#define e1bPin 20
#define e1bPin 22
#define e2bPin 21
#define e3bPin 22
#define e3bPin 20
#define e4bPin 23
// CV pins
@ -64,33 +64,34 @@ int readAltPressure();
// Key pins
// RH keys
#define K1Pin 0
#define K2Pin 1
#define K1Pin 5
#define K2Pin 6
#define K3Pin 2
#define K4Pin 3
#define K4Pin 11
#define K5Pin 4
#define K6Pin 5
#define K7Pin 6
#define K8Pin 7
#define K6Pin 7
#define K7Pin 9
#define K8Pin 10
// LH keys
#define K9Pin 8
#define K10Pin 9
#define K11Pin 10
#define K12Pin 11
#define K9Pin 3
#define K10Pin 8
#define K11Pin 0
#define K12Pin 1
// Octave roller pins
#define R1Pin 0
#define R2Pin 1
#define R3Pin 2
#define R4Pin 3
#define R5Pin 4
#define R6Pin 5
#define R1Pin 11
#define R2Pin 10
#define R3Pin 9
#define R4Pin 8
#define R5Pin 7
#define R6Pin 6
// Additional pins
#define bitePin 6
#define pbUpPin 7
#define pbDnPin 8
#define vibratoPin 9
#define bitePin 0
#define extraPin 2
#define pbUpPin 4
#define pbDnPin 5
#define vibratoPin 3
#endif

View file

@ -43,7 +43,7 @@ void statusLedBlink() {
statusLedFlash(300);
}
void updateSensorLEDs() {
ledHalfMeter(1, state.breathCCVal, 0x00FF00);
ledQuarterMeter(3, state.biteVal, 0x0000FF);
void updateSensorLEDs(instrument_state_t *state) {
ledHalfMeter(1, state->breathCCVal, 0x00FF00);
ledQuarterMeter(3, state->biteVal, 0x0000FF);
}

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,7 @@
#define __MENU_H
#include "wiring.h"
#include "settings.h"
#define MENU_ROW_HEIGHT 9
#define MENU_HEADER_OFFSET 12
@ -28,17 +29,18 @@ struct InputState {
int knobPreset = 0;
};
struct MenuScreen {
class MenuScreen {
public:
MenuScreen() {};
virtual const char *title() { return ""; };
virtual void update(InputState input, bool redraw) {};
virtual bool update(state_t &state, InputState &input, bool redraw) = 0;
virtual ~MenuScreen() {};
};
extern const MenuScreen adjustMenu;
void initDisplay();
void displayOff();
void showVersion();
void displayError(const char *error);
void handleMenu(bool draw);
void handleMenu(state_t &state, bool draw);
#endif

View file

@ -32,11 +32,11 @@ uint16_t readInt(uint16_t address) {
return data.val;
}
void writeCalibration() {
void writeCalibration(calibration_t &calibration) {
EEPROM.put(SETTINGS_OFFSET, calibration);
}
void readCalibration() {
void readCalibration(calibration_t &calibration) {
EEPROM.get(SETTINGS_OFFSET, calibration);
}
@ -181,7 +181,6 @@ bool receiveSysexSettings(const uint8_t* data, const uint16_t length) {
uint16_t *preset_buffer = (uint16_t*)presets;
for(uint16_t i=0; i<payload_size/2; i++) {
uint16_t addr = i*2;
uint16_t val;
preset_buffer[addr] = convertFromMidiValue(data+(payload_pos+addr));
}
@ -270,7 +269,7 @@ void handleSysex(uint8_t *data, unsigned int length) {
}
//Get message code
char messageCode[3];
char messageCode[4];
strncpy(messageCode, (char*)(data+9), 3);
if(!strncmp(messageCode, "c00", 3)) { //Config dump request
@ -319,7 +318,7 @@ void configModeLoop() {
}
//Read settings from eeprom. Returns wether or not anything was written (due to factory reset or upgrade)
void readEEPROM(const bool factoryReset) {
void readEEPROM(const bool factoryReset, calibration_t &calibration) {
// if stored settings are not for current version, or Enter+Menu are pressed at startup, they are replaced by factory settings
uint16_t settings_version = readInt(EEPROM_VERSION_ADDR);
@ -329,7 +328,7 @@ void readEEPROM(const bool factoryReset) {
settings_version = 0;
} else {
readPresets();
readCalibration();
readCalibration(calibration);
}
if(settings_version != EEPROM_VERSION) {

View file

@ -95,8 +95,15 @@ struct preset_t {
static_assert(sizeof(preset_t) == PRESET_MAX_SIZE, "preset_t must be 128 bytes");
extern preset_t presets[PRESET_COUNT];
extern calibration_t calibration;
extern preset_t *currentPreset;
// Application state
struct state_t {
int mainState; // The state of the main state machine
instrument_state_t *instrument;
preset_t *currentPreset;
calibration_t *calibration;
};
#define NO_CHECKSUM 0x7F007F00

View file

@ -27,12 +27,14 @@ void handleTestMode() {
if (keys != oldKeys) {
Serial.print("Keys:");
Serial.println(keys, HEX);
oldKeys = keys;
}
uint16_t util = utilTouched();
if (util != oldUtil) {
Serial.print("Util:");
Serial.println(util, HEX);
oldUtil = util;
}
if (buttons == 0x01) {
@ -48,10 +50,12 @@ void handleTestMode() {
}
for (int i = 0; i < 12; i++) {
Serial.print(">util");
Serial.print(">util");
Serial.print(i);
Serial.print(":");
Serial.println(readTouchUtil(i));
}
}
delay(2);
}

View file

@ -11,6 +11,7 @@
#include "config.h"
#include "settings.h"
#include "led.h"
#include "test.h"
/*
NAME: xEVI
@ -28,9 +29,15 @@ FUNCTION: EVI Wind Controller using MPRLS pressure sensors and capac
#endif
preset_t presets[PRESET_COUNT];
instrument_state_t state;
preset_t *currentPreset;
instrument_state_t instrument;
preset_t *currentPreset = &presets[0];
calibration_t calibration;
state_t state = {
NOTE_OFF,
&instrument,
currentPreset,
&calibration,
};
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
@ -98,20 +105,20 @@ inline int noteValueCheck(int note) {
//***********************************************************
void port(int portCC) {
if (portCC == state.portamentoVal) {
if (portCC == instrument.portamentoVal) {
return;
}
if (currentPreset->portamentoMode == PortamentoMode::PON || currentPreset->portamentoMode == PortamentoMode::PGLIDE_ONLY) {
if (state.portamentoVal > 0 && portCC == 0) {
if (instrument.portamentoVal > 0 && portCC == 0) {
midiSendControlChange(CCN_PortOnOff, 0);
} else if (state.portamentoVal == 0 && portCC > 0) {
} else if (instrument.portamentoVal == 0 && portCC > 0) {
midiSendControlChange(CCN_PortOnOff, 127);
}
}
midiSendControlChange(CCN_Port, portCC);
state.portamentoVal = portCC;
instrument.portamentoVal = portCC;
}
// Update CV output pin, run from timer.
@ -120,43 +127,43 @@ void cvUpdate() {
uint32_t currentTime = millis();
int cvPressure = readPressure();
analogWrite(cvBreathPin, cvPressure);
state.targetPitch = (state.activeNote - 24) * 42;
state.targetPitch += map(state.pitchBend, 0, 16383, -84, 84);
state.targetPitch -= state.quarterToneTrigger * 21;
if (state.portamentoVal > 0) {
if (state.targetPitch > state.cvPitch) {
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) {
state.cvPitch += 1 + (127 - state.portamentoVal) / 4;
instrument.cvPitch += 1 + (127 - instrument.portamentoVal) / 4;
} else {
cvPortaTuneCount++;
if (cvPortaTuneCount > CVPORTATUNE)
cvPortaTuneCount = 0;
}
if (state.cvPitch > state.targetPitch)
state.cvPitch = state.targetPitch;
} else if (state.targetPitch < state.cvPitch) {
if (instrument.cvPitch > instrument.targetPitch)
instrument.cvPitch = instrument.targetPitch;
} else if (instrument.targetPitch < instrument.cvPitch) {
if (!cvPortaTuneCount) {
state.cvPitch -= 1 + (127 - state.portamentoVal) / 4;
instrument.cvPitch -= 1 + (127 - instrument.portamentoVal) / 4;
} else {
cvPortaTuneCount++;
if (cvPortaTuneCount > CVPORTATUNE)
cvPortaTuneCount = 0;
}
if (state.cvPitch < state.targetPitch)
state.cvPitch = state.targetPitch;
if (instrument.cvPitch < instrument.targetPitch)
instrument.cvPitch = instrument.targetPitch;
} else {
state.cvPitch = state.targetPitch;
instrument.cvPitch = instrument.targetPitch;
}
} else {
state.cvPitch = state.targetPitch;
instrument.cvPitch = instrument.targetPitch;
}
if (currentPreset->cvVibRate) {
int timeDivider = timeDividerList[currentPreset->cvVibRate];
int cvVib = map(((waveformsTable[map(currentTime % timeDivider, 0, timeDivider, 0, maxSamplesNum - 1)] - 2047)), -259968, 259969, -11, 11);
state.cvPitch += cvVib;
instrument.cvPitch += cvVib;
}
int cvPitchTuned = 2 * (currentPreset->cvTune) + map(state.cvPitch, 0, 4032, 0, 4032 + 2 * (currentPreset->cvScale));
int cvPitchTuned = 2 * (currentPreset->cvTune) + map(instrument.cvPitch, 0, 4032, 0, 4032 + 2 * (currentPreset->cvScale));
analogWrite(cvPitchPin, constrain(cvPitchTuned, 0, 4095));
}
@ -209,7 +216,7 @@ int breath() {
int breathCCval, breathCCvalFine;
unsigned int breathCCvalHires;
breathCCvalHires = breathCurve(mapConstrain(state.breathSignal, state.breathThrVal, state.breathMaxVal, 0, 16383));
breathCCvalHires = breathCurve(mapConstrain(instrument.breathSignal, instrument.breathThrVal, instrument.breathMaxVal, 0, 16383));
breathCCval = (breathCCvalHires >> 7) & 0x007F;
breathCCvalFine = breathCCvalHires & 0x007F;
if (breathCCval != oldbreath) { // only send midi data if breath has changed from previous value
@ -246,10 +253,10 @@ void pitch_bend() {
byte pbTouched = 0;
int vibRead = 0;
int vibReadBite = 0;
state.pbUpSignal = readTouchUtil(pbUpPin); // PCB PIN "Pu"
state.pbDnSignal = readTouchUtil(pbDnPin); // PCB PIN "Pd"
bool halfPitchBendKey = (currentPreset->pinkySetting == PBD) && state.pinkyKey; // hold pinky key for 1/2 pitchbend value
state.quarterToneTrigger = (currentPreset->pinkySetting == QTN) && state.pinkyKey; // pinky key for a quarter tone down using pitch bend (assuming PB range on synth is set to 2 semitones)
instrument.pbUpSignal = readTouchUtil(pbUpPin); // PCB PIN "Pu"
instrument.pbDnSignal = readTouchUtil(pbDnPin); // PCB PIN "Pd"
bool halfPitchBendKey = (currentPreset->pinkySetting == PBD) && instrument.pinkyKey; // hold pinky key for 1/2 pitchbend value
instrument.quarterToneTrigger = (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[currentPreset->PBdepth];
if (halfPitchBendKey)
@ -268,31 +275,31 @@ void pitch_bend() {
vibMaxBite = vibMaxBiteList[currentPreset->vibSens - 1];
vibReadBite = readTouchUtil(bitePin); // get sensor data, do some smoothing - SENSOR PIN 17 - PCB PINS LABELED "BITE" (GND left, sensor pin right)
if (vibReadBite < state.vibThrBite) {
state.vibSignal = (state.vibSignal + mapConstrain(
vibReadBite, (state.vibZeroBite - vibMaxBite), state.vibThrBite, calculatedDepth, 0)
if (vibReadBite < instrument.vibThrBite) {
instrument.vibSignal = (instrument.vibSignal + mapConstrain(
vibReadBite, (instrument.vibZeroBite - vibMaxBite), instrument.vibThrBite, calculatedDepth, 0)
) / 2;
} else if (vibReadBite > state.vibThrBiteLo) {
state.vibSignal = (state.vibSignal + mapConstrain(
vibReadBite, (state.vibZeroBite + vibMaxBite), state.vibThrBite, calculatedDepth, 0)
} else if (vibReadBite > instrument.vibThrBiteLo) {
instrument.vibSignal = (instrument.vibSignal + mapConstrain(
vibReadBite, (instrument.vibZeroBite + vibMaxBite), instrument.vibThrBite, calculatedDepth, 0)
) / 2;
} else {
state.vibSignal = state.vibSignal / 2;
instrument.vibSignal = instrument.vibSignal / 2;
}
}
if (ExtraControl::VIBRATO == currentPreset->leverControl) { // lever vibrato
vibRead = readTouchUtil(vibratoPin);
if (vibRead < state.vibThr) {
state.vibSignal = (state.vibSignal +
mapConstrain(vibRead, (state.vibZero - vibMax), state.vibThr, calculatedDepth, 0)
if (vibRead < instrument.vibThr) {
instrument.vibSignal = (instrument.vibSignal +
mapConstrain(vibRead, (instrument.vibZero - vibMax), instrument.vibThr, calculatedDepth, 0)
) / 2;
} else if (vibRead > state.vibThrLo) {
state.vibSignal = (state.vibSignal +
mapConstrain(vibRead, (state.vibZero + vibMax), state.vibThr, calculatedDepth, 0)
} else if (vibRead > instrument.vibThrLo) {
instrument.vibSignal = (instrument.vibSignal +
mapConstrain(vibRead, (instrument.vibZero + vibMax), instrument.vibThr, calculatedDepth, 0)
) / 2;
} else {
state.vibSignal = state.vibSignal / 2;
instrument.vibSignal = instrument.vibSignal / 2;
}
}
@ -301,53 +308,53 @@ void pitch_bend() {
// keep vibZero value
break;
case 1:
state.vibZero = state.vibZero * 0.95 + vibRead * 0.05;
state.vibZeroBite = state.vibZeroBite * 0.95 + vibReadBite * 0.05;
instrument.vibZero = instrument.vibZero * 0.95 + vibRead * 0.05;
instrument.vibZeroBite = instrument.vibZeroBite * 0.95 + vibReadBite * 0.05;
break;
case 2:
state.vibZero = state.vibZero * 0.9 + vibRead * 0.1;
state.vibZeroBite = state.vibZeroBite * 0.9 + vibReadBite * 0.1;
instrument.vibZero = instrument.vibZero * 0.9 + vibRead * 0.1;
instrument.vibZeroBite = instrument.vibZeroBite * 0.9 + vibReadBite * 0.1;
break;
case 3:
state.vibZero = state.vibZero * 0.8 + vibRead * 0.2;
state.vibZeroBite = state.vibZeroBite * 0.8 + vibReadBite * 0.2;
instrument.vibZero = instrument.vibZero * 0.8 + vibRead * 0.2;
instrument.vibZeroBite = instrument.vibZeroBite * 0.8 + vibReadBite * 0.2;
break;
case 4:
state.vibZero = state.vibZero * 0.6 + vibRead * 0.4;
state.vibZeroBite = state.vibZeroBite * 0.6 + vibReadBite * 0.4;
instrument.vibZero = instrument.vibZero * 0.6 + vibRead * 0.4;
instrument.vibZeroBite = instrument.vibZeroBite * 0.6 + vibReadBite * 0.4;
}
state.vibThr = state.vibZero - currentPreset->vibSquelch;
state.vibThrLo = state.vibZero + currentPreset->vibSquelch;
state.vibThrBite = state.vibZeroBite - currentPreset->vibSquelch;
state.vibThrBiteLo = state.vibZeroBite + currentPreset->vibSquelch;
int pbPos = mapConstrain(state.pbUpSignal, calibration.pbUpMaxVal, calibration.pbUpThrVal, calculatedPBdepth, 0);
int pbNeg = mapConstrain(state.pbDnSignal, calibration.pbDnMaxVal, calibration.pbDnThrVal, calculatedPBdepth, 0);
instrument.vibThr = instrument.vibZero - currentPreset->vibSquelch;
instrument.vibThrLo = instrument.vibZero + currentPreset->vibSquelch;
instrument.vibThrBite = instrument.vibZeroBite - currentPreset->vibSquelch;
instrument.vibThrBiteLo = instrument.vibZeroBite + currentPreset->vibSquelch;
int pbPos = mapConstrain(instrument.pbUpSignal, calibration.pbUpMaxVal, calibration.pbUpThrVal, calculatedPBdepth, 0);
int pbNeg = mapConstrain(instrument.pbDnSignal, calibration.pbDnMaxVal, calibration.pbDnThrVal, calculatedPBdepth, 0);
int pbSum = 8193 + pbPos - pbNeg;
int pbDif = abs(pbPos - pbNeg);
if ((state.pbUpSignal < calibration.pbUpThrVal || state.pbDnSignal < calibration.pbDnThrVal) && currentPreset->PBdepth) {
if ((instrument.pbUpSignal < calibration.pbUpThrVal || instrument.pbDnSignal < calibration.pbDnThrVal) && currentPreset->PBdepth) {
if (pbDif < 10) {
state.pitchBend = 8192;
instrument.pitchBend = 8192;
} else {
state.pitchBend = state.pitchBend * 0.6 + 0.4 * pbSum;
instrument.pitchBend = instrument.pitchBend * 0.6 + 0.4 * pbSum;
}
pbTouched = 1;
}
if (!pbTouched) {
state.pitchBend = state.pitchBend * 0.6 + 8192 * 0.4; // released, so smooth your way back to zero
if ((state.pitchBend > 8187) && (state.pitchBend < 8197))
state.pitchBend = 8192; // 8192 is 0 pitch bend, don't miss it bc of smoothing
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
}
state.pitchBend = state.pitchBend + state.vibSignal;
state.pitchBend = constrain(state.pitchBend, 0, 16383);
instrument.pitchBend = instrument.pitchBend + instrument.vibSignal;
instrument.pitchBend = constrain(instrument.pitchBend, 0, 16383);
state.pbSend = state.pitchBend - state.quarterToneTrigger * calculatedPBdepth * 0.25;
state.pbSend = constrain(state.pbSend, 0, 16383);
instrument.pbSend = instrument.pitchBend - instrument.quarterToneTrigger * calculatedPBdepth * 0.25;
instrument.pbSend = constrain(instrument.pbSend, 0, 16383);
if (state.pbSend != oldpb) { // only send midi data if pitch bend has changed from previous value
midiSendPitchBend(state.pbSend);
oldpb = state.pbSend;
if (instrument.pbSend != oldpb) { // only send midi data if pitch bend has changed from previous value
midiSendPitchBend(instrument.pbSend);
oldpb = instrument.pbSend;
}
}
@ -361,23 +368,23 @@ void portamento_() {
int portSumCC = 0;
if (currentPreset->pinkySetting == GLD) {
if (state.pinkyKey) {
if (instrument.pinkyKey) {
portSumCC += currentPreset->portamentoLimit;
}
}
if (ExtraControl::GLIDE == currentPreset->biteControl) {
// Portamento is controlled with the bite sensor in the mouthpiece
state.biteSignal = readTouchUtil(bitePin);
if (state.biteSignal >= calibration.biteThrVal) { // if we are enabled and over the threshold, send portamento
portSumCC += mapConstrain(state.biteSignal, calibration.biteThrVal, calibration.biteMaxVal, 0, currentPreset->portamentoLimit);
instrument.biteSignal = readTouchUtil(bitePin);
if (instrument.biteSignal >= calibration.biteThrVal) { // if we are enabled and over the threshold, send portamento
portSumCC += mapConstrain(instrument.biteSignal, calibration.biteThrVal, calibration.biteMaxVal, 0, currentPreset->portamentoLimit);
}
}
if (ExtraControl::GLIDE == currentPreset->leverControl) {
// Portamento is controlled with thumb lever
state.leverSignal = readTouchUtil(vibratoPin);
if (((3000 - state.leverSignal) >= calibration.leverThrVal)) { // if we are enabled and over the threshold, send portamento
portSumCC += mapConstrain((3000 - state.leverSignal), calibration.leverThrVal, calibration.leverMaxVal, 0, currentPreset->portamentoLimit);
instrument.leverSignal = readTouchUtil(vibratoPin);
if (((3000 - instrument.leverSignal) >= calibration.leverThrVal)) { // if we are enabled and over the threshold, send portamento
portSumCC += mapConstrain((3000 - instrument.leverSignal), calibration.leverThrVal, calibration.leverMaxVal, 0, currentPreset->portamentoLimit);
}
}
@ -389,41 +396,41 @@ void portamento_() {
void biteCC_() {
int biteVal = 0;
if (ExtraControl::CC == currentPreset->biteControl) {
state.biteSignal = readTouchUtil(bitePin); // get sensor data, do some smoothing - SENSOR PIN 17 - PCB PINS LABELED "BITE" (GND left, sensor pin right)
if (state.biteSignal >= calibration.biteThrVal) { // we are over the threshold, calculate CC value
biteVal = mapConstrain(state.biteSignal, calibration.biteThrVal, calibration.biteMaxVal, 0, 127);
instrument.biteSignal = readTouchUtil(bitePin); // get sensor data, do some smoothing - SENSOR PIN 17 - PCB PINS LABELED "BITE" (GND left, sensor pin right)
if (instrument.biteSignal >= calibration.biteThrVal) { // we are over the threshold, calculate CC value
biteVal = mapConstrain(instrument.biteSignal, calibration.biteThrVal, calibration.biteMaxVal, 0, 127);
}
if (biteVal != state.biteVal) {
if (biteVal != instrument.biteVal) {
midiSendControlChange(currentPreset->biteCC, biteVal);
}
state.biteVal = biteVal;
instrument.biteVal = biteVal;
}
}
void autoCal() {
state.vibZero = state.vibZeroBite = 0;
instrument.vibZero = instrument.vibZeroBite = 0;
for(int i = 1 ; i <= CALIBRATE_SAMPLE_COUNT; ++i) {
state.breathZero += readPressure();
state.breathAltZero += readAltPressure();
state.vibZero += readTouchUtil(vibratoPin);
state.vibZeroBite += readTouchUtil(bitePin);
instrument.breathZero += readPressure();
instrument.breathAltZero += readAltPressure();
instrument.vibZero += readTouchUtil(vibratoPin);
instrument.vibZeroBite += readTouchUtil(bitePin);
}
state.breathZero /= CALIBRATE_SAMPLE_COUNT;
state.breathAltZero /= CALIBRATE_SAMPLE_COUNT;
state.vibZero /= CALIBRATE_SAMPLE_COUNT;
state.vibZeroBite /= CALIBRATE_SAMPLE_COUNT;
instrument.breathZero /= CALIBRATE_SAMPLE_COUNT;
instrument.breathAltZero /= CALIBRATE_SAMPLE_COUNT;
instrument.vibZero /= CALIBRATE_SAMPLE_COUNT;
instrument.vibZeroBite /= CALIBRATE_SAMPLE_COUNT;
state.vibThr = state.vibZero - currentPreset->vibSquelch;
state.vibThrLo = state.vibZero + currentPreset->vibSquelch;
state.vibThrBite = state.vibZeroBite - currentPreset->vibSquelch;
state.vibThrBiteLo = state.vibZeroBite + currentPreset->vibSquelch;
instrument.vibThr = instrument.vibZero - currentPreset->vibSquelch;
instrument.vibThrLo = instrument.vibZero + currentPreset->vibSquelch;
instrument.vibThrBite = instrument.vibZeroBite - currentPreset->vibSquelch;
instrument.vibThrBiteLo = instrument.vibZeroBite + currentPreset->vibSquelch;
state.breathThrVal = state.breathZero + calibration.breathThrValOffset;
state.breathMaxVal = state.breathThrVal + calibration.breathMaxValOffset;
state.breathAltThrVal = state.breathAltZero + calibration.breathAltThrValOffset;
state.breathAltMaxVal = state.breathAltThrVal + calibration.breathAltMaxValOffset;
instrument.breathThrVal = instrument.breathZero + calibration.breathThrValOffset;
instrument.breathMaxVal = instrument.breathThrVal + calibration.breathMaxValOffset;
instrument.breathAltThrVal = instrument.breathAltZero + calibration.breathAltThrValOffset;
instrument.breathAltMaxVal = instrument.breathAltThrVal + calibration.breathAltMaxValOffset;
}
@ -550,9 +557,9 @@ int readOctave() {
} else if (FingeringMode::HRN == fingering) { // HRN fingering
return 12 + offset + rollerHarmonic[K4][octaveR]; // roller harmonics
} else if (FingeringMode::EVR == fingering) { // HRN fingering
return 12 * (6 - octaveR) + offset;
return 12 * (6 - octaveR) + offset + instrument.octave;
} else { // EVI
return 12 * octaveR + offset;
return 12 * octaveR + offset + instrument.octave;
}
}
@ -564,10 +571,15 @@ int readSwitches() {
// Read touch pads (MPR121), compare against threshold value
bool touchKeys[12];
int16_t touchSum = 0;
for (byte i = 0; i < 12; i++) {
touchKeys[i] = readTouchKey(i) < calibration.ctouchThrVal;
int16_t val = readTouchKey(i);
touchKeys[i] = 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)
@ -577,9 +589,9 @@ int readSwitches() {
byte K6 = touchKeys[K6Pin]; // Trill key 2 (pitch change +1)
byte K7 = touchKeys[K7Pin]; // Trill key 3 (pitch change +4)
state.pinkyKey = touchKeys[K8Pin];
instrument.pinkyKey = touchKeys[K8Pin];
int qTransp = (state.pinkyKey && (currentPreset->pinkySetting < 25)) ? currentPreset->pinkySetting - 12 : 0;
int qTransp = (instrument.pinkyKey && (currentPreset->pinkySetting < 25)) ? currentPreset->pinkySetting - 12 : 0;
// Calculate midi note number from pressed keys
int fingeredNoteUntransposed = 0;
@ -609,7 +621,7 @@ int readSwitches() {
fingeredNoteUntransposed += 4;
}
int fingeredNoteRead = fingeredNoteUntransposed + state.transpose - 12 + qTransp;
int fingeredNoteRead = fingeredNoteUntransposed + instrument.transpose - 12 + qTransp;
if (fingeredNoteRead != lastFingering) { //
// reset the debouncing timer
@ -618,7 +630,7 @@ int readSwitches() {
if ((millis() - lastDeglitchTime) > currentPreset->deglitch) {
// whatever the reading is at, it's been there for longer
// than the debounce delay, so take it as the actual current state
// than the debounce delay, so take it as the actual current instrument
fingeredNote = fingeredNoteRead;
}
@ -630,10 +642,10 @@ int readSwitches() {
void noteOn(int fingeredNote, float pressureSensor, int initial_breath_value) {
// Yes, so calculate MIDI note and velocity, then send a note on event
// We should be at tonguing peak, so set velocity based on current pressureSensor value unless fixed velocity is set
state.breathSignal = constrain(max(pressureSensor, initial_breath_value), state.breathThrVal, state.breathMaxVal);
instrument.breathSignal = constrain(max(pressureSensor, initial_breath_value), instrument.breathThrVal, instrument.breathMaxVal);
byte velocitySend;
if (!currentPreset->fixedVelocity) {
unsigned int breathValHires = breathCurve(mapConstrain(state.breathSignal, state.breathThrVal, state.breathMaxVal, 0, 16383));
unsigned int breathValHires = breathCurve(mapConstrain(instrument.breathSignal, instrument.breathThrVal, instrument.breathMaxVal, 0, 16383));
velocitySend = (breathValHires >> 7) & 0x007F;
velocitySend = constrain(velocitySend + velocitySend * .1 * currentPreset->velBias, 1, 127);
} else {
@ -642,18 +654,18 @@ void noteOn(int fingeredNote, float pressureSensor, int initial_breath_value) {
breath(); // send breath data
midiSendNoteOn(fingeredNote, velocitySend); // send Note On message for new note
state.activeNote = fingeredNote;
instrument.activeNote = fingeredNote;
}
void handleOffStateActions() {
if (state.activeMIDIchannel != currentPreset->MIDIchannel) {
state.activeMIDIchannel = currentPreset->MIDIchannel; // only switch channel if no active note
midiSetChannel(state.activeMIDIchannel);
if (instrument.activeMIDIchannel != currentPreset->MIDIchannel) {
instrument.activeMIDIchannel = currentPreset->MIDIchannel; // only switch channel if no active note
midiSetChannel(instrument.activeMIDIchannel);
}
if ((state.activePatch != state.patch) && state.doPatchUpdate) {
state.activePatch = state.patch;
midiSendProgramChange(state.activePatch);
state.doPatchUpdate = 0;
if ((instrument.activePatch != instrument.patch) && instrument.doPatchUpdate) {
instrument.activePatch = instrument.patch;
midiSendProgramChange(instrument.activePatch);
instrument.doPatchUpdate = 0;
}
}
@ -661,10 +673,10 @@ void handleOffStateActions() {
Initialize the main instrument state
*/
void initState() {
state.activePatch = 0;
instrument.activePatch = 0;
state.mainState = NOTE_OFF; // initialize main state machine
state.activeMIDIchannel = currentPreset->MIDIchannel;
instrument.activeMIDIchannel = currentPreset->MIDIchannel;
midiInitialize(currentPreset->MIDIchannel);
}
@ -709,18 +721,18 @@ void runStateMachine() {
int fingeredNote = noteValueCheck(readSwitches() + readOctave());
if (state.mainState == NOTE_OFF) {
handleOffStateActions();
if ((state.breathSignal > state.breathThrVal)) {
if ((instrument.breathSignal > instrument.breathThrVal)) {
// Value has risen above threshold. Move to the RISE_WAIT
// state. Record time and initial breath value.
breath_on_time = millis();
initial_breath_value = state.breathSignal;
initial_breath_value = instrument.breathSignal;
state.mainState = RISE_WAIT; // Go to next state
}
} else if (state.mainState == RISE_WAIT) {
if ((state.breathSignal > state.breathThrVal)) {
if ((instrument.breathSignal > instrument.breathThrVal)) {
// Has enough time passed for us to collect our second sample?
if ((millis() - breath_on_time > currentPreset->velSmpDl) || (0 == currentPreset->velSmpDl) || currentPreset->fixedVelocity) {
noteOn(fingeredNote, state.breathSignal, initial_breath_value);
noteOn(fingeredNote, instrument.breathSignal, initial_breath_value);
state.mainState = NOTE_ON;
}
} else {
@ -728,19 +740,19 @@ void runStateMachine() {
state.mainState = NOTE_OFF;
}
} else if (state.mainState == NOTE_ON) {
if ((state.breathSignal < state.breathThrVal)) {
if ((instrument.breathSignal < instrument.breathThrVal)) {
// Value has fallen below threshold - turn the note off
midiSendNoteOff(state.activeNote); // send Note Off message
state.breathSignal = 0;
midiSendNoteOff(instrument.activeNote); // send Note Off message
instrument.breathSignal = 0;
state.mainState = NOTE_OFF;
} else {
if (fingeredNote != state.activeNote) {
if (fingeredNote != instrument.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.
noteOn(fingeredNote, state.breathSignal, 0);
noteOn(fingeredNote, instrument.breathSignal, 0);
delayMicroseconds(2000); // delay for midi recording fix
midiSendNoteOff(state.activeNote); // send Note Off message
midiSendNoteOff(instrument.activeNote); // send Note Off message
}
}
}
@ -748,16 +760,26 @@ void runStateMachine() {
void setup() {
if (checkButtonState(DEBUG_CONFIG)) {
Serial.begin(9600); // debug
Serial.println("Debug Startup");
}
Serial.begin(9600); // debug
Serial.println("Debug Startup");
if (CrashReport) {
while (!Serial) ; // wait for serial monitor open
Serial.print(CrashReport);
}
initHardware();
delay(100);
Serial.println(buttonState());
bool factoryReset = checkButtonState(STARTUP_FACTORY_RESET);
configManagementMode = checkButtonState(STARTUP_CONFIG);
testMode = checkButtonState(TEST_CONFIG);
initDisplay(); // Start up display and show logo
initHardware();
if (CrashReport) {
displayError("CRASH WARNING");
}
// If going into config management mode, stop here before we even touch the EEPROM.
if (configManagementMode) {
@ -766,24 +788,25 @@ void setup() {
}
// Read eeprom data into global vars
readEEPROM(factoryReset);
// readEEPROM(factoryReset);
statusLedFlash(500);
statusLedOff();
if (factoryReset) {
// Full calibration
fullAutoCal();
// fullAutoCal();
} else {
// Minimal startup calibration (atmo pressure)
autoCal();
// autoCal();
}
showVersion();
delay(1000);
initState(); // Set up midi/etc
// initState(); // Set up midi/etc
statusLedOn(); // Switch on the onboard LED to indicate power on/ready
displayOff();
}
//_______________________________________________________________________________________________ MAIN LOOP
@ -792,6 +815,9 @@ void loop() {
static unsigned long pixelUpdateTime = 0;
static const unsigned long pixelUpdateInterval = 80;
handleMenu(state, false);
return;
// If in config mgmt loop, do that and nothing else
if (configManagementMode) {
configModeLoop();
@ -799,10 +825,11 @@ void loop() {
}
if (testMode) {
handleTestMode();
return;
}
state.breathSignal = constrain(readPressure(), BREATH_LO_LIMIT, BREATH_HI_LIMIT); // Get the filtered pressure sensor reading from analog pin A0, input from sensor MP3V5004GP
instrument.breathSignal = constrain(readPressure(), BREATH_LO_LIMIT, BREATH_HI_LIMIT); // Get the filtered pressure sensor reading from analog pin A0, input from sensor MP3V5004GP
runStateMachine();
sendCCs();
@ -815,9 +842,9 @@ void loop() {
// 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(true);
handleMenu(state, true);
} else {
handleMenu(false);
handleMenu(state, false);
}
}