diff --git a/NuEVI/platformio.ini b/NuEVI/platformio.ini index 7b806db..3edc8eb 100644 --- a/NuEVI/platformio.ini +++ b/NuEVI/platformio.ini @@ -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 diff --git a/NuEVI/src/TODO b/NuEVI/src/TODO index fc0b1b4..1bf51bd 100644 --- a/NuEVI/src/TODO +++ b/NuEVI/src/TODO @@ -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 \ No newline at end of file +8. Encoder midi + +- Lever mode: pb +- Breath mode: relative +- Breath suck control? +- \ No newline at end of file diff --git a/NuEVI/src/adjustmenu.cpp b/NuEVI/src/adjustmenu.cpp deleted file mode 100644 index ed95fc2..0000000 --- a/NuEVI/src/adjustmenu.cpp +++ /dev/null @@ -1,187 +0,0 @@ -#include - -#include -#include -#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 -class AdjustMenuScreen : public MenuScreen { - public: - AdjustMenuScreen(const char* title, std::array 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 _entries; - std::array _rowDrawings; -}; - -std::array 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); -} diff --git a/NuEVI/src/config.h b/NuEVI/src/config.h index b952052..984b169 100644 --- a/NuEVI/src/config.h +++ b/NuEVI/src/config.h @@ -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 diff --git a/NuEVI/src/globals.h b/NuEVI/src/globals.h index 1b7d94f..44bf34e 100644 --- a/NuEVI/src/globals.h +++ b/NuEVI/src/globals.h @@ -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 curves; extern const unsigned short curveIn[]; diff --git a/NuEVI/src/hardware.cpp b/NuEVI/src/hardware.cpp index b727f52..fab0584 100644 --- a/NuEVI/src/hardware.cpp +++ b/NuEVI/src/hardware.cpp @@ -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) { diff --git a/NuEVI/src/hardware.h b/NuEVI/src/hardware.h index 56ada0b..8d7df73 100644 --- a/NuEVI/src/hardware.h +++ b/NuEVI/src/hardware.h @@ -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 diff --git a/NuEVI/src/led.cpp b/NuEVI/src/led.cpp index e0daf37..5b9f691 100644 --- a/NuEVI/src/led.cpp +++ b/NuEVI/src/led.cpp @@ -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); } diff --git a/NuEVI/src/menu.cpp b/NuEVI/src/menu.cpp index deb80f6..02c5843 100644 --- a/NuEVI/src/menu.cpp +++ b/NuEVI/src/menu.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "menu.h" #include "hardware.h" @@ -15,17 +16,11 @@ // constants const unsigned long debounceDelay = 30; // the debounce time; increase if the output flickers -const unsigned long buttonRepeatInterval = 50; -const unsigned long buttonRepeatDelay = 400; -const unsigned long cursorBlinkInterval = 300; // the cursor blink toggle interval time -const unsigned long patchViewTimeUp = 2000; // ms until patch view shuts off -const unsigned long menuTimeUp = 60000; // menu shuts off after one minute of button inactivity static unsigned long menuTime = 0; -static unsigned long patchViewTime = 0; -unsigned long cursorBlinkTime = 0; // the last time the cursor was toggled +bool displayOn = true; -std::array CC_NAMES = { +std::array CC_NAMES = { "Bank Select", // 0 "Mod Wheel", // 1 "Breath", // 2 @@ -156,7 +151,7 @@ std::array CC_NAMES = { "Poly Mode", // 127 }; - // 'NuEVI' or 'NuRAD' logo +// 'NuEVI' or 'NuRAD' logo #define LOGO16_GLCD_WIDTH 128 #define LOGO16_GLCD_HEIGHT 64 static const unsigned char PROGMEM nuevi_logo_bmp[] = { @@ -226,438 +221,773 @@ static const unsigned char PROGMEM nuevi_logo_bmp[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - -extern void readSwitches(void); #define OLED_RESET 4 -Adafruit_SSD1306 display(128, 64, &Wire1, OLED_RESET,1000000,1000000); +Adafruit_SSD1306 display(128, 64, &Wire1, OLED_RESET, 1000000, 1000000); +int16_t ctouchVal = 0; -MenuScreen *currentMenu = NULL; +MenuScreen* currentMenu = NULL; + +// Track pixels for faster redrawing +struct AdjustDrawing { + int row; + int thrX; + int maxX; + int valX; +}; + +struct AdjustValue { + const char* title; + const int16_t instrument_state_t::* value; + int16_t calibration_t::* thrVal; + int16_t calibration_t::* maxVal; + const int16_t limitLow; + const int16_t limitHigh; + + // If not null, thr and max are relative to zeroPoint + const int16_t instrument_state_t::* zeroPoint; +}; + +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(state_t& state, const AdjustValue& value, AdjustDrawing& drawing) { + const int thrX = mapConstrain(state.calibration->*value.thrVal, value.limitLow, value.limitHigh, 1, 127); + const int maxX = mapConstrain(state.calibration->*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(state_t& state, 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(state.instrument->*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; + } +} + +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); +} + +void drawAdjustRow(state_t& state, const AdjustValue& value, AdjustDrawing& drawing, bool highlight) { + display.fillRect(0, drawing.row, 128, 21, BLACK); + drawAdjustFrame(drawing.row); + drawAdjustTitle(value, drawing, highlight); + drawAdjustValues(state, value, drawing); + drawAdjustIndicators(state, value, drawing); +} void plotSubOption(const char* label, const char* unit = nullptr) { int text_x, unit_x; - int label_pixel_width = strlen(label)*12; + int label_pixel_width = strlen(label) * 12; + display.setTextColor(WHITE); - if(unit == nullptr) { - text_x = 96 - (label_pixel_width/2); + if (unit == nullptr) { + text_x = 96 - (label_pixel_width / 2); } else { - int unit_pixel_width = strlen(unit)*6; - int halfSum = (label_pixel_width + unit_pixel_width)/2; - text_x = 96 - halfSum; - unit_x = 96 + halfSum - unit_pixel_width; - display.setCursor(unit_x,40); - display.setTextSize(1); - display.println(unit); + int unit_pixel_width = strlen(unit) * 6; + int halfSum = (label_pixel_width + unit_pixel_width) / 2; + text_x = 96 - halfSum; + unit_x = 96 + halfSum - unit_pixel_width; + display.setCursor(unit_x, 40); + display.setTextSize(1); + display.println(unit); } - display.setTextSize(2); - display.setCursor(text_x,33); - display.println(label); + display.setTextSize(2); + display.setCursor(text_x, 33); + display.println(label); } template -static void plotMenuEntries(std::array entries, size_t cursorPos) { - display.fillRect(0, MENU_HEADER_OFFSET, 63, 64-MENU_HEADER_OFFSET, BLACK); +static void plotMenuEntries(std::array entries, size_t cursorPos, int xOffset = 0) { + display.fillRect(0 + xOffset, MENU_HEADER_OFFSET, 63 + xOffset, 64 - MENU_HEADER_OFFSET, BLACK); display.setTextSize(1); - size_t scrollPos = 0; + int scrollPos = 0; if (entries.size() >= MENU_NUM_ROWS) { - if ((cursorPos - scrollPos) > (MENU_NUM_ROWS-2) ) { - scrollPos = cursorPos - (MENU_NUM_ROWS-2); - } else if( (cursorPos - scrollPos) < 1) { + if ((cursorPos - scrollPos) > (MENU_NUM_ROWS - 2)) { + scrollPos = cursorPos - (MENU_NUM_ROWS - 2); + } else if ((cursorPos - scrollPos) < 1) { scrollPos = cursorPos - 1; } scrollPos = constrain(scrollPos, 0, entries.size() - MENU_NUM_ROWS); } - int row = 0; - int end = constrain(scrollPos + MENU_NUM_ROWS, 0, entries.size()); + size_t row = 0; + size_t end = constrain(scrollPos + MENU_NUM_ROWS, 0, entries.size()); for (size_t i = scrollPos; i < end; i++, row++) { int rowPixel = (row)*MENU_ROW_HEIGHT + MENU_HEADER_OFFSET; - display.setCursor(0,rowPixel); + display.setCursor(xOffset, rowPixel); if (cursorPos == i) { display.setTextColor(BLACK, WHITE); } else { display.setTextColor(WHITE); } - display.println(entries[i].title()); + display.println(entries[i]->title()); } } -class AboutMenu : public MenuScreen { - const char *title() { - return "ABOUT"; - } +class AboutMenu : public MenuScreen { + const char* title() { + return "ABOUT"; + } - void update(InputState input, bool redraw) { - if (redraw) { - display.clearDisplay(); - display.setCursor(49,0); - display.setTextColor(WHITE); - display.setTextSize(0); - display.println("xEVI"); - display.setCursor(16,12); - display.print("firmware v."); - display.println(FIRMWARE_VERSION); - display.print("eeprom v."); - display.println(EEPROM_VERSION); - } + bool update(state_t& state, InputState& input, bool redraw) { + if (redraw) { + display.fillRect(63, 11, 64, 52, BLACK); + display.setTextColor(WHITE); + display.setTextSize(0); + display.setCursor(64, 12); + display.print("xEVI"); + display.setCursor(64, 21); + display.print("firmware"); + display.setCursor(74, 30); + display.print("v"); + display.print(FIRMWARE_VERSION); + display.setCursor(64, 39); + display.print("eeprom"); + display.setCursor(74, 48); + display.print("v"); + display.print(EEPROM_VERSION); + return true; } + } }; template -class MainMenu : public MenuScreen { - public: - MainMenu(std::array entries) : _entries(entries) { } - void update(InputState input, bool redraw) { - if (input.changed && input.btnMenu) { - _inSubMenu = !_inSubMenu; - input.changed = false; - redraw = true; - } - - if (_inSubMenu) { - _entries[_cursorPos].update(input, redraw); - return; - } - - if (input.changed) { - _cursorPos = (_cursorPos + input.knobMenu) % _entries.size(); - } +class MainMenu : public MenuScreen { +public: + MainMenu(std::array entries) : _entries(entries) {} + bool update(state_t& state, InputState& input, bool redraw) { + bool dirty = redraw; + if (input.changed && input.knobMenu) { + _cursorPos = (_cursorPos + input.knobMenu + _entries.size()) % _entries.size(); + input.changed = false; + _entries[_cursorPos]->update(state, input, true); + dirty = true; draw(redraw); - }; - - const char *title() { - return "MENU"; - } - - private: - void draw(bool redraw) { - if (redraw) { - display.clearDisplay(); - display.setTextSize(1); - display.setCursor(0,0); - display.drawLine(0, MENU_ROW_HEIGHT, 127, MENU_ROW_HEIGHT, WHITE); - display.println("MENU"); - } - - plotMenuEntries(_entries, _cursorPos); - - } - - bool _inSubMenu = false; - size_t _cursorPos; - std::array _entries; -}; - -template -class SubMenu : public MenuScreen { - public: - SubMenu(const char *title, std::array entries) : _title(title), _entries(entries) {} - void update(InputState input, bool redraw) { - bool redrawValue = false; - if (input.changed && input.knobMenu != 0) { - _cursorPos = (_cursorPos + input.knobMenu) % _entries.size(); - draw(false); - input.changed = false; - redrawValue = true; - } - + } else { + dirty |= _entries[_cursorPos]->update(state, input, redraw); if (redraw) { draw(redraw); + dirty = true; } - - _entries[_cursorPos].update(input, redrawValue); - }; - - const char *title() { - return _title; } - private: - void draw(bool redraw) { - if (redraw) { + return dirty; + }; + + const char* title() { + return "MENU"; + } + +private: + void draw(bool redraw) { + if (redraw) { + Serial.println("main menu redraw"); + display.clearDisplay(); + display.setTextSize(1); + display.setCursor(0, 0); + display.drawLine(0, MENU_ROW_HEIGHT, 127, MENU_ROW_HEIGHT, WHITE); + display.println("MENU"); + } + + plotMenuEntries(_entries, _cursorPos); + + } + + size_t _cursorPos = 0; + std::array _entries; +}; + +template +class SubMenu : public MenuScreen { +public: + SubMenu(const char* title, std::array entries) : _title(title), _entries(entries) {} + bool update(state_t& state, InputState& input, bool redraw) { + bool redrawValue = false; + bool dirty = false; + return false; + if (input.changed && input.btnMenu) { + _focused = !_focused; + if (_focused) { + lastMenu = currentMenu; + currentMenu = this; + redraw = true; + } else { + currentMenu = lastMenu; + redraw = true; + } + } + + if (_focused && input.changed && input.knobMenu != 0) { + _cursorPos = (_cursorPos + input.knobMenu) % _entries.size(); + draw(false); + input.changed = false; + redrawValue = true; + dirty = true; + } + + if (redraw) { + draw(redraw); + dirty = true; + } + + if (_focused) { + dirty |= _entries[_cursorPos]->update(state, input, redrawValue); + } + + return dirty; + }; + + const char* title() { + return _title; + } + +private: + const char* _title; + size_t _cursorPos; + std::array _entries; + bool _focused = false; + MenuScreen* lastMenu; + + void draw(bool redraw) { + if (redraw) { + if (_focused) { display.clearDisplay(); - display.setTextSize(1); - display.setCursor(0,0); - display.drawLine(0, MENU_ROW_HEIGHT, 127, MENU_ROW_HEIGHT, WHITE); - display.println(_title); + } else { + display.fillRect(63, 11, 64, 52, BLACK); + } + display.setTextSize(1); + display.drawLine(_focused ? 0 : 63, MENU_ROW_HEIGHT, 127, MENU_ROW_HEIGHT, WHITE); + display.setCursor(_focused ? 0 : 63, 0); + display.println(_title); + } + + plotMenuEntries(_entries, _cursorPos, _focused ? 0 : 63); + } +}; + +template< + size_t L, + typename T, + typename U, + typename U_p +> +class ValueMenu : public MenuScreen { +public: + ValueMenu( + const char* title, + U_p state_t::* obj, + T U::* value, + const T min, const T max, const bool wrap = false, + const std::array labels = {} + ) : + _title(title), + _obj(obj), + _value(value), + _min(min), + _max(max), + _wrap(wrap), + _labels(labels) { + } + + bool update(state_t& state, InputState& input, bool redraw) { + if (input.knobVal1) { + int32_t newVal = (state.*_obj)->*_value + input.knobVal1; + if (newVal > _max) { + if (_wrap) { + newVal = _min; + } else { + newVal = _max; + } + } else if (newVal < _min) { + if (_wrap) { + newVal = _max; + } else { + newVal = _min; + } } - plotMenuEntries(_entries, _cursorPos); + (state.*_obj)->*_value = newVal; + + draw(state, redraw); + return true; + } else if (redraw) { + draw(state, redraw); + return true; } - const char *_title; - size_t _cursorPos; - std::array _entries; + + return false; + } + const char* title() { + return _title; + } +private: + const char* _title; + U_p state_t::* _obj; + T U::* _value; + const T _min; + const T _max; + const bool _wrap; + const std::array _labels; + + void draw(state_t &state, bool redraw) { + if (redraw) { + display.fillRect(63, 11, 64, 52, BLACK); + display.drawRect(63, 11, 64, 52, WHITE); + display.setTextSize(1); + int len = strlen(this->_title); + + display.setCursor(95 - len * 3, 15); + display.println(this->_title); + } else { + display.fillRect(64, 33, 62, 29, BLACK); + } + + char buffer[12]; + snprintf(buffer, 12, "%+d", (state.*_obj)->*_value); + size_t label_idx = (state.*_obj)->*_value - _min; + if (_labels.size() > 0 && label_idx >= 0 && label_idx < _labels.size()) { + plotSubOption(_labels[label_idx], buffer); + } else { + plotSubOption(buffer); + } + } }; template< size_t L, typename T > -class ValueMenu : public MenuScreen { +class PresetValueMenu : public ValueMenu { public: - ValueMenu( - const char * title, T &value, - const T min, const T max, const bool wrap = false, - const std::array labels = {} - ) : _title(title), _value(value), _min(min), _max(max), _wrap(wrap), _labels(labels) {} - void update(InputState input, bool redraw) { - if (input.knobMenu) { - _value = (_value + input.knobMenu); - if (_value > _max) { - _value = _min; - } else if (_value < _min) { - _value = _max; - } - - draw(redraw); - } else if (redraw) { - draw(redraw); - } - } - private: - T &_value; - const T _min; - const T _max; - const bool _wrap; - const std::array _labels; - const char *_title; - - void draw(bool redraw) { - if (redraw) { - display.fillRect(63,11,64,52,BLACK); - display.drawRect(63,11,64,52,WHITE); - display.setTextSize(1); - int len = strlen(this->_title); - - display.setCursor(95-len*3,15); - display.println(this->_title); - } - - char buffer[12]; - snprintf(buffer, 12, "%+d", _value); - size_t label_idx = _value - _min; - if (_labels.size() > 0 && label_idx >= 0 && label_idx <= _labels.size()) { - plotSubOption(_labels[label_idx], buffer); - } else { - plotSubOption(buffer); - } - } + PresetValueMenu( + const char* title, + T preset_t::* value, + const T min, const T max, const bool wrap = false, + const std::array labels = {} + ) : ValueMenu(title, &state_t::currentPreset, value, min, max, wrap, labels) {} }; +template< + size_t L, + typename T +> +class StateValueMenu : public ValueMenu { + public: + StateValueMenu( + const char* title, + T instrument_state_t::* value, + const T min, const T max, const bool wrap = false, + const std::array labels = {} + ) : ValueMenu(title, &state_t::instrument, value, min, max, wrap, labels) {} +}; -void drawFlash(int x, int y){ - display.drawLine(x+5,y,x,y+6,WHITE); - display.drawLine(x,y+6,x+5,y+6,WHITE); - display.drawLine(x,y+12,x+5,y+6,WHITE); -} +template< + size_t N, + typename T +> +class ChoiceMenu : public MenuScreen { +public: + ChoiceMenu( + const char* title, T preset_t::* value, + std::array choices, + const std::array labels = {} + ) : _title(title), _value(value), _choices(choices), _labels(labels) { + } + bool update(state_t& state, InputState& input, bool redraw) { + auto it = std::find(_choices.begin(), _choices.end(), state.currentPreset->*_value); + if (input.knobVal1) { + it += input.knobVal1; + if (it >= _choices.end()) { + state.currentPreset->*_value = _choices.front(); + it = _choices.begin(); + } else if (it < _choices.begin()) { + state.currentPreset->*_value = _choices.back(); + it = _choices.end(); + } else { + state.currentPreset->*_value = _choices[it]; + } -void initDisplay() { + draw(redraw, it - _choices.begin()); + return true; + } else if (redraw) { + draw(redraw, it - _choices.begin()); + return true; + } - // by default, we'll generate the high voltage from the 3.3v line internally! (neat!) - display.begin(SSD1306_SWITCHCAPVCC, 0x3D); // initialize with the I2C addr 0x3D (for the 128x64) + return false; + } + const char* title() { + return _title; + } +private: + const char* _title; + T preset_t::*_value; + const std::array _choices; + const std::array _labels; - // Show image buffer on the display hardware. - // Since the buffer is intialized with an Adafruit splashscreen - // internally, this will display the splashscreen. + void draw(bool redraw, size_t label_idx) { + if (redraw) { + display.fillRect(63, 11, 64, 52, BLACK); + display.drawRect(63, 11, 64, 52, WHITE); + display.setTextSize(1); + int len = strlen(this->_title); - display.clearDisplay(); - #if defined(NURAD) - display.drawBitmap(0,0,nurad_logo_bmp,LOGO16_GLCD_WIDTH,LOGO16_GLCD_HEIGHT,1); - #else - display.drawBitmap(0,0,nuevi_logo_bmp,LOGO16_GLCD_WIDTH,LOGO16_GLCD_HEIGHT,1); - #endif - display.display(); -} + display.setCursor(95 - len * 3, 15); + display.println(this->_title); + } -void displayError(const char *error) { - display.clearDisplay(); - display.setTextColor(WHITE); - display.setTextSize(1); - display.setCursor(0,0); - display.println(error); - display.display(); - Serial.print("ERROR: "); - Serial.println(error); -} + char buffer[12]; + snprintf(buffer, 12, "%+d", _value); + if (_labels.size() > 0 && label_idx >= 0 && label_idx <= _labels.size()) { + plotSubOption(_labels[label_idx], buffer); + } else { + plotSubOption(buffer); + } + } +}; -void showVersion() { - display.setTextColor(WHITE); - display.setTextSize(1); - display.setCursor(85,52); - display.print("v."); - display.println(FIRMWARE_VERSION); - display.display(); -} +class StatusMenu : public MenuScreen { +public: + StatusMenu(MenuScreen& mainMenu) : _mainMenu(mainMenu) { + Serial.println("init statusmenu"); + } + bool update(state_t& state, InputState& input, bool redraw) { + // TODO: handle cc/patch/preset changes + Serial.println("status update"); + if (input.changed && input.btnMenu) { + Serial.println("menu"); + currentMenu = &_mainMenu; + input.changed = false; + return currentMenu->update(state, input, true); + } -static void clearSub(){ - display.fillRect(63,11,64,52,BLACK); -} + return false; + }; +private: + MenuScreen& _mainMenu; +}; + +class ExitMenu : public MenuScreen { +public: + ExitMenu() {} + bool update(state_t& state, InputState& input, bool redraw) { + if (redraw) { + display.fillRect(63, 11, 64, 52, BLACK); + } + + if (input.changed && input.btnMenu) { + displayOff(); + } + + return false; + } + const char* title() { + return "EXIT"; + } +}; + +template +class AdjustMenuScreen : public MenuScreen { +public: + AdjustMenuScreen(const char* title, std::array entries) : _title(title), _entries(entries) {} + bool update(state_t& state, InputState& input, bool redraw) { + if (input.changed && input.btnMenu) { + _focused = !_focused; + } + + if (!_focused) { + display.fillRect(63, 11, 64, 52, BLACK); + return true; + } + + bool redrawIndicators = false; + if (input.changed) { + if (input.knobMenu) { + _selectedEntry = _selectedEntry + input.knobMenu; + redraw = true; + } + + AdjustValue value = _entries[_selectedEntry]; + if (input.knobVal1) { + state.calibration->*value.thrVal += input.knobVal1; + state.calibration->*value.thrVal = constrain(state.calibration->*value.thrVal, value.limitLow, value.limitHigh); + redrawIndicators = true; + } + + if (input.knobVal2) { + state.calibration->*value.maxVal += input.knobVal2; + state.calibration->*value.maxVal = constrain(state.calibration->*value.maxVal, value.limitLow, value.limitHigh); + redrawIndicators = true; + } + } else { + draw(state, redrawIndicators, redraw); + } + + return true; + } + const char* title() { + return _title; + } +private: + const char* _title; + size_t _selectedEntry = 0; + std::array _entries; + std::array _rowDrawings; + bool _focused = false; + + void draw(state_t& state, 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(state, _entries[i], _rowDrawings[i], i == _selectedEntry); + } else if (redrawIndicators && i == _selectedEntry) { + drawAdjustIndicators(state, _entries[i], _rowDrawings[i]); + } else { + drawAdjustValues(state, _entries[i], _rowDrawings[i]); + } + } + } +}; + +std::array adjustValues = { { + {"BREATH", &instrument_state_t::breathSignal, &calibration_t::breathThrValOffset, &calibration_t::breathMaxValOffset, + BREATH_LO_LIMIT, BREATH_HI_LIMIT, &instrument_state_t::breathZero}, + {"BR ALT", &instrument_state_t::breathAltSignal, &calibration_t::breathAltThrValOffset, &calibration_t::breathAltMaxValOffset, + BREATH_LO_LIMIT, BREATH_HI_LIMIT, &instrument_state_t::breathAltZero}, + {"BITE",&instrument_state_t::biteSignal, &calibration_t::biteThrVal, &calibration_t::biteMaxVal, BITE_LO_LIMIT, BITE_HI_LIMIT, NULL}, + {"PB DOWN",&instrument_state_t::pbDnSignal, &calibration_t::pbDnThrVal, &calibration_t::pbDnMaxVal, PITCHB_LO_LIMIT, PITCHB_HI_LIMIT, NULL}, + {"PB UP", &instrument_state_t::pbUpSignal, &calibration_t::pbUpThrVal, &calibration_t::pbUpMaxVal, PITCHB_LO_LIMIT, PITCHB_HI_LIMIT, NULL}, + {"EXTRA", &instrument_state_t::extraSignal, &calibration_t::extraThrVal, &calibration_t::extraMaxVal, EXTRA_LO_LIMIT, EXTRA_HI_LIMIT, NULL}, + {"LEVER", &instrument_state_t::leverSignal, &calibration_t::leverThrVal, &calibration_t::leverMaxVal, LEVER_LO_LIMIT, LEVER_HI_LIMIT, NULL}, + {"TOUCH", &instrument_state_t::avgCTouchSignal, &calibration_t::ctouchThrVal, &calibration_t::ctouchThrVal, CTOUCH_LO_LIMIT, CTOUCH_HI_LIMIT, NULL}, +}}; + +const AdjustMenuScreen<8> adjustMenu("ADJUST", adjustValues); -static void clearSubValue() { - display.fillRect(65, 24, 60, 37, BLACK); -} //*********************************************************** -const MenuScreen breathModeMenu = ValueMenu<4, BreathMode>("BREATH MODE", currentPreset->breathMode, 0, 1, true, {{ "STANDARD", "LSB", "AT", "LSB_AT" }}); -const MenuScreen breathCCMenu = ValueMenu<128, uint8_t>("BREATH CC", currentPreset->breathCC, 0, 127, true, CC_NAMES); -const MenuScreen velocityMenu = ValueMenu<1, uint8_t>("VELOCITY", currentPreset->fixedVelocity, 0, 127, true, {{ "DYN" }}); -const MenuScreen curveMenu = ValueMenu<0, uint8_t>("CURVE", currentPreset->breathCurve, 0, 12); // TODO: curve display -const MenuScreen velSmpDlMenu = ValueMenu<1, uint8_t>("VEL DELAY", currentPreset->velSmpDl, 0, 30, true, { "OFF" }); // TODO: unit ms -const MenuScreen velBiasMenu = ValueMenu<1, uint8_t>("VEL BOOST", currentPreset->velBias, 0, 30, true, { "OFF" }); -const MenuScreen breathIntervalMenu = ValueMenu<0, uint8_t>("BR INTERV", currentPreset->breathInterval, 0, 30, true); -const MenuScreen trill3Menu = ValueMenu<0, int8_t>("TRILL3", currentPreset->trill3_interval, 3, 4, true, {}); -const MenuScreen cvTuneMenu = ValueMenu<0, int8_t>("CV Tune", currentPreset->cvTune, -100, 100, false, {}); -const MenuScreen cvVibMenu = ValueMenu<0, uint8_t>("CV Vib LFO", currentPreset->cvVibRate, 0, 8, false, {}); -const MenuScreen cvScaleMenu = ValueMenu<0, int8_t>("CV SCALING", currentPreset->cvScale, -100, 100, false, {}); -const std::array transposeLabels = { +const ChoiceMenu<4, BreathMode> breathModeMenu("BREATH MODE", &preset_t::breathMode, { { BREATH_STD, BREATH_LSB, BREATH_AT, BREATH_LSB_AT} }, { { "STANDARD", "LSB", "AT", "LSB_AT" } }); +const PresetValueMenu<128, uint8_t> breathCCMenu("BREATH CC", &preset_t::breathCC, 0, 127, true, CC_NAMES); +const PresetValueMenu<1, uint8_t> velocityMenu("VELOCITY", &preset_t::fixedVelocity, 0, 127, true, { { "DYN" } }); +const PresetValueMenu<0, uint8_t> curveMenu("CURVE", &preset_t::breathCurve, 0, 12); // TODO: curve display +const PresetValueMenu<1, uint8_t> velSmpDlMenu("VEL DELAY", &preset_t::velSmpDl, 0, 30, true, { "OFF" }); // TODO: unit ms +const PresetValueMenu<1, uint8_t> velBiasMenu("VEL BOOST", &preset_t::velBias, 0, 30, true, { "OFF" }); +const PresetValueMenu<0, uint8_t> breathIntervalMenu("BR INTERV", &preset_t::breathInterval, 0, 30, true); +const PresetValueMenu<0, int8_t> trill3Menu("TRILL3", &preset_t::trill3_interval, 3, 4, true, {}); +const PresetValueMenu<0, int8_t> cvTuneMenu("CV Tune", &preset_t::cvTune, -100, 100, false, {}); +const PresetValueMenu<0, uint8_t> cvVibMenu("CV Vib LFO", &preset_t::cvVibRate, 0, 8, false, {}); +const PresetValueMenu<0, int8_t> cvScaleMenu("CV SCALING", &preset_t::cvScale, -100, 100, false, {}); +const std::array transposeLabels = { "C>", "C#>", "D>", "D#>", "E>", "F>", "F#>", "G>", "G#>", "A>", "Bb>", "B>", ">C<", "("TRANSPOSE", state.transpose, -12, 12, true, transposeLabels); -const MenuScreen octaveMenu = ValueMenu<0, uint8_t>("OCTAVE", state.octave, -3, 3, true, {}); -const MenuScreen midiMenu = ValueMenu<0, byte>("MIDI CH", currentPreset->MIDIchannel, 1, 16, false, {}); -const MenuScreen vibDepthMenu = ValueMenu<1, uint8_t>("DEPTH", currentPreset->vibratoDepth, 0, 9, true, {"OFF"}); -const MenuScreen vibRetnMenu = ValueMenu<1, uint8_t>("RETURN", currentPreset->vibRetn, 0, 4, true, {"OFF"}); -const MenuScreen vibSenseMenu = ValueMenu<0, uint8_t>("SENSE LVR", currentPreset->vibSens, 0, 12); -const MenuScreen vibSquelchMenu = ValueMenu<0, uint8_t>("SQUELCH LVR", currentPreset->vibSquelch, 0, 12); -const MenuScreen vibDirMenu = ValueMenu<2, VibratoMode>("DIRECTION", currentPreset->vibratoMode, VSTART_DOWN, VSTART_UP, true, { "START DOWN", "START UP"}); -const MenuScreen biteCtlMenu = ValueMenu<4, ExtraControl>("BITE CTL", currentPreset->biteControl, 0, 3, true, {{ +const StateValueMenu<25, int8_t> transposeMenu("TRANSPOSE", &instrument_state_t::transpose, -12, 12, true, transposeLabels); +const StateValueMenu<0, int8_t> octaveMenu("OCTAVE", &instrument_state_t::octave, -3, 3, true, {}); +const PresetValueMenu<0, byte> midiMenu("MIDI CH", &preset_t::MIDIchannel, 1, 16, false, {}); +const PresetValueMenu<1, uint8_t> vibDepthMenu("DEPTH", &preset_t::vibratoDepth, 0, 9, true, { "OFF" }); +const PresetValueMenu<1, uint8_t> vibRetnMenu("RETURN", &preset_t::vibRetn, 0, 4, true, { "OFF" }); +const PresetValueMenu<0, uint8_t> vibSenseMenu("SENSE LVR", &preset_t::vibSens, 0, 12); +const PresetValueMenu<0, uint8_t> vibSquelchMenu("SQUELCH LVR", &preset_t::vibSquelch, 0, 12); +const ChoiceMenu<2, VibratoMode> vibDirMenu("DIRECTION", &preset_t::vibratoMode, { {VSTART_DOWN, VSTART_UP} }, { "START DOWN", "START UP" }); +const ChoiceMenu<4, ExtraControl> biteCtlMenu("BITE CTL", &preset_t::biteControl, { {OFF, VIBRATO, GLIDE, CC} }, { { "OFF", "VIBRATO", "GLIDE", "CC" -}}); -const MenuScreen biteCCMenu = ValueMenu<128, uint8_t>("BITE CC", currentPreset->biteCC, 0, 127, true, CC_NAMES); -const MenuScreen leverCtlMenu = ValueMenu<4, ExtraControl>("LEVER CTL", currentPreset->leverControl, 0, 3, true, { +} }); +const PresetValueMenu<128, uint8_t> biteCCMenu("BITE CC", &preset_t::biteCC, 0, 127, true, CC_NAMES); +const ChoiceMenu<4, ExtraControl> leverCtlMenu("LEVER CTL", &preset_t::leverControl, { {OFF, VIBRATO, GLIDE, CC} }, { "OFF", "VIBRATO", "GLIDE", "CC" -}); -const MenuScreen leverCCMenu = ValueMenu<128, uint8_t>("LEVER CC", currentPreset->leverCC, 0, 127, true, CC_NAMES); -const MenuScreen portMenu = ValueMenu<4, PortamentoMode>("GLIDE MOD", currentPreset->portamentoMode, 0, 3, true, { + }); +const PresetValueMenu<128, uint8_t> leverCCMenu("LEVER CC", &preset_t::leverCC, 0, 127, true, CC_NAMES); +const ChoiceMenu<4, PortamentoMode> portMenu("GLIDE MOD", &preset_t::portamentoMode, { {POFF, PON, PSWITCH_ONLY, PGLIDE_ONLY} }, { "OFF", "ON", "SWITCH_ONLY", "GLIDE_ONLY", -}); -const MenuScreen portLimitMenu = ValueMenu<0, uint8_t>("GLIDE LMT", currentPreset->portamentoLimit, 1, 127, true); -const MenuScreen pitchBendMenu = ValueMenu<0, uint8_t>("PITCHBEND", currentPreset->PBdepth, 0, 12, true); -const MenuScreen extraCtlMenu = ValueMenu<4, ExtraControl>("EXCT CC A", currentPreset->extraControl, 0,4, true, { + }); +const PresetValueMenu<0, uint8_t> portLimitMenu("GLIDE LMT", &preset_t::portamentoLimit, 1, 127, true); +const PresetValueMenu<0, uint8_t> pitchBendMenu("PITCHBEND", &preset_t::PBdepth, 0, 12, true); +const ChoiceMenu<4, ExtraControl> extraCtlMenu("EXCT CC A", &preset_t::extraControl, { {OFF, VIBRATO, GLIDE, CC} }, { "OFF", "ON", "SWITCH_ONLY", "GLIDE_ONLY", -}); -const MenuScreen extraCCMenu = ValueMenu<128, uint8_t>("EXCT CC", currentPreset->extraCC, 0,4, true, CC_NAMES); -const MenuScreen deglitchMenu = ValueMenu<1, uint8_t>("DEGLITCH", currentPreset->deglitch, 0, 70, true, {"OFF"}); -const MenuScreen pinkyMenu = ValueMenu<29, uint8_t>("PINKY KEY", currentPreset->pinkySetting, 0, 29, true, { + }); +const PresetValueMenu<128, uint8_t> extraCCMenu("EXCT CC", &preset_t::extraCC, 0, 4, true, CC_NAMES); +const PresetValueMenu<1, uint8_t> deglitchMenu("DEGLITCH", &preset_t::deglitch, 0, 70, true, { "OFF" }); +const PresetValueMenu<29, uint8_t> pinkyMenu("PINKY KEY", &preset_t::pinkySetting, 0, 29, true, { "-12", "-11", "-10", "-9", "-8", "-7", "-6", "-5", "-4", "-3", "-2", "-1", - "PB/2", + "PB/2", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "PBD", "GLD", "MOD", "QTN" -}); -const MenuScreen fingeringMenu = ValueMenu<4, FingeringMode>("FINGERING", currentPreset->fingering, 0, 3, true, { + }); +const ChoiceMenu<4, FingeringMode> fingeringMenu("FINGERING", &preset_t::fingering, { EVI, EVR, TPT, HRN }, { "EVI", - "EVR", + "EVR", "TPT", "HRN", -}); -const MenuScreen rollerMenu = ValueMenu<6, RollerMode>("ROLLRMODE", currentPreset->rollerMode, 1, 6, true, { + }); +const ChoiceMenu<6, RollerMode> rollerMenu("ROLLRMODE", &preset_t::rollerMode, { + HIGHEST, + HIGHEST_EXTEND, + HIGHEST_PAIR, + HIGHEST_PAIR_EXTEND, + PARTIAL, + PARTIAL_EXTEND + }, { "HIGHEST", "HIGHEST_EXTEND", "HIGHEST_PAIR", "HIGHEST_PAIR_EXTEND", "PARTIAL", "PARTIAL_EXTEND", -}); + }); -std::array breathMenuEntries = { - breathModeMenu, - breathCCMenu, - velocityMenu, - curveMenu, - velSmpDlMenu, - velBiasMenu, - breathIntervalMenu, +std::array breathMenuEntries = { + &breathModeMenu, + &breathCCMenu, + &velocityMenu, + &curveMenu, + &velSmpDlMenu, + &velBiasMenu, + &breathIntervalMenu, }; -const MenuScreen breathMenu = SubMenu<7>("BREATH SETUP", breathMenuEntries); +const SubMenu<7> breathMenu("BR SETUP", breathMenuEntries); -const std::array controlMenuEntries = { - fingeringMenu, - rollerMenu, - biteCtlMenu, - biteCCMenu, - leverCtlMenu, - leverCCMenu, - extraCtlMenu, - extraCCMenu, - portMenu, - portLimitMenu, - deglitchMenu, - pinkyMenu, - pitchBendMenu +const std::array controlMenuEntries = { + &fingeringMenu, + &rollerMenu, + &biteCtlMenu, + &biteCCMenu, + &leverCtlMenu, + &leverCCMenu, + &extraCtlMenu, + &extraCCMenu, + &portMenu, + &portLimitMenu, + °litchMenu, + &pinkyMenu, + &pitchBendMenu }; -const MenuScreen controlMenu = SubMenu<13>("CONTROL SETUP", controlMenuEntries); +const SubMenu<13> controlMenu("CTL SETUP", controlMenuEntries); -const std::array vibratoMenuEntries = { - vibDepthMenu, - vibRetnMenu, - vibDirMenu, - vibSenseMenu, - vibSquelchMenu, +const std::array vibratoMenuEntries = { + &vibDepthMenu, + &vibRetnMenu, + &vibDirMenu, + &vibSenseMenu, + &vibSquelchMenu, }; -const MenuScreen vibratoMenu = SubMenu<5>("VIBRATO", vibratoMenuEntries); +const SubMenu<5> vibratoMenu("VIBRATO", vibratoMenuEntries); -const MenuScreen aboutMenu = AboutMenu(); +const AboutMenu aboutMenu = AboutMenu(); -std::array extrasMenuEntries = { - trill3Menu, - cvTuneMenu, - cvScaleMenu, - cvVibMenu, +std::array extrasMenuEntries = { + &trill3Menu, + &cvTuneMenu, + &cvScaleMenu, + &cvVibMenu, }; -const MenuScreen extrasMenu = SubMenu<4>("EXTRAS", extrasMenuEntries); +const SubMenu<4> extrasMenu("EXTRAS", extrasMenuEntries); +const ExitMenu exitMenu = ExitMenu(); // Top-level screens -const std::array mainMenuEntries = { - transposeMenu, - octaveMenu, - midiMenu, - breathMenu, - controlMenu, - vibratoMenu, - adjustMenu, - extrasMenu, - aboutMenu, +const std::array mainMenuEntries = { + &transposeMenu, + &octaveMenu, + &midiMenu, + &breathMenu, + &controlMenu, + &vibratoMenu, + &adjustMenu, + &extrasMenu, + &aboutMenu, + &exitMenu, }; -const MenuScreen mainMenuPage = MainMenu<9>(mainMenuEntries); -// const MenuScreen patchPage -// const MenuScreen presetPage -// const MenuScreen ccPage +MainMenu<10> mainMenu(mainMenuEntries); +StatusMenu statusMenu = StatusMenu(mainMenu); +/* static void curveCustomDraw() { - const char* curveMenuLabels[] = {"-4", "-3", "-2", "-1", "LIN", "+1", "+2", + const char* curveMenuLabels[] = { "-4", "-3", "-2", "-1", "LIN", "+1", "+2", "+3", "+4", "S1", "S2", "Z1", "Z2" }; int y0 = 0, x0 = 0; - int scale = ((1<<14)-1)/60; - for(int x = x0; x < 60; x+=1) { - int y = multiMap(x*scale, curveIn, curves[currentPreset->breathCurve], 17); - y = (y*37) / ((1<<14)-1); + int scale = ((1 << 14) - 1) / 60; + for (int x = x0; x < 60; x += 1) { + int y = multiMap(x * scale, curveIn, curves[currentPreset->breathCurve], 17); + y = (y * 37) / ((1 << 14) - 1); display.drawLine(x0 + 65, 60 - y0, x + 65, 60 - y, WHITE); x0 = x; y0 = y; } - display.setCursor(125 - 3*6, 60-8 ); + display.setCursor(125 - 3 * 6, 60 - 8); display.setTextSize(0); display.print(curveMenuLabels[currentPreset->breathCurve]); } +*/ static bool updateSensorPixelsFlag = false; @@ -670,32 +1000,21 @@ void drawSensorPixels() { static InputState readInput(uint32_t timeNow) { static uint32_t lastDebounceTime = 0; // the last time the output pin was toggled - static uint32_t buttonRepeatTime = 0; - static uint32_t buttonPressedTime = 0; static uint8_t lastDeumButtons = 0; static uint8_t deumButtonState = 0; - static int lastKnobs[] = {0, 0, 0, 0}; + static int lastKnobs[] = { 0, 0, 0, 0 }; InputState input; uint8_t deumButtons = buttonState(); - // check to see if you just pressed the button - // (i.e. the input went from LOW to HIGH), and you've waited long enough - // since the last press to ignore any noise: - // If the switch changed, due to noise or pressing: + // Debounce buttons if (deumButtons != lastDeumButtons) { - // reset the debouncing timer lastDebounceTime = timeNow; } if ((timeNow - lastDebounceTime) > debounceDelay) { - // whatever the reading is at, it's been there for longer than the debounce - // delay, so take it as the actual current state: - - // if the button state has changed: if (deumButtons != deumButtonState) { - // keys.current = deumButtons; input.btnMenu = deumButtons & BTN_MENU; input.btnVal1 = deumButtons & BTN_VAL1; input.btnVal2 = deumButtons & BTN_VAL2; @@ -704,28 +1023,28 @@ static InputState readInput(uint32_t timeNow) { deumButtonState = deumButtons; menuTime = timeNow; - buttonPressedTime = timeNow; } } for (int i = 0; i < 4; i++) { int val = readKnob(i); if (val != lastKnobs[i]) { - input.changed = 1; + input.changed = true; switch (i) { - case 0: - input.knobMenu = val; - break; - case 1: - input.knobVal1 = val; - break; - case 2: - input.knobVal2 = val; - break; - case 3: - input.knobPreset = val; - break; + case 0: + input.knobMenu = val; + break; + case 1: + input.knobVal1 = val; + break; + case 2: + input.knobVal2 = val; + break; + case 3: + input.knobPreset = val; + break; } + menuTime = timeNow; } } @@ -735,21 +1054,66 @@ static InputState readInput(uint32_t timeNow) { return input; } +void displayOff() { + display.clearDisplay(); + display.display(); + display.ssd1306_command(SSD1306_DISPLAYOFF); + currentMenu = &statusMenu; + displayOn = false; +} +void displayError(const char* error) { + display.clearDisplay(); + display.setTextColor(WHITE); + display.setTextSize(1); + display.setCursor(0, 0); + display.println(error); + display.display(); + Serial.print("ERROR: "); + Serial.println(error); +} -void handleMenu(bool draw) { +void showVersion() { + display.setTextColor(WHITE); + display.setTextSize(1); + display.setCursor(85, 52); + display.print("v"); + display.println(FIRMWARE_VERSION); + display.display(); +} + +void initDisplay() { + // by default, we'll generate the high voltage from the 3.3v line internally! (neat!) + display.begin(SSD1306_SWITCHCAPVCC, 0x3D); // initialize with the I2C addr 0x3D (for the 128x64) + + // Show image buffer on the display hardware. + // Since the buffer is intialized with an Adafruit splashscreen + // internally, this will display the splashscreen. + + display.clearDisplay(); + display.drawBitmap(0, 0, nuevi_logo_bmp, LOGO16_GLCD_WIDTH, LOGO16_GLCD_HEIGHT, 1); + display.display(); + currentMenu = &statusMenu; +} + +void handleMenu(state_t& state, bool draw) { unsigned long timeNow = millis(); InputState input = readInput(timeNow); - // shut off menu system if not used for a while (changes not stored by exiting a setting manually will not be stored in EEPROM) - if (currentMenu && ((timeNow - menuTime) > menuTimeUp)) { - display.ssd1306_command(SSD1306_DISPLAYOFF); - display.clearDisplay(); - currentMenu = NULL; + // shut off menu system if not used for a while + if (displayOn && ((timeNow - menuTime) > MENU_AUTO_OFF_TIME)) { + displayOff(); + } + + if (input.changed) { + display.ssd1306_command(SSD1306_DISPLAYON); + displayOn = true; } if (currentMenu && (draw || input.changed)) { - currentMenu->update(input, false); + if (currentMenu->update(state, input, false)) { + display.display(); + } } } \ No newline at end of file diff --git a/NuEVI/src/menu.h b/NuEVI/src/menu.h index 3bdc009..80f3bc6 100644 --- a/NuEVI/src/menu.h +++ b/NuEVI/src/menu.h @@ -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 diff --git a/NuEVI/src/settings.cpp b/NuEVI/src/settings.cpp index bc2f3eb..d4ef625 100644 --- a/NuEVI/src/settings.cpp +++ b/NuEVI/src/settings.cpp @@ -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; iutil"); + Serial.print(">util"); Serial.print(i); Serial.print(":"); Serial.println(readTouchUtil(i)); } } + + delay(2); } \ No newline at end of file diff --git a/NuEVI/src/xEVI.cpp b/NuEVI/src/xEVI.cpp index 5e30ce4..3b8afc3 100644 --- a/NuEVI/src/xEVI.cpp +++ b/NuEVI/src/xEVI.cpp @@ -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); } }