diff --git a/NuEVI/src/config.h b/NuEVI/src/config.h index 984b169..9f1e640 100644 --- a/NuEVI/src/config.h +++ b/NuEVI/src/config.h @@ -11,7 +11,7 @@ #define CCN_Port 5 // Controller number for portamento level #define CCN_PortOnOff 65// Controller number for portamento on/off #define START_NOTE 24 // set startNote to C (change this value in steps of 12 to start in other octaves) -#define FILTER_FREQ 30.0 +#define FILTER_FREQ 10.0 #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 @@ -29,6 +29,8 @@ #define BTN_VAL1 0x2 #define BTN_VAL2 0x4 #define BTN_PRESET 0x8 +#define KNOB_CURVE_THRESHOLD 100 +#define KNOB_CURVE_MULTIPLIER 5 // Send breath CC data no more than every CC_BREATH_INTERVAL // milliseconds @@ -42,17 +44,19 @@ #define maxSamplesNum 120 -#define BREATH_LO_LIMIT 8000 -#define BREATH_HI_LIMIT 10000 -#define BITE_LO_LIMIT 0 +#define BREATH_LO_LIMIT 9000 +#define BREATH_HI_LIMIT 12000 +#define BITE_LO_LIMIT 500 #define BITE_HI_LIMIT 1000 -#define PITCHB_LO_LIMIT 0 +#define PITCHB_LO_LIMIT 500 #define PITCHB_HI_LIMIT 1000 -#define EXTRA_LO_LIMIT 0 +#define EXTRA_LO_LIMIT 500 #define EXTRA_HI_LIMIT 1000 -#define CTOUCH_LO_LIMIT 0 +#define CTOUCH_LO_LIMIT 500 #define CTOUCH_HI_LIMIT 1000 -#define LEVER_LO_LIMIT 0 +#define LEVER_LO_LIMIT 500 #define LEVER_HI_LIMIT 1000 +#define SPIKE_LO_LIMIT 0 +#define SPIKE_HI_LIMIT 100 #endif diff --git a/NuEVI/src/globals.h b/NuEVI/src/globals.h index 44bf34e..3421502 100644 --- a/NuEVI/src/globals.h +++ b/NuEVI/src/globals.h @@ -18,6 +18,9 @@ // A note is sounding #define NOTE_ON 3 +// We turned the note off due to a HF spike +#define SPIKE_HOLD 4 + enum PinkyMode : uint8_t { PBD = 12, GLD = 25, @@ -86,6 +89,7 @@ struct instrument_state_t { // Raw sensor signals int16_t breathSignal = 0; // breath level (smoothed) not mapped to CC value int16_t breathAltSignal = 0; + int16_t spikeSignal = 0; // breath level (smoothed) not mapped to CC value int16_t biteSignal = 0; // capacitance data from bite sensor, for midi cc and threshold checks int16_t leverSignal = 0; int16_t pbUpSignal = 0; @@ -130,6 +134,10 @@ struct instrument_state_t { extern const std::array curves; extern const unsigned short curveIn[]; +// Re-zero floating calibration values +extern void rezero(); +extern void fullAutoCal(); + extern unsigned int multiMap(unsigned short val, const unsigned short * _in, const unsigned short * _out, uint8_t size); #define mapConstrain(val, in_lo, in_hi, out_lo, out_hi) map(constrain(val, in_lo, in_hi), in_lo, in_hi, out_lo, out_hi) diff --git a/NuEVI/src/hardware.cpp b/NuEVI/src/hardware.cpp index fab0584..cbe9f75 100644 --- a/NuEVI/src/hardware.cpp +++ b/NuEVI/src/hardware.cpp @@ -8,6 +8,7 @@ #include "FilterOnePole.h" // for the breath signal low-pass filtering, from https://github.com/JonHub/Filters FilterOnePole breathFilter; FilterOnePole breathAltFilter; +FilterOnePole spikeFilter; Adafruit_MPR121 touchSensorKeys = Adafruit_MPR121(); Adafruit_MPR121 touchSensorUtil = Adafruit_MPR121(); @@ -42,6 +43,7 @@ void initHardware() { breathFilter.setFilter(LOWPASS, FILTER_FREQ, 0.0); // create a one pole (RC) lowpass filter breathAltFilter.setFilter(LOWPASS, FILTER_FREQ, 0.0); // create a one pole (RC) lowpass filter + spikeFilter.setFilter(HIGHPASS, 200, 0.0); // create a one pole (RC) lowpass filter ledStrip.begin(); @@ -71,6 +73,12 @@ void initHardware() { } } +void updateFilters(preset_t &preset) { + breathFilter.setFrequency(preset.breathFilterFreq); + breathAltFilter.setFrequency(preset.breathFilterFreq); + spikeFilter.setFrequency(preset.spikeFilterFreq); +} + /* Return true if the given button bitmask is pressed */ @@ -107,16 +115,27 @@ 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) { + // Make it temporarily more sensitive when switching directions, the hardware loses a tick frequently + static int lastOut = 0; + static unsigned long lastOutTime = 0; int out = 0; int32_t val = knobs[n].read(); - if (val > 4) { + if (val > (lastOut < 0) ? 3 : 4) { out = val / 4; - } else if (val < -4) { + } else if (val < (lastOut > 0) ? -3 : -4) { out = val / 4; } if (out != 0) { - knobs[n].write(0); + unsigned long time = millis(); + + knobs[n].write(val - (out * 4)); + lastOut = out; + + if (time - lastOutTime < KNOB_CURVE_THRESHOLD) { + out *= KNOB_CURVE_MULTIPLIER; + } + lastOutTime = time; } return out; } @@ -136,8 +155,15 @@ uint16_t utilTouched() { return touchSensorKeys.touched(); } +int readSpikePressure() { + return spikeFilter.input(pressureSensorMain.readPressure()) * PRESSURE_SENS_MULTIPLIER; +} + int readPressure() { - return breathFilter.input(pressureSensorMain.readPressure()) * PRESSURE_SENS_MULTIPLIER; + float p = pressureSensorMain.readPressure(); + Serial.print(">raw:"); + Serial.println(p * PRESSURE_SENS_MULTIPLIER); + return breathFilter.input(p) * PRESSURE_SENS_MULTIPLIER; } int readAltPressure() { diff --git a/NuEVI/src/hardware.h b/NuEVI/src/hardware.h index 8d7df73..a168a36 100644 --- a/NuEVI/src/hardware.h +++ b/NuEVI/src/hardware.h @@ -7,6 +7,7 @@ #include #include #include +#include "settings.h" // Hardware sensor definitions // TODO: remove these @@ -16,6 +17,7 @@ extern Adafruit_Sensor *accelSensor; extern Adafruit_ICM20948 icmSensor; void initHardware(); +void updateFilters(preset_t &preset); bool checkButtonState(uint8_t mask); // return true if the given buttons are pressed uint8_t buttonState(); // return true if the given buttons are pressed int readKnob(uint8_t n); @@ -25,6 +27,7 @@ uint16_t keysTouched(); uint16_t utilTouched(); int readPressure(); int readAltPressure(); +int readSpikePressure(); // xEVI hardware setup @@ -92,6 +95,6 @@ int readAltPressure(); #define extraPin 2 #define pbUpPin 4 #define pbDnPin 5 -#define vibratoPin 3 +#define leverPin 3 #endif diff --git a/NuEVI/src/led.cpp b/NuEVI/src/led.cpp index 5b9f691..b07ab02 100644 --- a/NuEVI/src/led.cpp +++ b/NuEVI/src/led.cpp @@ -43,7 +43,7 @@ void statusLedBlink() { statusLedFlash(300); } -void updateSensorLEDs(instrument_state_t *state) { - 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/led.h b/NuEVI/src/led.h index 6e235b2..80d62a8 100644 --- a/NuEVI/src/led.h +++ b/NuEVI/src/led.h @@ -1,13 +1,15 @@ #ifndef __LED_H #define __LED_H +#include "globals.h" + void statusLedOn(); void statusLedOff(); void statusLedFlip(); void statusLed(bool state); void statusLedFlash(uint16_t delayTime); void statusLedBlink(); -void updateSensorLEDs(); +void updateSensorLEDs(instrument_state_t &state); void ledMeter(byte indicatedValue); #endif diff --git a/NuEVI/src/menu.cpp b/NuEVI/src/menu.cpp index 02c5843..96d3185 100644 --- a/NuEVI/src/menu.cpp +++ b/NuEVI/src/menu.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "menu.h" #include "hardware.h" @@ -247,28 +248,30 @@ struct AdjustValue { 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); + display.fillTriangle(x - 2, row + 9, x + 2, row + 9, 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; + int16_t thrV = state.calibration->*value.thrVal; + int16_t maxV = state.calibration->*value.maxVal; + if (value.zeroPoint) { + int16_t zero = state.instrument->*value.zeroPoint; + thrV += zero; + maxV += zero; } - if (drawing.thrX != thrX) { - drawIndicator(drawing.thrX, drawing.row, BLACK); - drawIndicator(thrX, drawing.row, WHITE); - drawing.thrX = thrX; - } + const int thrX = mapConstrain(thrV, value.limitLow, value.limitHigh, 1, 127); + const int maxX = mapConstrain(maxV, value.limitLow, value.limitHigh, 1, 127); + drawIndicator(drawing.thrX, drawing.row, BLACK); + drawIndicator(thrX, drawing.row, WHITE); + + drawIndicator(drawing.maxX, drawing.row, BLACK); + drawIndicator(maxX, drawing.row, WHITE); + + drawing.maxX = maxX; + drawing.thrX = thrX; } static void drawAdjustTitle(const AdjustValue& value, AdjustDrawing& drawing, bool highlight) { @@ -278,34 +281,38 @@ static void drawAdjustTitle(const AdjustValue& value, AdjustDrawing& drawing, bo } else { display.setTextColor(WHITE, BLACK); } - display.setCursor(0, drawing.row); + display.setCursor(0, drawing.row - 9); display.println(value.title); + display.setTextColor(WHITE, BLACK); } 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); + int16_t thrV = state.calibration->*value.thrVal; + int16_t maxV = state.calibration->*value.maxVal; + if (value.zeroPoint) { + int16_t zero = state.instrument->*value.zeroPoint; + thrV += zero; + maxV += zero; + } + + char buffer[16]; + snprintf(buffer, 16, "%d>%d<%d", thrV, state.instrument->*value.value, maxV); display.setTextSize(1); - display.setCursor(128 - 6 * strlen(buffer), drawing.row); + display.setCursor(128 - 6 * strlen(buffer), drawing.row - 9); 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; - } + display.drawFastVLine(drawing.valX, drawing.row + 4, 3, BLACK); + display.drawFastVLine(valX, drawing.row + 4, 3, 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(0, line, 127, line, WHITE); // Top line + display.drawLine(0, line + 10, 127, line + 10, 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); + display.drawLine(0, line + 1, 0, line + 10, WHITE); + display.drawLine(127, line + 1, 127, line + 10, WHITE); } void drawAdjustRow(state_t& state, const AdjustValue& value, AdjustDrawing& drawing, bool highlight) { @@ -318,28 +325,36 @@ void drawAdjustRow(state_t& state, const AdjustValue& value, AdjustDrawing& draw void plotSubOption(const char* label, const char* unit = nullptr) { int text_x, unit_x; - int label_pixel_width = strlen(label) * 12; + const size_t len = strlen(label); + bool bigText = len < 4 || (unit == nullptr && len < 5); + + int label_pixel_width = strlen(label) * (bigText ? 12 : 6); display.setTextColor(WHITE); 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); + if (bigText) { + 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); + } else { + text_x = 96 - (label_pixel_width / 2); + display.setCursor(96 - (unit_pixel_width / 2), 40); + } display.setTextSize(1); display.println(unit); } - display.setTextSize(2); + display.setTextSize(bigText ? 2 : 1); display.setCursor(text_x, 33); display.println(label); } template -static void plotMenuEntries(std::array entries, size_t cursorPos, int xOffset = 0) { +static void plotMenuEntries(const std::array entries, int cursorPos, int xOffset = 0) { display.fillRect(0 + xOffset, MENU_HEADER_OFFSET, 63 + xOffset, 64 - MENU_HEADER_OFFSET, BLACK); display.setTextSize(1); @@ -354,9 +369,9 @@ static void plotMenuEntries(std::array entries, size_t cur scrollPos = constrain(scrollPos, 0, entries.size() - MENU_NUM_ROWS); } - 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 row = 0; + int end = constrain(scrollPos + MENU_NUM_ROWS, 0, entries.size()); + for (int i = scrollPos; i < end; i++, row++) { int rowPixel = (row)*MENU_ROW_HEIGHT + MENU_HEADER_OFFSET; display.setCursor(xOffset, rowPixel); if (cursorPos == i) { @@ -369,55 +384,90 @@ static void plotMenuEntries(std::array entries, size_t cur } } +static void curveCustomDraw(size_t breathCurve) { + const char* curveMenuLabels[] = { "-4", "-3", "-2", "-1", "LIN", "+1", "+2", + "+3", "+4", "S1", "S2", "Z1", "Z2" }; + if (breathCurve >= curves.size()) { + return; + } + + 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[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.setTextSize(0); + display.print(curveMenuLabels[breathCurve]); +} + class AboutMenu : public MenuScreen { const char* title() { return "ABOUT"; } - 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; + void update(state_t& state, InputState& input, bool redraw) { + if (!redraw) { + return; } + + 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); } }; template class MainMenu : public MenuScreen { public: - MainMenu(std::array entries) : _entries(entries) {} - bool update(state_t& state, InputState& input, bool redraw) { - bool dirty = redraw; + MainMenu(const std::array entries) : _entries(entries) {} + void update(state_t& state, InputState& input, bool redraw) { + InputState nullinput; + if (input.changed) { + _saved = false; + } + + if (input.changed && input.knobPreset) { + state.currentPresetIdx = constrain((int)state.currentPresetIdx + input.knobPreset, 0, PRESET_COUNT - 1); + state.currentPreset = &presets[state.currentPresetIdx]; + redraw = true; + } + + if (input.changed && input.btnPreset) { + // Save current preset + writePreset(state.currentPresetIdx); + _saved = true; + redraw = true; + } + + if (redraw) { + drawHeader(state); + plotMenuEntries(_entries, _cursorPos); + _entries[_cursorPos]->update(state, nullinput, true); + } 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); + _entries[_cursorPos]->update(state, nullinput, true); + plotMenuEntries(_entries, _cursorPos); } else { - dirty |= _entries[_cursorPos]->update(state, input, redraw); - if (redraw) { - draw(redraw); - dirty = true; - } + _entries[_cursorPos]->update(state, input, redraw); } - - return dirty; }; const char* title() { @@ -425,32 +475,33 @@ public: } 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; + const std::array _entries; + bool _saved = false; + + void drawHeader(state_t &state) { + display.clearDisplay(); + display.setTextSize(1); + display.setCursor(0, 0); + display.drawLine(0, MENU_ROW_HEIGHT, 127, MENU_ROW_HEIGHT, WHITE); + display.println("MENU"); + display.setCursor(64, 0); + if (_saved) { + display.print(" SAVED "); + } else { + display.print("PRESET "); + } + display.print(state.currentPresetIdx + 1); + } }; 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) { + SubMenu(const char* title, const std::array entries) : _title(title), _entries(entries) {} + void update(state_t& state, InputState& input, bool redraw) { + InputState nullinput; bool redrawValue = false; - bool dirty = false; - return false; if (input.changed && input.btnMenu) { _focused = !_focused; if (_focused) { @@ -460,27 +511,24 @@ public: } else { currentMenu = lastMenu; redraw = true; + currentMenu->update(state, nullinput, 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; + redrawValue = true; } if (_focused) { - dirty |= _entries[_cursorPos]->update(state, input, redrawValue); + _entries[_cursorPos]->update(state, input, redrawValue); } - - return dirty; }; const char* title() { @@ -489,55 +537,48 @@ public: private: const char* _title; - size_t _cursorPos; - std::array _entries; + int _cursorPos; + const std::array _entries; bool _focused = false; MenuScreen* lastMenu; void draw(bool redraw) { - if (redraw) { - if (_focused) { - display.clearDisplay(); - } else { - display.fillRect(63, 11, 64, 52, BLACK); - } + if (redraw && _focused) { + display.clearDisplay(); 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); + plotMenuEntries(_entries, _focused ? _cursorPos : -1, _focused ? 0 : 63); } }; template< size_t L, - typename T, - typename U, - typename U_p + typename T > 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 = {} + const std::array labels = {}, + const char *unit = nullptr ) : _title(title), - _obj(obj), - _value(value), _min(min), _max(max), _wrap(wrap), - _labels(labels) { + _labels(labels), + _unit(unit) { } - bool update(state_t& state, InputState& input, bool redraw) { + void update(state_t& state, InputState& input, bool redraw) { if (input.knobVal1) { - int32_t newVal = (state.*_obj)->*_value + input.knobVal1; + T* val = value(state); + int32_t newVal = *val + input.knobVal1; if (newVal > _max) { if (_wrap) { newVal = _min; @@ -552,30 +593,167 @@ public: } } - (state.*_obj)->*_value = newVal; + *val = newVal; draw(state, redraw); - return true; } else if (redraw) { draw(state, redraw); - return true; + } + } + const char* title() { + return _title; + } +protected: + virtual T* value(state_t &state) = 0; + const char* _title; + const T _min; + const T _max; + const bool _wrap; + const std::array _labels; + const char* _unit; + + void drawFrame(bool redraw) { + if (!redraw) { + display.fillRect(64, 33, 62, 29, BLACK); + return; } - return false; + 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); + } + virtual void draw(state_t &state, bool redraw) { + display.setTextColor(WHITE, BLACK); + drawFrame(redraw); + + char buffer[12]; + snprintf(buffer, 12, "%+d", *value(state)); + size_t label_idx = *value(state) - _min; + if (_labels.size() > 0 && label_idx >= 0 && label_idx < _labels.size()) { + plotSubOption(_labels[label_idx], buffer); + } else { + plotSubOption(buffer, _unit); + } + } +}; + +template< + size_t L, + typename T +> +class PresetValueMenu : public ValueMenu { + public: + PresetValueMenu( + const char* title, + T preset_t::* const value, + const T min, const T max, const bool wrap = false, + const std::array labels = {}, + const char *unit = nullptr + ) : ValueMenu(title, min, max, wrap, labels, unit), _value(value) {} + protected: + T* value(state_t &state) { + return &(state.currentPreset->*_value); + } + private: + T preset_t::* const _value; +}; + +template< + size_t L, + typename T +> +class CurveValueMenu : public ValueMenu { + public: + CurveValueMenu( + const char* title, + T preset_t::* const value, + const T min, const T max, const bool wrap = false, + const std::array labels = {} + ) : ValueMenu(title, min, max, wrap, labels), _value(value) {} + protected: + T* value(state_t &state) { + return &(state.currentPreset->*_value); + } + void draw(state_t &state, bool redraw) { + display.setTextColor(WHITE, BLACK); + this->drawFrame(true); + + curveCustomDraw(*value(state)); + } + private: + T preset_t::* const _value; +}; + + +template< + size_t L, + typename T +> +class InstrumentValueMenu : public ValueMenu { + public: + InstrumentValueMenu( + const char* title, + T instrument_state_t::* const value, + const T min, const T max, const bool wrap = false, + const std::array labels = {} + ) : ValueMenu(title, min, max, wrap, labels), _value(value) {} + protected: + T* value(state_t &state) { + return &(state.instrument->*_value); + } + private: + T instrument_state_t::* const _value; +}; + +template< + size_t N, + typename T +> +class ChoiceMenu : public MenuScreen { +public: + ChoiceMenu( + const char* title, + T preset_t::* const value, + const std::array choices, + const std::array labels = {} + ) : _title(title), _value(value), _choices(choices), _labels(labels) { + } + void update(state_t& state, InputState& input, bool redraw) { + auto it = std::find(_choices.begin(), _choices.end(), (state.currentPreset)->*_value); + if (it == _choices.end()) { + it = _choices.begin(); + } + 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() - 1; + } else { + (state.currentPreset)->*_value = *it; + } + + draw(state, redraw, it - _choices.begin()); + } else if (redraw) { + draw(state, redraw, it - _choices.begin()); + } } 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; + T preset_t::*const _value; + const std::array _choices; + const std::array _labels; - void draw(state_t &state, bool redraw) { + void draw(state_t& state, bool redraw, size_t label_idx) { if (redraw) { display.fillRect(63, 11, 64, 52, BLACK); display.drawRect(63, 11, 64, 52, WHITE); @@ -589,103 +767,9 @@ private: } 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 PresetValueMenu : public ValueMenu { - public: - 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) {} -}; - -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]; - } - - draw(redraw, it - _choices.begin()); - return true; - } else if (redraw) { - draw(redraw, it - _choices.begin()); - return true; - } - - return false; - } - const char* title() { - return _title; - } -private: - const char* _title; - T preset_t::*_value; - const std::array _choices; - const std::array _labels; - - 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.setCursor(95 - len * 3, 15); - display.println(this->_title); - } - - char buffer[12]; - snprintf(buffer, 12, "%+d", _value); + snprintf(buffer, 12, "%+d", state.currentPreset->*_value); if (_labels.size() > 0 && label_idx >= 0 && label_idx <= _labels.size()) { - plotSubOption(_labels[label_idx], buffer); + plotSubOption(_labels[label_idx]); } else { plotSubOption(buffer); } @@ -697,17 +781,12 @@ 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"); + void update(state_t& state, InputState& input, bool redraw) { + InputState nullinput; if (input.changed && input.btnMenu) { - Serial.println("menu"); currentMenu = &_mainMenu; - input.changed = false; - return currentMenu->update(state, input, true); + currentMenu->update(state, nullinput, true); } - - return false; }; private: MenuScreen& _mainMenu; @@ -716,16 +795,14 @@ private: class ExitMenu : public MenuScreen { public: ExitMenu() {} - bool update(state_t& state, InputState& input, bool redraw) { + void update(state_t& state, InputState& input, bool redraw) { if (redraw) { display.fillRect(63, 11, 64, 52, BLACK); } if (input.changed && input.btnMenu) { - displayOff(); + displayOff(state); } - - return false; } const char* title() { return "EXIT"; @@ -735,57 +812,89 @@ public: 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) { + AdjustMenuScreen(const char* title, std::array entries) : _title(title), _entries(entries) { + for (int i = 0; i < ADJUST_NUM_ROWS; i++) { + _rowDrawings[i].row = i * ADJUST_ROW_HEIGHT + 9; + } + } + + void update(state_t& state, InputState& input, bool redraw) { if (input.changed && input.btnMenu) { _focused = !_focused; + if (_focused) { + lastMenu = currentMenu; + currentMenu = this; + redraw = true; + } else { + saveCalibration(state); + currentMenu = lastMenu; + redraw = true; + InputState nullinput; + currentMenu->update(state, nullinput, true); + } } if (!_focused) { display.fillRect(63, 11, 64, 52, BLACK); - return true; + return; } bool redrawIndicators = false; if (input.changed) { if (input.knobMenu) { - _selectedEntry = _selectedEntry + input.knobMenu; + _selectedEntry = constrain(_selectedEntry + input.knobMenu, 0, N - 1); 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); + if (!value.zeroPoint) { + state.calibration->*value.thrVal = constrain(state.calibration->*value.thrVal, value.limitLow, value.limitHigh); + } redrawIndicators = true; + _dirty = true; } if (input.knobVal2) { state.calibration->*value.maxVal += input.knobVal2; - state.calibration->*value.maxVal = constrain(state.calibration->*value.maxVal, value.limitLow, value.limitHigh); + if (!value.zeroPoint) { + state.calibration->*value.maxVal = constrain(state.calibration->*value.maxVal, value.limitLow, value.limitHigh); + } redrawIndicators = true; + _dirty = true; } - } else { - draw(state, redrawIndicators, redraw); } - return true; + draw(state, redrawIndicators, redraw); } const char* title() { return _title; } + void saveCalibration(state_t &state) { + if (_dirty) { + writeCalibration(*state.calibration); + } + } private: const char* _title; - size_t _selectedEntry = 0; + int _selectedEntry = 0; std::array _entries; - std::array _rowDrawings; + std::array _rowDrawings; + bool _dirty = false;; bool _focused = false; + MenuScreen* lastMenu; void draw(state_t& state, bool redrawIndicators, bool redraw) { - size_t scrollPos = 0; + int scrollPos = 0; + + if (redraw) { + display.clearDisplay(); + } + if (_entries.size() >= ADJUST_NUM_ROWS) { - if ((_selectedEntry - scrollPos) > (ADJUST_NUM_ROWS - 2)) { - scrollPos = _selectedEntry - (ADJUST_NUM_ROWS - 2); + if ((_selectedEntry - scrollPos) > (ADJUST_NUM_ROWS - 1)) { + scrollPos = _selectedEntry - (ADJUST_NUM_ROWS - 1); } else if ((_selectedEntry - scrollPos) < 1) { scrollPos = _selectedEntry - 1; } @@ -794,21 +903,45 @@ private: } int end = constrain(scrollPos + ADJUST_NUM_ROWS, 0, N); - for (size_t i = scrollPos; i < end; i++) { + for (int i = scrollPos, j = 0; i < end; i++, j++) { if (redraw) { - drawAdjustRow(state, _entries[i], _rowDrawings[i], i == _selectedEntry); + drawAdjustRow(state, _entries[i], _rowDrawings[j], i == _selectedEntry); } else if (redrawIndicators && i == _selectedEntry) { - drawAdjustIndicators(state, _entries[i], _rowDrawings[i]); + drawAdjustIndicators(state, _entries[i], _rowDrawings[j]); } else { - drawAdjustValues(state, _entries[i], _rowDrawings[i]); + drawAdjustValues(state, _entries[i], _rowDrawings[j]); } } } }; -std::array adjustValues = { { +class FuncMenu : public MenuScreen { +public: + FuncMenu(const char *title, std::function&& func) + : _title(title), _func(func) {} + void update(state_t& state, InputState& input, bool redraw) { + if (redraw) { + display.fillRect(63, 11, 64, 52, BLACK); + } + + if (input.changed && input.btnVal1) { + _func(state, input); + } + } + const char* title() { + return _title; + } +private: + const char *_title; + const std::function _func; +}; + + + +std::array adjustValues = { { {"BREATH", &instrument_state_t::breathSignal, &calibration_t::breathThrValOffset, &calibration_t::breathMaxValOffset, BREATH_LO_LIMIT, BREATH_HI_LIMIT, &instrument_state_t::breathZero}, + {"TRIGGER", &instrument_state_t::spikeSignal, &calibration_t::spikeThrVal, &calibration_t::spikeThrVal, SPIKE_LO_LIMIT, SPIKE_HI_LIMIT, NULL}, {"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}, @@ -819,77 +952,79 @@ std::array adjustValues = { { {"TOUCH", &instrument_state_t::avgCTouchSignal, &calibration_t::ctouchThrVal, &calibration_t::ctouchThrVal, CTOUCH_LO_LIMIT, CTOUCH_HI_LIMIT, NULL}, }}; -const AdjustMenuScreen<8> adjustMenu("ADJUST", adjustValues); +AdjustMenuScreen<9> adjustMenu("ADJUST", adjustValues); //*********************************************************** -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, {}); +ChoiceMenu<4, BreathMode> breathModeMenu("BR MODE", &preset_t::breathMode, { { BREATH_STD, BREATH_LSB, BREATH_AT, BREATH_LSB_AT} }, { { "STD", "LSB", "AT", "AT+" } }); +PresetValueMenu<128, uint8_t> breathCCMenu("BREATH CC", &preset_t::breathCC, 0, 127, true, CC_NAMES); +PresetValueMenu<1, uint8_t> velocityMenu("VELOCITY", &preset_t::fixedVelocity, 0, 127, true, { { "DYN" } }); +CurveValueMenu<0, uint8_t> curveMenu("CURVE", &preset_t::breathCurve, 0, 12); +PresetValueMenu<1, uint8_t> velSmpDlMenu("VEL DELAY", &preset_t::velSmpDl, 0, 30, true, { "OFF" }, "ms"); +PresetValueMenu<1, uint8_t> velBiasMenu("VEL BOOST", &preset_t::velBias, 0, 30, true, { "OFF" }); +PresetValueMenu<0, uint8_t> breathIntervalMenu("BR INTV", &preset_t::breathInterval, 0, 30, true, {}, "ms"); +PresetValueMenu<0, uint8_t> filterMenu("FILTER CT", &preset_t::breathFilterFreq, 1, 100, false, {}, "hz"); +PresetValueMenu<1, uint8_t> spikeFilterMenu("FILTER TRG", &preset_t::spikeFilterFreq, 0, 100, false, {"OFF"}, "hz"); +PresetValueMenu<0, int8_t> trill3Menu("TRILL3", &preset_t::trill3_interval, 3, 4, true, {}); +PresetValueMenu<0, int8_t> cvTuneMenu("CV Tune", &preset_t::cvTune, -100, 100, false, {}); +PresetValueMenu<0, uint8_t> cvVibMenu("CV LFO", &preset_t::cvVibRate, 0, 8, false, {}); +PresetValueMenu<0, int8_t> cvScaleMenu("CV SCL", &preset_t::cvScale, -100, 100, false, {}); const std::array transposeLabels = { "C>", "C#>", "D>", "D#>", "E>", "F>", "F#>", "G>", "G#>", "A>", "Bb>", "B>", ">C<", " 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} }, { { +InstrumentValueMenu<25, int8_t> transposeMenu("TRANSPOSE", &instrument_state_t::transpose, -12, 12, true, transposeLabels); +InstrumentValueMenu<0, int8_t> octaveMenu("OCTAVE", &instrument_state_t::octave, -3, 3, true, {}); +PresetValueMenu<0, byte> midiMenu("MIDI CH", &preset_t::MIDIchannel, 1, 16, false, {}); +PresetValueMenu<1, uint8_t> vibDepthMenu("DEPTH", &preset_t::vibratoDepth, 0, 9, true, { "OFF" }); +PresetValueMenu<1, uint8_t> vibRetnMenu("RETURN", &preset_t::vibRetn, 0, 4, true, { "OFF" }); +PresetValueMenu<0, uint8_t> vibSenseMenu("SENSE LVR", &preset_t::vibSens, 0, 12); +PresetValueMenu<0, uint8_t> vibSquelchMenu("SQUELCH LVR", &preset_t::vibSquelch, 0, 12); +ChoiceMenu<2, VibratoMode> vibDirMenu("DIRECTION", &preset_t::vibratoMode, { {VSTART_DOWN, VSTART_UP} }, { "DWN", "UP" }); +ChoiceMenu<4, ExtraControl> biteCtlMenu("BITE CTL", &preset_t::biteControl, { {OFF, VIBRATO, GLIDE, CC} }, { { "OFF", "VIBRATO", "GLIDE", "CC" } }); -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} }, { +PresetValueMenu<128, uint8_t> biteCCMenu("BITE CC", &preset_t::biteCC, 0, 127, true, CC_NAMES); +ChoiceMenu<4, ExtraControl> leverCtlMenu("LEVER CTL", &preset_t::leverControl, { {OFF, VIBRATO, GLIDE, CC} }, { + "OFF", + "VIB", + "GLD", + "CC" + }); +PresetValueMenu<128, uint8_t> leverCCMenu("LEVER CC", &preset_t::leverCC, 0, 127, true, CC_NAMES); +ChoiceMenu<4, PortamentoMode> portMenu("GLIDE MOD", &preset_t::portamentoMode, { {POFF, PON, PSWITCH_ONLY, PGLIDE_ONLY} }, { + "OFF", + "ON", + "SWC", + "GLD", + }); +PresetValueMenu<0, uint8_t> portLimitMenu("GLIDE LMT", &preset_t::portamentoLimit, 1, 127, true); +PresetValueMenu<0, uint8_t> pitchBendMenu("PITCHBEND", &preset_t::PBdepth, 0, 12, true); +ChoiceMenu<4, ExtraControl> extraCtlMenu("EXT CTL", &preset_t::extraControl, { {OFF, VIBRATO, GLIDE, CC} }, { "OFF", "VIBRATO", "GLIDE", "CC" }); -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 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 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, { +PresetValueMenu<128, uint8_t> extraCCMenu("EXT CC", &preset_t::extraCC, 0, 127, true, CC_NAMES); +PresetValueMenu<1, uint8_t> deglitchMenu("DEGLITCH", &preset_t::deglitch, 0, 70, true, { "OFF" }, "ms"); +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", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "PBD", "GLD", "MOD", "QTN" }); -const ChoiceMenu<4, FingeringMode> fingeringMenu("FINGERING", &preset_t::fingering, { EVI, EVR, TPT, HRN }, { +ChoiceMenu<4, FingeringMode> fingeringMenu("FINGERING", &preset_t::fingering, { EVI, EVR, TPT, HRN }, { "EVI", "EVR", "TPT", "HRN", }); -const ChoiceMenu<6, RollerMode> rollerMenu("ROLLRMODE", &preset_t::rollerMode, { +ChoiceMenu<6, RollerMode> rollerMenu("ROLLRMODE", &preset_t::rollerMode, { HIGHEST, HIGHEST_EXTEND, HIGHEST_PAIR, @@ -897,15 +1032,24 @@ const ChoiceMenu<6, RollerMode> rollerMenu("ROLLRMODE", &preset_t::rollerMode, { PARTIAL, PARTIAL_EXTEND }, { - "HIGHEST", - "HIGHEST_EXTEND", - "HIGHEST_PAIR", - "HIGHEST_PAIR_EXTEND", - "PARTIAL", - "PARTIAL_EXTEND", + "HI", + "HI+", + "PAIR", + "PAIR+", + "PART", + "PART+", }); +FuncMenu recalibrateMenu("RE-CAL", [](state_t &state, InputState& input) { + fullAutoCal(); +}); +FuncMenu factoryResetMenu("FACT. RESET", [](state_t &state, InputState& input) { + readEEPROM(true, *state.calibration); +}); +FuncMenu saveAllPresetsMenu("SAVE PRESETS", [](state_t &state, InputState& input) { + writePresets(); +}); -std::array breathMenuEntries = { +std::array breathMenuEntries = { &breathModeMenu, &breathCCMenu, &velocityMenu, @@ -913,10 +1057,12 @@ std::array breathMenuEntries = { &velSmpDlMenu, &velBiasMenu, &breathIntervalMenu, + &filterMenu, + &spikeFilterMenu, }; -const SubMenu<7> breathMenu("BR SETUP", breathMenuEntries); +SubMenu<9> breathMenu("BR SETUP", breathMenuEntries); -const std::array controlMenuEntries = { +const std::array controlMenuEntries = { &fingeringMenu, &rollerMenu, &biteCtlMenu, @@ -931,32 +1077,35 @@ const std::array controlMenuEntries = { &pinkyMenu, &pitchBendMenu }; -const SubMenu<13> controlMenu("CTL SETUP", controlMenuEntries); +SubMenu<13> controlMenu("CTL SETUP", controlMenuEntries); -const std::array vibratoMenuEntries = { +const std::array vibratoMenuEntries = { &vibDepthMenu, &vibRetnMenu, &vibDirMenu, &vibSenseMenu, &vibSquelchMenu, }; -const SubMenu<5> vibratoMenu("VIBRATO", vibratoMenuEntries); +SubMenu<5> vibratoMenu("VIBRATO", vibratoMenuEntries); -const AboutMenu aboutMenu = AboutMenu(); +AboutMenu aboutMenu = AboutMenu(); -std::array extrasMenuEntries = { +const std::array extrasMenuEntries = { &trill3Menu, &cvTuneMenu, &cvScaleMenu, &cvVibMenu, + &recalibrateMenu, + &factoryResetMenu, + &saveAllPresetsMenu, }; -const SubMenu<4> extrasMenu("EXTRAS", extrasMenuEntries); +SubMenu<7> extrasMenu("EXTRAS", extrasMenuEntries); -const ExitMenu exitMenu = ExitMenu(); +ExitMenu exitMenu = ExitMenu(); // Top-level screens -const std::array mainMenuEntries = { +const std::array mainMenuEntries = { &transposeMenu, &octaveMenu, &midiMenu, @@ -971,30 +1120,6 @@ const std::array mainMenuEntries = { MainMenu<10> mainMenu(mainMenuEntries); StatusMenu statusMenu = StatusMenu(mainMenu); -/* -static void curveCustomDraw() { - 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); - display.drawLine(x0 + 65, 60 - y0, x + 65, 60 - y, WHITE); - x0 = x; y0 = y; - } - display.setCursor(125 - 3 * 6, 60 - 8); - display.setTextSize(0); - display.print(curveMenuLabels[currentPreset->breathCurve]); -} -*/ - - -static bool updateSensorPixelsFlag = false; -void drawSensorPixels() { - updateSensorPixelsFlag = true; -} - //*********************************************************** static InputState readInput(uint32_t timeNow) { @@ -1054,7 +1179,8 @@ static InputState readInput(uint32_t timeNow) { return input; } -void displayOff() { +void displayOff(state_t &state) { + adjustMenu.saveCalibration(state); display.clearDisplay(); display.display(); display.ssd1306_command(SSD1306_DISPLAYOFF); @@ -1098,22 +1224,30 @@ void initDisplay() { void handleMenu(state_t& state, bool draw) { unsigned long timeNow = millis(); + bool wakeUp = false; InputState input = readInput(timeNow); // shut off menu system if not used for a while if (displayOn && ((timeNow - menuTime) > MENU_AUTO_OFF_TIME)) { - displayOff(); + displayOff(state); } - if (input.changed) { + if (!displayOn && input.changed) { display.ssd1306_command(SSD1306_DISPLAYON); displayOn = true; + wakeUp = true; } if (currentMenu && (draw || input.changed)) { - if (currentMenu->update(state, input, false)) { + currentMenu->update(state, input, wakeUp); + if (displayOn) { display.display(); } + + if (input.changed) { + rezero(); // Do this in case anything has changed + updateFilters(*state.currentPreset); + } } } \ No newline at end of file diff --git a/NuEVI/src/menu.h b/NuEVI/src/menu.h index 80f3bc6..c1e146e 100644 --- a/NuEVI/src/menu.h +++ b/NuEVI/src/menu.h @@ -8,7 +8,7 @@ #define MENU_HEADER_OFFSET 12 #define MENU_NUM_ROWS 6 #define ADJUST_NUM_ROWS 3 -#define ADJUST_ROW_HEIGHT 21 +#define ADJUST_ROW_HEIGHT 22 extern const unsigned long debounceDelay; // the debounce time; increase if the output flickers extern const unsigned long buttonRepeatInterval; @@ -33,12 +33,12 @@ class MenuScreen { public: MenuScreen() {}; virtual const char *title() { return ""; }; - virtual bool update(state_t &state, InputState &input, bool redraw) = 0; + virtual void update(state_t &state, InputState &input, bool redraw) = 0; virtual ~MenuScreen() {}; }; void initDisplay(); -void displayOff(); +void displayOff(state_t &state); void showVersion(); void displayError(const char *error); void handleMenu(state_t &state, bool draw); diff --git a/NuEVI/src/settings.cpp b/NuEVI/src/settings.cpp index d4ef625..76be4aa 100644 --- a/NuEVI/src/settings.cpp +++ b/NuEVI/src/settings.cpp @@ -41,7 +41,7 @@ void readCalibration(calibration_t &calibration) { } void writePreset(uint8_t preset) { - EEPROM.put(SETTINGS_OFFSET + preset * sizeof(preset_t), presets[preset]); + EEPROM.put(SETTINGS_OFFSET + CALIBRATION_MAX_SIZE + preset * sizeof(preset_t), presets[preset]); } void writePresets() { @@ -326,16 +326,27 @@ void readEEPROM(const bool factoryReset, calibration_t &calibration) { // blank eeprom will be 0xFFFF. For a full reset, call it "version 0" so everything gets overwritten. if (factoryReset || settings_version == 0xffffu) { settings_version = 0; + preset_t defaultPreset; + calibration_t defaultCalibration; + for (int i = 0; i < PRESET_COUNT; i++) { + presets[i] = defaultPreset; + } + calibration = defaultCalibration; } else { readPresets(); readCalibration(calibration); } if(settings_version != EEPROM_VERSION) { - // Add default settings here + for (int i = 0; i < PRESET_COUNT; i++) { + if (settings_version < 3) { + presets[i].breathFilterFreq = 20; + presets[i].spikeFilterFreq = 20; + } + } writePresets(); - writeCalibration(); + writeCalibration(calibration); writeInt(EEPROM_VERSION_ADDR, EEPROM_VERSION); } } diff --git a/NuEVI/src/settings.h b/NuEVI/src/settings.h index 64fa0ac..6a6325a 100644 --- a/NuEVI/src/settings.h +++ b/NuEVI/src/settings.h @@ -5,7 +5,7 @@ #include "globals.h" -#define EEPROM_VERSION 1 +#define EEPROM_VERSION 3 #define EEPROM_VERSION_ADDR 0 #define SETTINGS_OFFSET 2 #define PRESET_MAX_SIZE 128 // Leave extra space for future settings @@ -24,18 +24,19 @@ struct calibration_t { int16_t breathMaxValOffset = 1500; int16_t breathAltThrValOffset = 5; int16_t breathAltMaxValOffset = 1500; - int16_t biteThrVal = 50; - int16_t biteMaxVal = 150; - int16_t pbDnThrVal = 50; - int16_t pbDnMaxVal = 150; - int16_t pbUpThrVal = 50; - int16_t pbUpMaxVal = 150; - int16_t leverThrVal = 50; - int16_t leverMaxVal = 150; - int16_t extraThrVal = 50; - int16_t extraMaxVal = 150; - int16_t ctouchThrVal = 80; - uint8_t _reserved[24]; + int16_t biteThrVal = 850; + int16_t biteMaxVal = 1000; + int16_t pbDnThrVal = 850; + int16_t pbDnMaxVal = 1000; + int16_t pbUpThrVal = 850; + int16_t pbUpMaxVal = 1000; + int16_t leverThrVal = 850; + int16_t leverMaxVal = 1000; + int16_t extraThrVal = 850; + int16_t extraMaxVal = 1000; + int16_t ctouchThrVal = 900; + int16_t spikeThrVal= 25; + uint8_t _reserved[22]; }; static_assert(sizeof(calibration_t) == CALIBRATION_MAX_SIZE, "calibration data wrong size"); @@ -58,7 +59,7 @@ struct preset_t { uint8_t velSmpDl = 20; // velocity sample delay uint8_t velBias = 0; // velocity bias uint8_t pinkySetting = 12; // 0 - 11 (QuickTranspose -12 to -1), 12 (pb/2), 13 - 24 (QuickTranspose +1 to +12), 25 (EC2), 26 (ECSW), 27 (LVL), 28 (LVLP) - uint8_t breathInterval; // 3-15 + uint8_t breathInterval = 10; // 3-15 int8_t trill3_interval = 4; uint8_t vibSquelch = 12; // vibrato signal squelch uint8_t cvVibRate = 0; // OFF, 1 - 8 CV extra controller LFO vibrato rate 4.5Hz to 8Hz @@ -90,7 +91,10 @@ struct preset_t { uint8_t icmRotationMode; uint8_t icmRotationCC; - uint8_t _reserved[87]; + uint8_t breathFilterFreq = 20; + uint8_t spikeFilterFreq = 20; + + uint8_t _reserved[85]; }; static_assert(sizeof(preset_t) == PRESET_MAX_SIZE, "preset_t must be 128 bytes"); @@ -103,13 +107,15 @@ struct state_t { instrument_state_t *instrument; preset_t *currentPreset; calibration_t *calibration; + size_t currentPresetIdx; }; #define NO_CHECKSUM 0x7F007F00 -void readEEPROM(const bool factoryReset); +void readEEPROM(const bool factoryReset, calibration_t &calibration); void writePreset(uint8_t preset); -void writeCalibration(); +void writePresets(); +void writeCalibration(calibration_t &calibration); //Functions for config management mode void sendSysexSettings(); diff --git a/NuEVI/src/xEVI.cpp b/NuEVI/src/xEVI.cpp index 3b8afc3..c618534 100644 --- a/NuEVI/src/xEVI.cpp +++ b/NuEVI/src/xEVI.cpp @@ -12,6 +12,7 @@ #include "settings.h" #include "led.h" #include "test.h" +#include "FilterOnePole.h" /* NAME: xEVI @@ -30,13 +31,13 @@ FUNCTION: EVI Wind Controller using MPRLS pressure sensors and capac preset_t presets[PRESET_COUNT]; instrument_state_t instrument; -preset_t *currentPreset = &presets[0]; calibration_t calibration; state_t state = { NOTE_OFF, &instrument, - currentPreset, + &presets[0], &calibration, + 0, }; static const int pbDepthList[13] = { 8192, 8192, 4096, 2731, 2048, 1638, 1365, 1170, 1024, 910, 819, 744, 683 }; @@ -90,6 +91,8 @@ const int trumpetHarmonic[2][7] = { {0, 7, 12, 16, 19, 26, 31}, //! K4: hrm 8-> bool configManagementMode = false; bool testMode = false; +FilterOnePole breathCCFilter; + //_______________________________________________________________________________________________ SETUP // MIDI note value check with out of range octave repeat @@ -109,7 +112,7 @@ void port(int portCC) { return; } - if (currentPreset->portamentoMode == PortamentoMode::PON || currentPreset->portamentoMode == PortamentoMode::PGLIDE_ONLY) { + if (state.currentPreset->portamentoMode == PortamentoMode::PON || state.currentPreset->portamentoMode == PortamentoMode::PGLIDE_ONLY) { if (instrument.portamentoVal > 0 && portCC == 0) { midiSendControlChange(CCN_PortOnOff, 0); } else if (instrument.portamentoVal == 0 && portCC > 0) { @@ -158,12 +161,12 @@ void cvUpdate() { instrument.cvPitch = instrument.targetPitch; } - if (currentPreset->cvVibRate) { - int timeDivider = timeDividerList[currentPreset->cvVibRate]; + if (state.currentPreset->cvVibRate) { + int timeDivider = timeDividerList[state.currentPreset->cvVibRate]; int cvVib = map(((waveformsTable[map(currentTime % timeDivider, 0, timeDivider, 0, maxSamplesNum - 1)] - 2047)), -259968, 259969, -11, 11); instrument.cvPitch += cvVib; } - int cvPitchTuned = 2 * (currentPreset->cvTune) + map(instrument.cvPitch, 0, 4032, 0, 4032 + 2 * (currentPreset->cvScale)); + int cvPitchTuned = 2 * (state.currentPreset->cvTune) + map(instrument.cvPitch, 0, 4032, 0, 4032 + 2 * (state.currentPreset->cvScale)); analogWrite(cvPitchPin, constrain(cvPitchTuned, 0, 4095)); } @@ -197,9 +200,12 @@ unsigned int multiMap(unsigned short val, const unsigned short* _in, const unsig // map breath values to selected curve unsigned int breathCurve(unsigned int inputVal) { - if (currentPreset->breathCurve >= curves.size()) + + if (state.currentPreset->breathCurve >= curves.size()) { return inputVal; - return multiMap(inputVal, curveIn, curves[currentPreset->breathCurve], 17); + } + + return multiMap(inputVal, curveIn, curves[state.currentPreset->breathCurve], 17); } //************************************************************** @@ -217,14 +223,15 @@ int breath() { int breathCCval, breathCCvalFine; unsigned int breathCCvalHires; breathCCvalHires = breathCurve(mapConstrain(instrument.breathSignal, instrument.breathThrVal, instrument.breathMaxVal, 0, 16383)); + breathCCvalHires = breathCCFilter.input(breathCCvalHires); breathCCval = (breathCCvalHires >> 7) & 0x007F; breathCCvalFine = breathCCvalHires & 0x007F; if (breathCCval != oldbreath) { // only send midi data if breath has changed from previous value - if (currentPreset->breathCC) { + if (state.currentPreset->breathCC) { // send midi cc - midiSendControlChange(currentPreset->breathCC, breathCCval); + midiSendControlChange(state.currentPreset->breathCC, breathCCval); } - if (currentPreset->breathMode == BreathMode::BREATH_AT || currentPreset->breathMode == BreathMode::BREATH_LSB_AT) { + if (state.currentPreset->breathMode == BreathMode::BREATH_AT || state.currentPreset->breathMode == BreathMode::BREATH_LSB_AT) { // send aftertouch midiSendAfterTouch(breathCCval); } @@ -232,8 +239,8 @@ int breath() { } if (breathCCvalHires != oldbreathhires - && (currentPreset->breathMode == BreathMode::BREATH_LSB || currentPreset->breathMode == BreathMode::BREATH_LSB_AT)) { - midiSendControlChange(currentPreset->breathCC + 32, breathCCvalFine); + && (state.currentPreset->breathMode == BreathMode::BREATH_LSB || state.currentPreset->breathMode == BreathMode::BREATH_LSB_AT)) { + midiSendControlChange(state.currentPreset->breathCC + 32, breathCCvalFine); } oldbreathhires = breathCCvalHires; @@ -253,33 +260,31 @@ void pitch_bend() { byte pbTouched = 0; int vibRead = 0; int vibReadBite = 0; - 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) + bool halfPitchBendKey = (state.currentPreset->pinkySetting == PBD) && instrument.pinkyKey; // hold pinky key for 1/2 pitchbend value + instrument.quarterToneTrigger = (state.currentPreset->pinkySetting == QTN) && instrument.pinkyKey; // pinky key for a quarter tone down using pitch bend (assuming PB range on synth is set to 2 semitones) - calculatedPBdepth = pbDepthList[currentPreset->PBdepth]; + calculatedPBdepth = pbDepthList[state.currentPreset->PBdepth]; if (halfPitchBendKey) calculatedPBdepth = calculatedPBdepth * 0.5; - vibMax = vibMaxList[currentPreset->vibSens - 1]; + vibMax = vibMaxList[state.currentPreset->vibSens - 1]; float calculatedDepth = 0; - if (currentPreset->vibratoMode == VibratoMode::VSTART_DOWN) { - calculatedDepth = calculatedPBdepth * vibDepth[currentPreset->vibratoDepth]; + if (state.currentPreset->vibratoMode == VibratoMode::VSTART_DOWN) { + calculatedDepth = calculatedPBdepth * vibDepth[state.currentPreset->vibratoDepth]; } else { - calculatedDepth = (0 - calculatedPBdepth * vibDepth[currentPreset->vibratoDepth]); + calculatedDepth = (0 - calculatedPBdepth * vibDepth[state.currentPreset->vibratoDepth]); } - if (ExtraControl::VIBRATO == currentPreset->biteControl) { // bite vibrato - 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 (ExtraControl::VIBRATO == state.currentPreset->biteControl) { // bite vibrato + vibMaxBite = vibMaxBiteList[state.currentPreset->vibSens - 1]; + vibReadBite = instrument.biteSignal; - if (vibReadBite < instrument.vibThrBite) { + if (vibReadBite > instrument.vibThrBite) { instrument.vibSignal = (instrument.vibSignal + mapConstrain( vibReadBite, (instrument.vibZeroBite - vibMaxBite), instrument.vibThrBite, calculatedDepth, 0) ) / 2; - } else if (vibReadBite > instrument.vibThrBiteLo) { + } else if (vibReadBite < instrument.vibThrBiteLo) { instrument.vibSignal = (instrument.vibSignal + mapConstrain( vibReadBite, (instrument.vibZeroBite + vibMaxBite), instrument.vibThrBite, calculatedDepth, 0) ) / 2; @@ -288,13 +293,13 @@ void pitch_bend() { } } - if (ExtraControl::VIBRATO == currentPreset->leverControl) { // lever vibrato - vibRead = readTouchUtil(vibratoPin); - if (vibRead < instrument.vibThr) { + if (ExtraControl::VIBRATO == state.currentPreset->leverControl) { // lever vibrato + vibRead = instrument.leverSignal; + if (vibRead > instrument.vibThr) { instrument.vibSignal = (instrument.vibSignal + mapConstrain(vibRead, (instrument.vibZero - vibMax), instrument.vibThr, calculatedDepth, 0) ) / 2; - } else if (vibRead > instrument.vibThrLo) { + } else if (vibRead < instrument.vibThrLo) { instrument.vibSignal = (instrument.vibSignal + mapConstrain(vibRead, (instrument.vibZero + vibMax), instrument.vibThr, calculatedDepth, 0) ) / 2; @@ -303,7 +308,7 @@ void pitch_bend() { } } - switch (currentPreset->vibRetn) { // moving baseline + switch (state.currentPreset->vibRetn) { // moving baseline case 0: // keep vibZero value break; @@ -323,16 +328,18 @@ void pitch_bend() { instrument.vibZero = instrument.vibZero * 0.6 + vibRead * 0.4; instrument.vibZeroBite = instrument.vibZeroBite * 0.6 + vibReadBite * 0.4; } - 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); + instrument.vibThr = instrument.vibZero + state.currentPreset->vibSquelch; + instrument.vibThrLo = instrument.vibZero - state.currentPreset->vibSquelch; + instrument.vibThrBite = instrument.vibZeroBite + state.currentPreset->vibSquelch; + instrument.vibThrBiteLo = instrument.vibZeroBite - state.currentPreset->vibSquelch; + + // PB calculation + int pbPos = mapConstrain(instrument.pbUpSignal, calibration.pbUpThrVal, calibration.pbUpMaxVal, calculatedPBdepth, 0); + int pbNeg = mapConstrain(instrument.pbDnSignal, calibration.pbDnThrVal, calibration.pbDnMaxVal, calculatedPBdepth, 0); int pbSum = 8193 + pbPos - pbNeg; int pbDif = abs(pbPos - pbNeg); - if ((instrument.pbUpSignal < calibration.pbUpThrVal || instrument.pbDnSignal < calibration.pbDnThrVal) && currentPreset->PBdepth) { + if ((instrument.pbUpSignal > calibration.pbUpThrVal || instrument.pbDnSignal > calibration.pbDnThrVal) && state.currentPreset->PBdepth) { if (pbDif < 10) { instrument.pitchBend = 8192; } else { @@ -361,93 +368,127 @@ void pitch_bend() { //*********************************************************** void portamento_() { - if (currentPreset->portamentoMode == PortamentoMode::POFF) { + if (state.currentPreset->portamentoMode == PortamentoMode::POFF) { port(0); // ensure it's off return; } int portSumCC = 0; - if (currentPreset->pinkySetting == GLD) { + if (state.currentPreset->pinkySetting == GLD) { if (instrument.pinkyKey) { - portSumCC += currentPreset->portamentoLimit; + portSumCC += state.currentPreset->portamentoLimit; } } - if (ExtraControl::GLIDE == currentPreset->biteControl) { + if (ExtraControl::GLIDE == state.currentPreset->biteControl) { // Portamento is controlled with the bite sensor in the mouthpiece - 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); + portSumCC += mapConstrain(instrument.biteSignal, calibration.biteThrVal, calibration.biteMaxVal, 0, state.currentPreset->portamentoLimit); } } - if (ExtraControl::GLIDE == currentPreset->leverControl) { + if (ExtraControl::GLIDE == state.currentPreset->leverControl) { // Portamento is controlled with thumb lever - 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); + portSumCC += mapConstrain((3000 - instrument.leverSignal), calibration.leverThrVal, calibration.leverMaxVal, 0, state.currentPreset->portamentoLimit); } } - port(constrain(portSumCC, 0, currentPreset->portamentoLimit)); // Total output glide rate limited to glide max setting + port(constrain(portSumCC, 0, state.currentPreset->portamentoLimit)); // Total output glide rate limited to glide max setting } //*********************************************************** -void biteCC_() { +void sendCC() { int biteVal = 0; - if (ExtraControl::CC == currentPreset->biteControl) { - instrument.biteSignal = readTouchUtil(bitePin); // get sensor data, do some smoothing - SENSOR PIN 17 - PCB PINS LABELED "BITE" (GND left, sensor pin right) + if (ExtraControl::CC == state.currentPreset->biteControl) { 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 != instrument.biteVal) { - midiSendControlChange(currentPreset->biteCC, biteVal); + midiSendControlChange(state.currentPreset->biteCC, biteVal); } instrument.biteVal = biteVal; } + + int extraVal = 0; + if (ExtraControl::CC == state.currentPreset->extraControl) { + if (instrument.extraSignal >= calibration.extraThrVal) { // we are over the threshold, calculate CC value + extraVal = mapConstrain(instrument.extraSignal, calibration.extraThrVal, calibration.extraMaxVal, 0, 127); + } + + if (extraVal != instrument.extraVal) { + midiSendControlChange(state.currentPreset->extraCC, extraVal); + } + instrument.extraVal = extraVal; + } } -void autoCal() { - instrument.vibZero = instrument.vibZeroBite = 0; - for(int i = 1 ; i <= CALIBRATE_SAMPLE_COUNT; ++i) { - instrument.breathZero += readPressure(); - instrument.breathAltZero += readAltPressure(); - instrument.vibZero += readTouchUtil(vibratoPin); - instrument.vibZeroBite += readTouchUtil(bitePin); - } - - instrument.breathZero /= CALIBRATE_SAMPLE_COUNT; - instrument.breathAltZero /= CALIBRATE_SAMPLE_COUNT; - instrument.vibZero /= CALIBRATE_SAMPLE_COUNT; - instrument.vibZeroBite /= CALIBRATE_SAMPLE_COUNT; - - instrument.vibThr = instrument.vibZero - currentPreset->vibSquelch; - instrument.vibThrLo = instrument.vibZero + currentPreset->vibSquelch; - instrument.vibThrBite = instrument.vibZeroBite - currentPreset->vibSquelch; - instrument.vibThrBiteLo = instrument.vibZeroBite + currentPreset->vibSquelch; +// Re-zero floating calibration values +void rezero() { + instrument.vibThr = instrument.vibZero + state.currentPreset->vibSquelch; + instrument.vibThrLo = instrument.vibZero - state.currentPreset->vibSquelch; + instrument.vibThrBite = instrument.vibZeroBite + state.currentPreset->vibSquelch; + instrument.vibThrBiteLo = instrument.vibZeroBite - state.currentPreset->vibSquelch; instrument.breathThrVal = instrument.breathZero + calibration.breathThrValOffset; instrument.breathMaxVal = instrument.breathThrVal + calibration.breathMaxValOffset; instrument.breathAltThrVal = instrument.breathAltZero + calibration.breathAltThrValOffset; instrument.breathAltMaxVal = instrument.breathAltThrVal + calibration.breathAltMaxValOffset; +} +void autoCal() { + instrument.vibZero = instrument.vibZeroBite = 0; + long int bZero = 0; + long int bAltZero = 0; + for(int i = 1 ; i <= CALIBRATE_SAMPLE_COUNT; ++i) { + bZero += readPressure(); + bAltZero += readAltPressure(); + instrument.vibZero += readTouchUtil(leverPin); + instrument.vibZeroBite += readTouchUtil(bitePin); + } + + instrument.breathZero = bZero / CALIBRATE_SAMPLE_COUNT; + instrument.breathAltZero = bAltZero / CALIBRATE_SAMPLE_COUNT; + instrument.vibZero /= CALIBRATE_SAMPLE_COUNT; + instrument.vibZeroBite /= CALIBRATE_SAMPLE_COUNT; + + rezero(); } void fullAutoCal() { int calRead; int calReadNext; + + calibration.breathAltThrValOffset = 5; + calibration.breathAltMaxValOffset = 1500 ; + calibration.breathThrValOffset = 5; + calibration.breathMaxValOffset = 1500 ; autoCal(); // Lever - calRead = 3000 - readTouchUtil(vibratoPin); - calibration.leverThrVal = constrain(calRead + 60, LEVER_LO_LIMIT, LEVER_HI_LIMIT); - calibration.leverMaxVal = constrain(calRead + 120, LEVER_LO_LIMIT, LEVER_HI_LIMIT); + calRead = readTouchUtil(leverPin); + calibration.leverThrVal = constrain(calRead + 100, LEVER_LO_LIMIT, LEVER_HI_LIMIT); + calibration.leverMaxVal = constrain(calRead + 300, LEVER_LO_LIMIT, LEVER_HI_LIMIT); + + // Extra + calRead = readTouchUtil(extraPin); + calibration.extraThrVal = constrain(calRead + 100, EXTRA_LO_LIMIT, EXTRA_HI_LIMIT); + calibration.extraMaxVal = constrain(calRead + 300, EXTRA_LO_LIMIT, EXTRA_HI_LIMIT); // Bite sensor calRead = readTouchUtil(bitePin); calibration.biteThrVal = constrain(calRead + 100, BITE_LO_LIMIT, BITE_HI_LIMIT); - calibration.biteMaxVal = constrain(calibration.biteThrVal + 300, BITE_LO_LIMIT, BITE_HI_LIMIT); + calibration.biteMaxVal = constrain(calRead + 300, BITE_LO_LIMIT, BITE_HI_LIMIT); + + // PB + calRead = readTouchUtil(pbDnPin); + calibration.pbDnThrVal = constrain(calRead + 100, BITE_LO_LIMIT, BITE_HI_LIMIT); + calibration.pbDnMaxVal = constrain(calRead + 300, BITE_LO_LIMIT, BITE_HI_LIMIT); + + calRead = readTouchUtil(pbUpPin); + calibration.pbUpThrVal = constrain(calRead + 100, BITE_LO_LIMIT, BITE_HI_LIMIT); + calibration.pbUpMaxVal = constrain(calRead + 300, BITE_LO_LIMIT, BITE_HI_LIMIT); // Touch sensors calRead = CTOUCH_HI_LIMIT; @@ -457,7 +498,7 @@ void fullAutoCal() { calRead = calReadNext; // use lowest value } - calibration.ctouchThrVal = calRead - 20; + calibration.ctouchThrVal = calRead + 50; } //*********************************************************** @@ -467,6 +508,9 @@ void fullAutoCal() { */ int readOctave() { static byte lastOctaveR = 0; + static unsigned long lastDeglitchTime = 0; // The last time the fingering was changed + static unsigned long lastOctave = 0; // The last time the fingering was changed + static unsigned long fingeredOctave = 0; // The last time the fingering was changed // Roller modes // 1: Highest touched roller, release memory @@ -475,20 +519,20 @@ int readOctave() { // 4: Touched roller pair, extend range // 5: Touched roller, pair = 5th partial // 6: Touched roller, pair = 5th partial, extend range - RollerMode rollerMode = currentPreset->rollerMode; - byte extend = rollerMode == RollerMode::HIGHEST_EXTEND || rollerMode == RollerMode::HIGHEST_PAIR_EXTEND || rollerMode == RollerMode::PARTIAL_EXTEND ? 1 : 0; + RollerMode rollerMode = state.currentPreset->rollerMode; + bool extend = rollerMode == RollerMode::HIGHEST_EXTEND || rollerMode == RollerMode::HIGHEST_PAIR_EXTEND || rollerMode == RollerMode::PARTIAL_EXTEND ? 1 : 0; - byte rollers[6]; + bool rollers[6]; uint16_t ctouchThrVal = calibration.ctouchThrVal; - rollers[0] = readTouchUtil(R1Pin) < ctouchThrVal; - rollers[1] = readTouchUtil(R2Pin) < ctouchThrVal; - rollers[2] = readTouchUtil(R3Pin) < ctouchThrVal; - rollers[3] = readTouchUtil(R4Pin) < ctouchThrVal; - rollers[4] = readTouchUtil(R5Pin) < ctouchThrVal; - rollers[5] = readTouchUtil(R6Pin) < ctouchThrVal; + rollers[0] = readTouchUtil(R1Pin) > ctouchThrVal; + rollers[1] = readTouchUtil(R2Pin) > ctouchThrVal; + rollers[2] = readTouchUtil(R3Pin) > ctouchThrVal; + rollers[3] = readTouchUtil(R4Pin) > ctouchThrVal; + rollers[4] = readTouchUtil(R5Pin) > ctouchThrVal; + rollers[5] = readTouchUtil(R6Pin) > ctouchThrVal; - byte offset = 0; - byte octaveR = 0; + int offset = 0; + int octaveR = 0; if (rollerMode == RollerMode::HIGHEST || rollerMode == RollerMode::HIGHEST_EXTEND) { for (int i = 5; i >= 0; i--) { if (rollers[i]) { @@ -550,17 +594,32 @@ int readOctave() { lastOctaveR = octaveR; - FingeringMode fingering = currentPreset->fingering; + int octave = 0; + + FingeringMode fingering = state.currentPreset->fingering; byte K4 = readTouchKey(K4Pin) < calibration.ctouchThrVal; if (FingeringMode::TPT == fingering) { // TPT fingering - return 24 + offset + trumpetHarmonic[K4][octaveR]; // roller harmonics + octave = 24 + offset + trumpetHarmonic[K4][octaveR]; // roller harmonics } else if (FingeringMode::HRN == fingering) { // HRN fingering - return 12 + offset + rollerHarmonic[K4][octaveR]; // roller harmonics + octave = 12 + offset + rollerHarmonic[K4][octaveR]; // roller harmonics } else if (FingeringMode::EVR == fingering) { // HRN fingering - return 12 * (6 - octaveR) + offset + instrument.octave; + octave = 12 * (6 - octaveR) + offset + instrument.octave * 12; } else { // EVI - return 12 * octaveR + offset + instrument.octave; + octave = 12 * octaveR + offset + instrument.octave * 12; } + + if (octave != lastOctave) { // + // reset the debouncing timer + lastDeglitchTime = millis(); + } + + if ((millis() - lastDeglitchTime) > state.currentPreset->deglitch) { + fingeredOctave = octave; + } + + lastOctave = octave; + + return fingeredOctave; } int readSwitches() { @@ -574,7 +633,7 @@ int readSwitches() { int16_t touchSum = 0; for (byte i = 0; i < 12; i++) { int16_t val = readTouchKey(i); - touchKeys[i] = calibration.ctouchThrVal; + touchKeys[i] = val > calibration.ctouchThrVal; touchSum += val; } @@ -591,31 +650,31 @@ int readSwitches() { instrument.pinkyKey = touchKeys[K8Pin]; - int qTransp = (instrument.pinkyKey && (currentPreset->pinkySetting < 25)) ? currentPreset->pinkySetting - 12 : 0; + int qTransp = (instrument.pinkyKey && (state.currentPreset->pinkySetting < 25)) ? state.currentPreset->pinkySetting - 12 : 0; // Calculate midi note number from pressed keys int fingeredNoteUntransposed = 0; - if (EVI == currentPreset->fingering) { // EVI fingering + if (EVI == state.currentPreset->fingering) { // EVI fingering fingeredNoteUntransposed = START_NOTE - 2 * K1 - K2 - 3 * K3 //"Trumpet valves" - 5 * K4 // Fifth key - + 2 * K5 + K6 + currentPreset->trill3_interval * K7; // Trill keys. 3rd trill key interval controlled by setting - } else if (EVR == currentPreset->fingering) { // EVR fingering + + 2 * K5 + K6 + state.currentPreset->trill3_interval * K7; // Trill keys. 3rd trill key interval controlled by setting + } else if (EVR == state.currentPreset->fingering) { // EVR fingering fingeredNoteUntransposed = START_NOTE - 2 * K1 - K2 - 3 * K3 //"Trumpet valves" - 5 * K4 // Fifth key - + 2 * K5 + K6 + currentPreset->trill3_interval * K7; // Trill keys. 3rd trill key interval controlled by setting - } else if (TPT == currentPreset->fingering) { // TPT fingering + + 2 * K5 + K6 + state.currentPreset->trill3_interval * K7; // Trill keys. 3rd trill key interval controlled by setting + } else if (TPT == state.currentPreset->fingering) { // TPT fingering fingeredNoteUntransposed = START_NOTE - 2 * K1 - K2 - 3 * K3 //"Trumpet valves" - 2 // Trumpet in B flat - + 2 * K5 + K6 + currentPreset->trill3_interval * K7; // Trill keys. 3rd trill key interval controlled by setting - } else if (HRN == currentPreset->fingering) { // HRN fingering + + 2 * K5 + K6 + state.currentPreset->trill3_interval * K7; // Trill keys. 3rd trill key interval controlled by setting + } else if (HRN == state.currentPreset->fingering) { // HRN fingering fingeredNoteUntransposed = START_NOTE - 2 * K1 - K2 - 3 * K3 //"Trumpet valves" + 5 * K4 // Switch to Bb horn + 5 // Horn in F - + 2 * K5 + K6 + currentPreset->trill3_interval * K7; // Trill keys. 3rd trill key interval controlled by setting + + 2 * K5 + K6 + state.currentPreset->trill3_interval * K7; // Trill keys. 3rd trill key interval controlled by setting } if (K3 && K7) { - if (4 == currentPreset->trill3_interval) + if (4 == state.currentPreset->trill3_interval) fingeredNoteUntransposed += 2; else fingeredNoteUntransposed += 4; @@ -628,7 +687,7 @@ int readSwitches() { lastDeglitchTime = millis(); } - if ((millis() - lastDeglitchTime) > currentPreset->deglitch) { + if ((millis() - lastDeglitchTime) > state.currentPreset->deglitch) { // whatever the reading is at, it's been there for longer // than the debounce delay, so take it as the actual current instrument fingeredNote = fingeredNoteRead; @@ -639,27 +698,26 @@ int readSwitches() { return fingeredNote; } -void noteOn(int fingeredNote, float pressureSensor, int initial_breath_value) { +void noteOn(int fingeredNote, int 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 instrument.breathSignal = constrain(max(pressureSensor, initial_breath_value), instrument.breathThrVal, instrument.breathMaxVal); byte velocitySend; - if (!currentPreset->fixedVelocity) { + if (!state.currentPreset->fixedVelocity) { 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); + velocitySend = constrain(velocitySend + velocitySend * .1 * state.currentPreset->velBias, 1, 127); } else { - velocitySend = currentPreset->fixedVelocity; + velocitySend = state.currentPreset->fixedVelocity; } breath(); // send breath data midiSendNoteOn(fingeredNote, velocitySend); // send Note On message for new note - instrument.activeNote = fingeredNote; } void handleOffStateActions() { - if (instrument.activeMIDIchannel != currentPreset->MIDIchannel) { - instrument.activeMIDIchannel = currentPreset->MIDIchannel; // only switch channel if no active note + if (instrument.activeMIDIchannel != state.currentPreset->MIDIchannel) { + instrument.activeMIDIchannel = state.currentPreset->MIDIchannel; // only switch channel if no active note midiSetChannel(instrument.activeMIDIchannel); } if ((instrument.activePatch != instrument.patch) && instrument.doPatchUpdate) { @@ -676,8 +734,20 @@ void initState() { instrument.activePatch = 0; state.mainState = NOTE_OFF; // initialize main state machine - instrument.activeMIDIchannel = currentPreset->MIDIchannel; - midiInitialize(currentPreset->MIDIchannel); + instrument.activeMIDIchannel = state.currentPreset->MIDIchannel; + midiInitialize(state.currentPreset->MIDIchannel); + breathCCFilter.setFilter(LOWPASS, 3, 0.0); // create a one pole (RC) lowpass filter +} + +/** + * Read all utility sensors +*/ +void readUtil() { + instrument.biteSignal = readTouchUtil(bitePin); + instrument.leverSignal = readTouchUtil(leverPin); + instrument.pbUpSignal = readTouchUtil(pbUpPin); // PCB PIN "Pu" + instrument.pbDnSignal = readTouchUtil(pbDnPin); // PCB PIN "Pd" + instrument.extraSignal = readTouchUtil(extraPin); } /** @@ -690,23 +760,26 @@ void sendCCs() { static unsigned long ccSendTime3 = 0L; // The last time we sent CC values 3 (and slower) // Is it time to send more CC data? - uint32_t currentTime = millis(); - if ((currentTime - ccBreathSendTime) > (currentPreset->breathInterval - 1u)) { + unsigned long currentTime = millis(); + if ((currentTime - ccBreathSendTime) > (state.currentPreset->breathInterval)) { breath(); ccBreathSendTime = currentTime; } if (currentTime - ccSendTime > CC_INTERVAL_PRIMARY) { // deal with Pitch Bend, Modulation, etc. + readUtil(); pitch_bend(); - biteCC_(); + sendCC(); ccSendTime = currentTime; } if (currentTime - ccSendTime2 > CC_INTERVAL_PORT) { + readUtil(); portamento_(); ccSendTime2 = currentTime; } if (currentTime - ccSendTime3 > CC_INTERVAL_OTHER) { - updateSensorLEDs(); + readUtil(); + updateSensorLEDs(*state.instrument); ccSendTime3 = currentTime; } } @@ -718,10 +791,19 @@ void runStateMachine() { static unsigned long breath_on_time = 0L; // Time when breath sensor value went over the ON threshold static int initial_breath_value = 0; // The breath value at the time we observed the transition + Serial.print(">breath:"); + Serial.println(instrument.breathSignal); + Serial.print(">breathThr:"); + Serial.println(instrument.breathThrVal); + Serial.print(">spike:"); + Serial.println(instrument.spikeSignal); + Serial.print(">note:"); + Serial.println(state.mainState); + int fingeredNote = noteValueCheck(readSwitches() + readOctave()); if (state.mainState == NOTE_OFF) { handleOffStateActions(); - if ((instrument.breathSignal > instrument.breathThrVal)) { + if (instrument.breathSignal > instrument.breathThrVal && state.mainState == NOTE_OFF) { // Value has risen above threshold. Move to the RISE_WAIT // state. Record time and initial breath value. breath_on_time = millis(); @@ -731,16 +813,20 @@ void runStateMachine() { } else if (state.mainState == RISE_WAIT) { 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) { + if ((millis() - breath_on_time > state.currentPreset->velSmpDl) || (0 == state.currentPreset->velSmpDl) || state.currentPreset->fixedVelocity) { noteOn(fingeredNote, instrument.breathSignal, initial_breath_value); state.mainState = NOTE_ON; + instrument.activeNote = fingeredNote; } } else { // Value fell below threshold before velocity sample delay time passed. Return to NOTE_OFF state state.mainState = NOTE_OFF; } } else if (state.mainState == NOTE_ON) { - if ((instrument.breathSignal < instrument.breathThrVal)) { + if (state.currentPreset->spikeFilterFreq && instrument.spikeSignal < -calibration.spikeThrVal) { + midiSendNoteOff(instrument.activeNote); // send Note Off message + state.mainState = SPIKE_HOLD; + } else if (instrument.breathSignal < instrument.breathThrVal) { // Value has fallen below threshold - turn the note off midiSendNoteOff(instrument.activeNote); // send Note Off message instrument.breathSignal = 0; @@ -753,8 +839,17 @@ void runStateMachine() { noteOn(fingeredNote, instrument.breathSignal, 0); delayMicroseconds(2000); // delay for midi recording fix midiSendNoteOff(instrument.activeNote); // send Note Off message + instrument.activeNote = fingeredNote; } } + } else if (state.mainState == SPIKE_HOLD) { + if (state.currentPreset->spikeFilterFreq && instrument.spikeSignal > calibration.spikeThrVal) { + noteOn(fingeredNote, instrument.breathSignal, initial_breath_value); + state.mainState = NOTE_ON; + instrument.activeNote = fingeredNote; + } else if ((instrument.breathSignal < instrument.breathThrVal)) { + state.mainState = NOTE_OFF; + } } } @@ -769,7 +864,7 @@ void setup() { } initHardware(); - delay(100); + delay(100); // Make sure the inputs settle Serial.println(buttonState()); bool factoryReset = checkButtonState(STARTUP_FACTORY_RESET); configManagementMode = checkButtonState(STARTUP_CONFIG); @@ -788,25 +883,26 @@ void setup() { } // Read eeprom data into global vars - // readEEPROM(factoryReset); + readEEPROM(factoryReset, *state.calibration); + updateFilters(*state.currentPreset); 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(); + displayOff(state); } //_______________________________________________________________________________________________ MAIN LOOP @@ -815,9 +911,6 @@ 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(); @@ -830,6 +923,8 @@ void loop() { } instrument.breathSignal = constrain(readPressure(), BREATH_LO_LIMIT, BREATH_HI_LIMIT); // Get the filtered pressure sensor reading from analog pin A0, input from sensor MP3V5004GP + instrument.breathAltSignal = constrain(readAltPressure(), BREATH_LO_LIMIT, BREATH_HI_LIMIT); // Get the filtered pressure sensor reading from analog pin A0, input from sensor MP3V5004GP + instrument.spikeSignal = constrain(readSpikePressure(), -SPIKE_HI_LIMIT, SPIKE_HI_LIMIT); runStateMachine(); sendCCs();