diff --git a/NuEVI/src/TODO b/NuEVI/src/TODO index 47e69d1..effa3fb 100644 --- a/NuEVI/src/TODO +++ b/NuEVI/src/TODO @@ -1,9 +1,6 @@ 1. LED abstraction code 5. Refactor CV behavior into module -6. 9dof sensor code -7. Alternate fingerings -8. Encoder midi +7. LH fingerings - Lever mode: pb -- Breath suck control? -- \ No newline at end of file +- Breath suck control? \ No newline at end of file diff --git a/NuEVI/src/config.h b/NuEVI/src/config.h index f2c3f90..4dce885 100644 --- a/NuEVI/src/config.h +++ b/NuEVI/src/config.h @@ -10,8 +10,9 @@ #define ON_Delay 20 // Set Delay after ON threshold before velocity is checked (wait for tounging peak) #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 START_NOTE 36 // set startNote to C (change this value in steps of 12 to start in other octaves) #define FILTER_FREQ 10.0 +#define BREATH_THR_MAX_BOOST 40.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 @@ -31,6 +32,7 @@ #define BTN_PRESET 0x8 #define KNOB_CURVE_THRESHOLD 100 #define KNOB_CURVE_MULTIPLIER 5 +#define KNOB_DISPLAY_TIME 500 // Send breath CC data no more than every CC_BREATH_INTERVAL // milliseconds diff --git a/NuEVI/src/globals.h b/NuEVI/src/globals.h index c2a898c..0b47679 100644 --- a/NuEVI/src/globals.h +++ b/NuEVI/src/globals.h @@ -20,9 +20,9 @@ enum PinkyMode : uint8_t { PBD = 12, - GLD = 25, - MOD = 26, - QTN = 27, + GLD = 26, + MOD = 27, + QTN = 28, }; enum FingeringMode : uint8_t { @@ -105,6 +105,8 @@ struct instrument_state_t { int pitchBend = 8192; int pbSend = 8192; // Pitch bend actually sent, modified by vibrato, etc byte knobVals[4]; + byte lastKnobVal; + unsigned long lastKnobTime; byte rollCCVal = 0; byte tiltCCVal = 0; @@ -119,6 +121,7 @@ struct instrument_state_t { // Calibration int16_t breathZero; // this gets auto calibrated in setup int16_t breathThrVal; // this gets auto calibrated in setup + int16_t breathMovingThrVal; int16_t breathMaxVal; // this gets auto calibrated in setup int16_t breathAltZero; // this gets auto calibrated in setup int16_t breathAltThrVal; // this gets auto calibrated in setup diff --git a/NuEVI/src/hardware.cpp b/NuEVI/src/hardware.cpp index e69cf6c..bd2c293 100644 --- a/NuEVI/src/hardware.cpp +++ b/NuEVI/src/hardware.cpp @@ -9,6 +9,8 @@ FilterOnePole breathFilter; FilterOnePole breathAltFilter; FilterOnePole spikeFilter; +FilterOnePole tiltFilter; +FilterOnePole rollFilter; Adafruit_MPR121 touchSensorKeys = Adafruit_MPR121(); Adafruit_MPR121 touchSensorUtil = Adafruit_MPR121(); @@ -44,6 +46,8 @@ 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 + tiltFilter.setFilter(LOWPASS, 2, 0.0); // create a one pole (RC) lowpass filter + rollFilter.setFilter(LOWPASS, 2, 0.0); // create a one pole (RC) lowpass filter icmSensor.begin_I2C(ICM20948_I2CADDR_DEFAULT, &MainI2CBus); ledStrip.begin(); @@ -179,7 +183,7 @@ icm_result_t readICM() { icmSensor.getEvent(&accel, &gyro, &temp, &mag); return { - mag.magnetic.y, - mag.magnetic.x, + tiltFilter.input(mag.magnetic.y), + rollFilter.input(mag.magnetic.x), }; } \ No newline at end of file diff --git a/NuEVI/src/icm.cpp b/NuEVI/src/icm.cpp new file mode 100644 index 0000000..c274902 --- /dev/null +++ b/NuEVI/src/icm.cpp @@ -0,0 +1,25 @@ +#include "hardware.h" +#include "midi.h" + +void checkICM(state_t &state) { + icm_result_t icmSignal = readICM(); + Serial.print(">roll: "); + Serial.println(icmSignal.roll); + Serial.print(">tilt: "); + Serial.println(icmSignal.tilt); + if (ExtraControl::CC == state.currentPreset->icmRollMode) { + byte roll = mapConstrain(abs(icmSignal.roll), 0, 40, 127, 0); + if (roll != state.instrument->rollCCVal) { + midiSendControlChange(state.currentPreset->icmRollCC, roll); + state.instrument->rollCCVal = roll; + } + } + + if (ExtraControl::CC == state.currentPreset->icmTiltMode) { + byte tilt = mapConstrain(abs(icmSignal.tilt), -20, 40, 0, 127); + if (tilt != state.instrument->tiltCCVal) { + midiSendControlChange(state.currentPreset->icmTiltCC, tilt); + state.instrument->tiltCCVal = tilt; + } + } +} \ No newline at end of file diff --git a/NuEVI/src/icm.h b/NuEVI/src/icm.h new file mode 100644 index 0000000..ae89bf1 --- /dev/null +++ b/NuEVI/src/icm.h @@ -0,0 +1,8 @@ +#ifndef __ICM_H +#define __ICM_H + +#include "settings.h" + +void checkICM(state_t &state); + +#endif \ No newline at end of file diff --git a/NuEVI/src/led.cpp b/NuEVI/src/led.cpp index 9e632e5..683015a 100644 --- a/NuEVI/src/led.cpp +++ b/NuEVI/src/led.cpp @@ -25,6 +25,14 @@ void singleLED(int n, int color) { // 0 -> 0, 0, 0, 0, 0, 0, 0, 0 void ledFullMeter(byte indicatedValue, int color){ + static byte lastVal = 0; + static int lastColor = 0; + if (lastVal == indicatedValue && lastColor == color) { + return; + } + lastVal = indicatedValue; + lastColor = color; + int scaledVal = indicatedValue; ledStrip.setBrightness(1); for (int i = 0; i < 8; i++) { @@ -79,5 +87,9 @@ void statusLedBlink() { } void updateSensorLEDs(instrument_state_t &state) { - ledFullMeter(state.breathCCVal, 0x0000FF); + if (millis() - state.lastKnobTime < KNOB_DISPLAY_TIME) { + ledFullMeter(state.lastKnobVal, 0x00FF00); + } else { + ledFullMeter(state.breathCCVal, 0x0000FF); + } } diff --git a/NuEVI/src/menu.cpp b/NuEVI/src/menu.cpp index 778511a..a04d733 100644 --- a/NuEVI/src/menu.cpp +++ b/NuEVI/src/menu.cpp @@ -516,7 +516,7 @@ public: } if (_focused && input.changed && input.knobMenu != 0) { - _cursorPos = (_cursorPos + input.knobMenu) % _entries.size(); + _cursorPos = ((_cursorPos + input.knobMenu) + _entries.size()) % _entries.size(); draw(false); redrawValue = true; } diff --git a/NuEVI/src/xEVI.cpp b/NuEVI/src/xEVI.cpp index 5012cf6..7f0c3e9 100644 --- a/NuEVI/src/xEVI.cpp +++ b/NuEVI/src/xEVI.cpp @@ -13,6 +13,7 @@ #include "led.h" #include "test.h" #include "FilterOnePole.h" +#include "icm.h" /* NAME: xEVI @@ -92,6 +93,7 @@ bool configManagementMode = false; bool testMode = false; FilterOnePole breathCCFilter; +FilterOnePole breathBaselineFilter; //_______________________________________________________________________________________________ SETUP @@ -444,33 +446,13 @@ void sendCC() { midiSendControlChange(state.currentPreset->knob4CC, val); break; } + + state.instrument->knobVals[i] = val; + state.instrument->lastKnobVal = val; + state.instrument->lastKnobTime = millis(); } - - state.instrument->knobVals[i] = val; } } - - icm_result_t icmSignal = readICM(); - Serial.print(">roll: "); - Serial.println(icmSignal.roll); - Serial.print(">tilt: "); - Serial.println(icmSignal.tilt); - if (ExtraControl::CC == state.currentPreset->icmRollMode) { - byte roll = mapConstrain(abs(icmSignal.roll), 0, 40, 127, 0); - if (roll != state.instrument->rollCCVal) { - midiSendControlChange(state.currentPreset->icmRollCC, roll); - state.instrument->rollCCVal = roll; - } - } - - if (ExtraControl::CC == state.currentPreset->icmTiltMode) { - byte tilt = mapConstrain(abs(icmSignal.tilt), -20, 40, 0, 127); - if (tilt != state.instrument->tiltCCVal) { - midiSendControlChange(state.currentPreset->icmTiltCC, tilt); - state.instrument->tiltCCVal = tilt; - } - } - } // Re-zero floating calibration values @@ -602,7 +584,7 @@ int readOctave() { if (rollers[0]) { octaveR = 1; } else if (rollers[5]) { - octaveR = 6; + octaveR = 7; } } } else if (rollerMode == RollerMode::PARTIAL || rollerMode == RollerMode::PARTIAL_EXTEND) { @@ -632,6 +614,8 @@ int readOctave() { if (extend && octaveR == 0) { if (rollerMode == RollerMode::HIGHEST_EXTEND && lastOctaveR == 6) { octaveR = 7; + } else if (rollerMode == RollerMode::HIGHEST_PAIR_EXTEND && lastOctaveR == 7) { + octaveR = 8; } else if (rollerMode == RollerMode::PARTIAL_EXTEND && lastOctaveR == 7) { octaveR = 8; } else if (lastOctaveR == 1) { @@ -785,7 +769,8 @@ void initState() { instrument.activeMIDIchannel = state.currentPreset->MIDIchannel; midiInitialize(state.currentPreset->MIDIchannel); - breathCCFilter.setFilter(LOWPASS, 3, 0.0); // create a one pole (RC) lowpass filter + breathCCFilter.setFilter(LOWPASS, 3, 0.0); + breathBaselineFilter.setFilter(LOWPASS, 1, 0.0); } /** @@ -802,7 +787,7 @@ void readUtil() { /** * Send CC data when needed */ -void sendCCs() { +void handleCCs() { static unsigned long ccBreathSendTime = 0L; // The last time we sent breath CC values static unsigned long ccSendTime = 0L; // The last time we sent CC values static unsigned long ccSendTime2 = 0L; // The last time we sent CC values 2 (slower) @@ -819,6 +804,7 @@ void sendCCs() { readUtil(); pitch_bend(); sendCC(); + checkICM(state); ccSendTime = currentTime; } if (currentTime - ccSendTime2 > CC_INTERVAL_PORT) { @@ -833,6 +819,43 @@ void sendCCs() { } } +/** + * Read the breath sensors according to the active mode +*/ +void readBreath() { + + int16_t breathSignal = constrain(readPressure(), BREATH_LO_LIMIT, BREATH_HI_LIMIT); // Get the filtered pressure sensor reading from analog pin A0, input from sensor MP3V5004GP + int16_t spikeSignal = constrain(readSpikePressure(), -SPIKE_HI_LIMIT, SPIKE_HI_LIMIT); + instrument.breathMovingThrVal = constrain( + breathBaselineFilter.input((breathSignal + instrument.breathZero) / 2), + instrument.breathThrVal, + instrument.breathMaxVal + ); + + if (state.currentPreset->breathMode == BREATH_ACC || state.currentPreset->breathMode == BREATH_ACC_AT) { + int delta = breathSignal - instrument.breathZero; + if (abs(delta) > state.calibration->breathAltThrValOffset) { + instrument.breathSignal = constrain(instrument.breathSignal + delta / 10, instrument.breathZero, instrument.breathMaxVal); + } + } else { + instrument.breathSignal = breathSignal + (spikeSignal > 0 ? spikeSignal * state.currentPreset->spikeOnFactor : spikeSignal * state.currentPreset->spikeOffFactor); + } + instrument.breathAltSignal = constrain(readAltPressure(), BREATH_LO_LIMIT, BREATH_HI_LIMIT); // Get the filtered pressure sensor reading from analog pin A0, input from sensor MP3V5004GP + + Serial.print(">breathThr:"); + Serial.println(instrument.breathThrVal); + Serial.print(">breathMovingThr:"); + Serial.println(instrument.breathMovingThrVal); + Serial.print(">note:"); + Serial.println(state.mainState); + Serial.print(">spike:"); + Serial.println(spikeSignal); + Serial.print(">breath:"); + Serial.println(breathSignal); + Serial.print(">combo:"); + Serial.println(instrument.breathSignal); +} + /** * Main instrument state machine */ @@ -842,7 +865,7 @@ void runStateMachine() { int fingeredNote = noteValueCheck(readSwitches() + readOctave()); if (state.mainState == NOTE_OFF) { handleOffStateActions(); - if (instrument.breathSignal > instrument.breathThrVal && state.mainState == NOTE_OFF) { + if (instrument.breathSignal > instrument.breathMovingThrVal && 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(); @@ -850,7 +873,7 @@ void runStateMachine() { state.mainState = RISE_WAIT; // Go to next state } } else if (state.mainState == RISE_WAIT) { - if ((instrument.breathSignal > instrument.breathThrVal)) { + if ((instrument.breathSignal > instrument.breathMovingThrVal)) { // Has enough time passed for us to collect our second sample? if ((millis() - breath_on_time > state.currentPreset->velSmpDl) || (0 == state.currentPreset->velSmpDl) || state.currentPreset->fixedVelocity) { noteOn(fingeredNote, instrument.breathSignal, initial_breath_value); @@ -862,7 +885,7 @@ void runStateMachine() { state.mainState = NOTE_OFF; } } else if (state.mainState == NOTE_ON) { - if (instrument.breathSignal < instrument.breathThrVal) { + if (instrument.breathSignal < instrument.breathMovingThrVal) { // Value has fallen below threshold - turn the note off midiSendNoteOff(instrument.activeNote); // send Note Off message instrument.breathSignal = 0; @@ -954,35 +977,10 @@ void loop() { return; } - int16_t breathSignal = constrain(readPressure(), BREATH_LO_LIMIT, BREATH_HI_LIMIT); // Get the filtered pressure sensor reading from analog pin A0, input from sensor MP3V5004GP - int16_t spikeSignal = constrain(readSpikePressure(), -SPIKE_HI_LIMIT, SPIKE_HI_LIMIT); - - - if (state.currentPreset->breathMode == BREATH_ACC || state.currentPreset->breathMode == BREATH_ACC_AT) { - int delta = breathSignal - instrument.breathZero; - if (abs(delta) > state.calibration->breathAltThrValOffset) { - instrument.breathSignal = constrain(instrument.breathSignal + delta / 10, instrument.breathZero, instrument.breathMaxVal); - } - } else { - instrument.breathSignal = breathSignal + (spikeSignal > 0 ? spikeSignal * state.currentPreset->spikeOnFactor : spikeSignal * state.currentPreset->spikeOffFactor); - } - instrument.breathAltSignal = constrain(readAltPressure(), BREATH_LO_LIMIT, BREATH_HI_LIMIT); // Get the filtered pressure sensor reading from analog pin A0, input from sensor MP3V5004GP - - /* - Serial.print(">breathThr:"); - Serial.println(instrument.breathThrVal); - Serial.print(">note:"); - Serial.println(state.mainState); - Serial.print(">spike:"); - Serial.println(spikeSignal); - Serial.print(">breath:"); - Serial.println(breathSignal); - Serial.print(">combo:"); - Serial.println(instrument.breathSignal); - */ + readBreath(); runStateMachine(); - sendCCs(); + handleCCs(); // cvUpdate(); midiDiscardInput();