New poly modes, processing improvements

This commit is contained in:
Brian Hrebec 2024-01-30 22:16:15 -06:00
parent c09177c0ee
commit 4211a85562
9 changed files with 258 additions and 162 deletions

View file

@ -65,7 +65,7 @@ enum ExtraControl : uint8_t {
enum PolySelect : uint8_t {
EHarmonizerOff = 0,
EDuo = 1,
EChord = 2,
EDouble = 2,
};
enum PortamentoMode : uint8_t {
@ -75,15 +75,23 @@ enum PortamentoMode : uint8_t {
PGLIDE_ONLY = 3,
};
enum InstrumentMode : uint8_t {
MODE_NORMAL,
MODE_TEST,
MODE_CONFIG,
MODE_DEBUG,
};
struct instrument_state_t {
InstrumentMode mode = MODE_NORMAL;
uint8_t patch; // 1-128
byte activeMIDIchannel = 1; // MIDI channel
byte activeNote = 0; // note playing
byte activeNote2 = 0; // note playing
byte activePatch = 0;
byte doPatchUpdate = 0;
int8_t transpose = 0;
int8_t octave = 0;
PolySelect polyMode = PolySelect::EHarmonizerOff;
// Raw sensor signals
int16_t breathSignal = 0; // breath level (smoothed) not mapped to CC value
@ -113,6 +121,9 @@ struct instrument_state_t {
// Key states
byte quarterToneTrigger;
byte pinkyKey = 0;
byte fingeredNote = 0;
byte fingeredNote2 = 0;
byte fingeredOctave = 0;
// CV values
int cvPitch;

View file

@ -166,8 +166,6 @@ int readSpikePressure() {
int readPressure() {
float p = pressureSensorMain.readPressure();
Serial.print(">raw:");
Serial.println(p * PRESSURE_SENS_MULTIPLIER);
return breathFilter.input(p) * PRESSURE_SENS_MULTIPLIER;
}

View file

@ -73,28 +73,28 @@ icm_result_t readICM();
// Key pins
// RH keys
#define K1Pin 5
#define K2Pin 6
#define K3Pin 2
#define K4Pin 11
#define K5Pin 4
#define K6Pin 7
#define K7Pin 9
#define K8Pin 10
#define K1Pin 1
#define K2Pin 7
#define K3Pin 3
#define K4Pin 10
#define K5Pin 2
#define K6Pin 6
#define K7Pin 4
#define K8Pin 11
// LH keys
#define K9Pin 3
#define K10Pin 8
#define K11Pin 0
#define K12Pin 1
#define K9Pin 0
#define K10Pin 5
#define K11Pin 8
#define K12Pin 9
// Octave roller pins
#define R1Pin 11
#define R2Pin 10
#define R3Pin 9
#define R4Pin 8
#define R5Pin 7
#define R6Pin 6
#define R1Pin 6
#define R2Pin 7
#define R3Pin 8
#define R4Pin 9
#define R5Pin 10
#define R6Pin 11
// Additional pins
#define bitePin 0

View file

@ -1024,6 +1024,11 @@ ChoiceMenu<4, FingeringMode> fingeringMenu("FINGERING", &preset_t::fingering, {
"TPT",
"HRN",
});
ChoiceMenu<3, PolySelect> polyMenu("POLYMODE", &preset_t::polyMode, { EHarmonizerOff, EDouble, EDuo }, {
"OFF",
"DOUBLE",
"DUO",
});
ChoiceMenu<6, RollerMode> rollerMenu("ROLLRMODE", &preset_t::rollerMode, {
HIGHEST,
HIGHEST_EXTEND,
@ -1048,6 +1053,19 @@ FuncMenu factoryResetMenu("FACT. RESET", [](state_t &state, InputState& input) {
FuncMenu saveAllPresetsMenu("SAVE PRESETS", [](state_t &state, InputState& input) {
writePresets();
});
FuncMenu debugModeMenu("DEBUG MODE", [](state_t &state, InputState& input) {
Serial.begin(9600); // debug
Serial.println("Debug Startup");
state.instrument->mode = state.instrument->mode == MODE_DEBUG ? MODE_NORMAL : MODE_DEBUG;
});
FuncMenu testModeMenu("TEST MODE", [](state_t &state, InputState& input) {
display.clearDisplay();
state.instrument->mode = MODE_TEST;
});
FuncMenu configModeMenu("CONFIG MGT", [](state_t &state, InputState& input) {
configModeSetup();
state.instrument->mode = MODE_CONFIG;
});
std::array<MenuScreen *const, 11> breathMenuEntries = {
&breathModeMenu,
@ -1064,8 +1082,9 @@ std::array<MenuScreen *const, 11> breathMenuEntries = {
};
SubMenu<11> breathMenu("BR SETUP", breathMenuEntries);
const std::array<MenuScreen *const, 19> controlMenuEntries = {
const std::array<MenuScreen *const, 20> controlMenuEntries = {
&fingeringMenu,
&polyMenu,
&rollerMenu,
&biteCtlMenu,
&biteCCMenu,
@ -1085,7 +1104,7 @@ const std::array<MenuScreen *const, 19> controlMenuEntries = {
&accelModeMenu,
&accelCCMenu,
};
SubMenu<19> controlMenu("CTL SETUP", controlMenuEntries);
SubMenu<20> controlMenu("CTL SETUP", controlMenuEntries);
const std::array<MenuScreen *const, 5> vibratoMenuEntries = {
&vibDepthMenu,
@ -1099,7 +1118,7 @@ SubMenu<5> vibratoMenu("VIBRATO", vibratoMenuEntries);
AboutMenu aboutMenu = AboutMenu();
const std::array <MenuScreen *const, 7> extrasMenuEntries = {
const std::array <MenuScreen *const, 10> extrasMenuEntries = {
&trill3Menu,
&cvTuneMenu,
&cvScaleMenu,
@ -1107,8 +1126,11 @@ const std::array <MenuScreen *const, 7> extrasMenuEntries = {
&recalibrateMenu,
&factoryResetMenu,
&saveAllPresetsMenu,
&testModeMenu,
&debugModeMenu,
&configModeMenu,
};
SubMenu<7> extrasMenu("EXTRAS", extrasMenuEntries);
SubMenu<10> extrasMenu("EXTRAS", extrasMenuEntries);
ExitMenu exitMenu = ExitMenu();
@ -1225,6 +1247,7 @@ void initDisplay() {
// internally, this will display the splashscreen.
display.clearDisplay();
display.setRotation(2);
display.drawBitmap(0, 0, nuevi_logo_bmp, LOGO16_GLCD_WIDTH, LOGO16_GLCD_HEIGHT, 1);
display.display();
currentMenu = &statusMenu;

View file

@ -313,7 +313,7 @@ void configModeSetup() {
}
//"Main loop". Just sits and wait for midi messages and lets the sysex handler do all the work.
void configModeLoop() {
void configModeLoop(state_t &state) {
usbMIDI.read();
}

View file

@ -94,8 +94,9 @@ struct preset_t {
uint8_t spikeFilterFreq = 20;
int8_t spikeOnFactor = 5;
int8_t spikeOffFactor = 5;
PolySelect polyMode = PolySelect::EHarmonizerOff;
uint8_t _reserved[83];
uint8_t _reserved[82];
};
static_assert(sizeof(preset_t) == PRESET_MAX_SIZE, "preset_t must be 128 bytes");
@ -132,6 +133,6 @@ void configInitScreen();
void configShowMessage(const char* message);
void configModeSetup();
void configModeLoop();
void configModeLoop(state_t &state);
#endif

View file

@ -5,7 +5,7 @@ uint16_t oldKeys = 0;
uint16_t oldUtil = 0;
bool plotCap = false;
void handleTestMode() {
void handleTestMode(state_t &state) {
uint8_t buttons = buttonState();
if (buttons != oldButtons) {
oldButtons = buttons;
@ -41,6 +41,10 @@ void handleTestMode() {
plotCap = !plotCap;
}
if (buttons == 0x08) {
state.instrument->mode = MODE_NORMAL;
}
if (plotCap) {
for (int i = 0; i < 12; i++) {
Serial.print(">key");

View file

@ -1,6 +1,6 @@
#ifndef __TEST_H
#define __TEST_H
void handleTestMode();
void handleTestMode(state_t &state);
#endif

View file

@ -89,9 +89,6 @@ const int rollerHarmonic[2][7] = { {0, 7, 12, 16, 19, 24, 26}, // F horn 2,3,4
const int trumpetHarmonic[2][7] = { {0, 7, 12, 16, 19, 26, 31}, //! K4: hrm 8->9, 10->12
{0, 7, 12, 16, 19, 24, 28} }; // trumpet 2,3,4,5,6,8,10 hrm
bool configManagementMode = false;
bool testMode = false;
FilterOnePole breathCCFilter;
FilterOnePole breathBaselineFilter;
@ -242,8 +239,8 @@ int breath() {
oldbreath = breathCCval;
}
if (breathCCvalHires != oldbreathhires
&& (state.currentPreset->breathMode == BreathMode::BREATH_LSB || state.currentPreset->breathMode == BreathMode::BREATH_LSB_AT)) {
if (breathCCvalHires != oldbreathhires &&
(state.currentPreset->breathMode == BreathMode::BREATH_LSB || state.currentPreset->breathMode == BreathMode::BREATH_LSB_AT)) {
midiSendControlChange(state.currentPreset->breathCC + 32, breathCCvalFine);
}
@ -641,25 +638,17 @@ int readOctave() {
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;
return octave;
}
int readSwitches() {
void readSwitches() {
// Keep the last fingering value for debouncing
static int lastFingering = 0;
static int fingeredNote = 0;
static int lastFingering2 = 0;
static int lastOctave = 0;
static unsigned long lastDeglitchTime = 0; // The last time the fingering was changed
int fingeredNoteUntransposed = 0;
int fingeredNote2Untransposed = 0;
// Read touch pads (MPR121), compare against threshold value
bool touchKeys[12];
@ -676,34 +665,54 @@ int readSwitches() {
byte K1 = touchKeys[K1Pin]; // Valve 1 (pitch change -2)
byte K2 = touchKeys[K2Pin]; // Valve 2 (pitch change -1)
byte K3 = touchKeys[K3Pin]; // Valve 3 (pitch change -3)
byte K4 = touchKeys[K4Pin]; // Left Hand index finger (pitch change -5)
byte K4 = touchKeys[K4Pin]; // Value 4 (pitch change -5)
byte K5 = touchKeys[K5Pin]; // Trill key 1 (pitch change +2)
byte K6 = touchKeys[K6Pin]; // Trill key 2 (pitch change +1)
byte K7 = touchKeys[K7Pin]; // Trill key 3 (pitch change +4)
byte K8 = touchKeys[K8Pin]; // Pinky Key
byte K9 = touchKeys[K9Pin]; // LH Key 1
byte K10 = touchKeys[K10Pin]; // LH Key 2
byte K11 = touchKeys[K11Pin]; // LH Key 3
byte K12 = touchKeys[K12Pin]; // LH Key 4
instrument.pinkyKey = touchKeys[K8Pin];
instrument.pinkyKey = K8;
int qTransp = (instrument.pinkyKey && (state.currentPreset->pinkySetting < 25)) ? state.currentPreset->pinkySetting - 12 : 0;
int qTransp = (K8 && (state.currentPreset->pinkySetting < 25)) ? state.currentPreset->pinkySetting - 12 : 0;
// Calculate midi note number from pressed keys
int fingeredNoteUntransposed = 0;
if (EVI == state.currentPreset->fingering) { // EVI fingering
fingeredNoteUntransposed = START_NOTE - 2 * K1 - K2 - 3 * K3 //"Trumpet valves"
switch (state.currentPreset->fingering) {
case EVI:
case EVR:
fingeredNoteUntransposed = START_NOTE
- 2 * K1
- K2
- 3 * K3 //"Trumpet valves"
- 5 * K4 // Fifth key
+ 2 * K5 + K6 + state.currentPreset->trill3_interval * K7; // Trill keys. 3rd trill key interval controlled by setting
} 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 + 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 * K5
+ K6
+ state.currentPreset->trill3_interval * K7; // Trill keys. 3rd trill key interval controlled by setting
break;
case TPT:
fingeredNoteUntransposed = START_NOTE
- 2 * K1
- K2
- 3 * K3 //"Trumpet valves"
- 2 // Trumpet in B flat
+ 2 * K5 + K6 + state.currentPreset->trill3_interval * K7; // Trill keys. 3rd trill key interval controlled by setting
} else if (HRN == state.currentPreset->fingering) { // HRN fingering
fingeredNoteUntransposed = START_NOTE - 2 * K1 - K2 - 3 * K3 //"Trumpet valves"
+ 2 * K5
+ K6
+ state.currentPreset->trill3_interval * K7; // Trill keys. 3rd trill key interval controlled by setting
break;
case HRN:
fingeredNoteUntransposed = START_NOTE
- 2 * K1
- K2
- 3 * K3 //"Trumpet valves"
+ 5 * K4 // Switch to Bb horn
+ 5 // Horn in F
+ 2 * K5 + K6 + state.currentPreset->trill3_interval * K7; // Trill keys. 3rd trill key interval controlled by setting
+ 2 * K5
+ K6
+ state.currentPreset->trill3_interval * K7; // Trill keys. 3rd trill key interval controlled by setting
break;
}
if (K3 && K7) {
@ -713,9 +722,31 @@ int readSwitches() {
fingeredNoteUntransposed += 4;
}
int fingeredNoteRead = fingeredNoteUntransposed + instrument.transpose - 12 + qTransp;
fingeredNote2Untransposed =
- 2 * K9
- K10
- 3 * K11
- 7 * K12;
if (fingeredNoteRead != lastFingering) { //
switch (state.currentPreset->polyMode) {
case EHarmonizerOff: // LH affects main instrument
fingeredNoteUntransposed += fingeredNote2Untransposed;
fingeredNote2Untransposed = fingeredNoteUntransposed;
break;
case EDuo: // Duo (absolute) mode
fingeredNote2Untransposed = START_NOTE + fingeredNote2Untransposed;
break;
case EDouble: // Double-stop (relative) mode
fingeredNote2Untransposed = fingeredNoteUntransposed + fingeredNote2Untransposed;
break;
}
int fingeredNoteRead = fingeredNoteUntransposed + instrument.transpose - 12 + qTransp;
int fingeredNote2Read = fingeredNote2Untransposed + instrument.transpose - 12 + qTransp;
int fingeredOctave = readOctave();
if (fingeredOctave != lastOctave || fingeredNoteRead != lastFingering || fingeredNote2Read != lastFingering2) { //
// reset the debouncing timer
lastDeglitchTime = millis();
}
@ -723,12 +754,14 @@ int readSwitches() {
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;
instrument.fingeredNote = noteValueCheck(fingeredNoteRead + fingeredOctave);
instrument.fingeredNote2 = noteValueCheck(fingeredNote2Read + fingeredOctave);
instrument.fingeredOctave = fingeredOctave;
}
lastFingering = fingeredNoteRead;
return fingeredNote;
lastFingering2 = fingeredNote2Read;
lastOctave = fingeredOctave;
}
void noteOn(int fingeredNote, int pressureSensor, int initial_breath_value) {
@ -744,8 +777,39 @@ void noteOn(int fingeredNote, int pressureSensor, int initial_breath_value) {
velocitySend = state.currentPreset->fixedVelocity;
}
breath(); // send breath data
midiSendNoteOn(fingeredNote, velocitySend); // send Note On message for new note
midiSendNoteOn(fingeredNote, velocitySend); // send Note Off message
}
/**
* Send a new note message if it has changed
*/
void changeNote(int fingeredNote, int activeNote, int fingeredNote2, int activeNote2) {
if (fingeredNote == activeNote && fingeredNote2 == activeNote2) {
// No change
return;
}
// Player has moved to a new fingering while still blowing.
// Send a note off for the current note and a note on for
// the new note.
if (fingeredNote != activeNote && fingeredNote != activeNote2) {
noteOn(fingeredNote, instrument.breathSignal, 0);
}
if (fingeredNote2 != activeNote && fingeredNote2 != activeNote2) {
noteOn(fingeredNote2, instrument.breathSignal, 0);
}
delayMicroseconds(2000); // delay for midi recording fix
// send Note Off messages
if (activeNote != fingeredNote && activeNote != fingeredNote2) {
midiSendNoteOff(activeNote);
}
if (activeNote2 != fingeredNote && activeNote2 != fingeredNote2) {
midiSendNoteOff(activeNote2);
}
}
void handleOffStateActions() {
@ -825,6 +889,7 @@ void handleCCs() {
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 breathAltSignal = constrain(readAltPressure(), 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),
@ -840,20 +905,24 @@ void readBreath() {
} 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
instrument.breathAltSignal = breathAltSignal;
Serial.print(">breathThr:");
Serial.println(instrument.breathThrVal);
if (instrument.mode == MODE_DEBUG) {
Serial.print(">breath:");
Serial.println(breathSignal);
Serial.print(">breathAlt:");
Serial.println(breathAltSignal);
Serial.print(">Diff:");
Serial.println(breathSignal - breathAltSignal);
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);
}
}
/**
@ -862,7 +931,6 @@ void readBreath() {
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
int fingeredNote = noteValueCheck(readSwitches() + readOctave());
if (state.mainState == NOTE_OFF) {
handleOffStateActions();
if (instrument.breathSignal > instrument.breathMovingThrVal && state.mainState == NOTE_OFF) {
@ -876,9 +944,14 @@ void runStateMachine() {
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);
noteOn(instrument.fingeredNote, instrument.breathSignal, initial_breath_value);
if (instrument.fingeredNote != instrument.fingeredNote2) {
noteOn(instrument.fingeredNote2, instrument.breathSignal, initial_breath_value);
}
breath(); // send breath data
state.mainState = NOTE_ON;
instrument.activeNote = fingeredNote;
instrument.activeNote = instrument.fingeredNote;
instrument.activeNote2 = instrument.fingeredNote2;
}
} else {
// Value fell below threshold before velocity sample delay time passed. Return to NOTE_OFF state
@ -888,38 +961,30 @@ void runStateMachine() {
if (instrument.breathSignal < instrument.breathMovingThrVal) {
// Value has fallen below threshold - turn the note off
midiSendNoteOff(instrument.activeNote); // send Note Off message
if (instrument.activeNote != instrument.activeNote2) {
midiSendNoteOff(instrument.activeNote2); // send Note Off message
}
instrument.breathSignal = 0;
state.mainState = NOTE_OFF;
} else {
if (fingeredNote != instrument.activeNote) {
// Player has moved to a new fingering while still blowing.
// Send a note off for the current note and a note on for
// the new note.
noteOn(fingeredNote, instrument.breathSignal, 0);
delayMicroseconds(2000); // delay for midi recording fix
midiSendNoteOff(instrument.activeNote); // send Note Off message
instrument.activeNote = fingeredNote;
}
changeNote(instrument.fingeredNote, instrument.activeNote, instrument.fingeredNote2, instrument.activeNote2);
instrument.activeNote = instrument.fingeredNote;
instrument.activeNote2 = instrument.fingeredNote2;
}
}
}
void setup() {
if (checkButtonState(DEBUG_CONFIG)) {
}
if (CrashReport) {
Serial.begin(9600); // debug
Serial.println("Debug Startup");
if (CrashReport) {
while (!Serial); // wait for serial monitor open
Serial.print(CrashReport);
}
initHardware();
delay(100); // Make sure the inputs settle
Serial.println(buttonState());
bool factoryReset = checkButtonState(STARTUP_FACTORY_RESET);
configManagementMode = checkButtonState(STARTUP_CONFIG);
testMode = checkButtonState(TEST_CONFIG);
initDisplay(); // Start up display and show logo
@ -927,12 +992,6 @@ void setup() {
displayError("CRASH WARNING");
}
// If going into config management mode, stop here before we even touch the EEPROM.
if (configManagementMode) {
configModeSetup();
return;
}
// Read eeprom data into global vars
readEEPROM(factoryReset, *state.calibration);
updateFilters(*state.currentPreset);
@ -967,18 +1026,18 @@ void loop() {
lastUpdate = time;
// If in config mgmt loop, do that and nothing else
if (configManagementMode) {
configModeLoop();
if (instrument.mode == MODE_CONFIG) {
configModeLoop(state);
return;
}
if (testMode) {
handleTestMode();
if (instrument.mode == MODE_TEST) {
handleTestMode(state);
return;
}
readBreath();
readSwitches();
runStateMachine();
handleCCs();