Refactored for teensy 4.0, xEvi hardware
- Switched to platformio, ino -> cpp - MPRLS for pressure sensor - Added basic ICM support - Removed widi, battery, other features not supported in xEvi - Removed legacy options/processing - Added LED strip support - Added encoder support - Reworked menu code to use encoders/be more flexible
This commit is contained in:
parent
c58c3f9e46
commit
01d193c9b3
92 changed files with 69119 additions and 73272 deletions
5
NuEVI/.gitignore
vendored
Normal file
5
NuEVI/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
10
NuEVI/.vscode/extensions.json
vendored
Normal file
10
NuEVI/.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"platformio.platformio-ide"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"ms-vscode.cpptools-extension-pack"
|
||||
]
|
||||
}
|
9
NuEVI/.vscode/settings.json
vendored
Normal file
9
NuEVI/.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"files.associations": {
|
||||
"type_traits": "cpp",
|
||||
"*.tcc": "cpp",
|
||||
"ostream": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"array": "cpp"
|
||||
}
|
||||
}
|
2356
NuEVI/NuEVI.ino
2356
NuEVI/NuEVI.ino
File diff suppressed because it is too large
Load diff
|
@ -1,593 +0,0 @@
|
|||
#include <Arduino.h>
|
||||
#include <Adafruit_SSD1306.h>
|
||||
#include <Adafruit_MPR121.h>
|
||||
|
||||
#include "menu.h"
|
||||
#include "numenu.h"
|
||||
#include "globals.h"
|
||||
#include "config.h"
|
||||
#include "hardware.h"
|
||||
#include "settings.h"
|
||||
|
||||
//***********************************************************
|
||||
|
||||
extern Adafruit_SSD1306 display;
|
||||
#if defined(NURAD)
|
||||
extern Adafruit_MPR121 touchSensorRH;
|
||||
extern Adafruit_MPR121 touchSensorLH;
|
||||
extern Adafruit_MPR121 touchSensorRollers;
|
||||
#else
|
||||
extern Adafruit_MPR121 touchSensor;
|
||||
#endif
|
||||
extern byte cursorNow;
|
||||
#if defined(NURAD)
|
||||
extern int calOffsetRollers[6];
|
||||
extern int calOffsetRH[12];
|
||||
extern int calOffsetLH[12];
|
||||
#endif
|
||||
|
||||
//***********************************************************
|
||||
// Variables used for the adjust menu
|
||||
static byte forcePix;
|
||||
|
||||
static uint16_t pos1;
|
||||
static uint16_t pos2;
|
||||
|
||||
static int16_t adjustOption;
|
||||
static int16_t adjustCurrent;
|
||||
|
||||
static int16_t sensorPixelPos1 = -1;
|
||||
static int16_t sensorPixelPos2 = -1;
|
||||
|
||||
static bool refreshScreen;
|
||||
|
||||
//***********************************************************
|
||||
|
||||
static void breathSave(const AdjustMenuEntry& e) {
|
||||
writeSetting(BREATH_THR_ADDR, *e.entries[0].value);
|
||||
writeSetting(BREATH_MAX_ADDR, *e.entries[1].value);
|
||||
}
|
||||
|
||||
const AdjustMenuEntry breathAdjustMenu = {
|
||||
"BREATH",
|
||||
{
|
||||
{ &breathThrVal, breathLoLimit, breathHiLimit },
|
||||
{ &breathMaxVal, breathLoLimit, breathHiLimit }
|
||||
},
|
||||
breathSave
|
||||
};
|
||||
|
||||
static void portamentoSave(const AdjustMenuEntry& e) {
|
||||
writeSetting(PORTAM_THR_ADDR, *e.entries[0].value);
|
||||
writeSetting(PORTAM_MAX_ADDR, *e.entries[1].value);
|
||||
}
|
||||
|
||||
const AdjustMenuEntry portamentoAdjustMenu = {
|
||||
"BITE",
|
||||
{
|
||||
{ &portamThrVal, portamLoLimit, portamHiLimit },
|
||||
{ &portamMaxVal, portamLoLimit, portamHiLimit }
|
||||
},
|
||||
portamentoSave
|
||||
};
|
||||
|
||||
static void pbSave(const AdjustMenuEntry& e) {
|
||||
writeSetting(PITCHB_THR_ADDR, *e.entries[0].value);
|
||||
writeSetting(PITCHB_MAX_ADDR, *e.entries[1].value);
|
||||
}
|
||||
|
||||
const AdjustMenuEntry pitchBendAdjustMenu = {
|
||||
"BEND",
|
||||
{
|
||||
{ &pitchbThrVal, pitchbLoLimit, pitchbHiLimit },
|
||||
{ &pitchbMaxVal, pitchbLoLimit, pitchbHiLimit }
|
||||
},
|
||||
pbSave
|
||||
};
|
||||
|
||||
static void extracSave(const AdjustMenuEntry& e) {
|
||||
writeSetting(EXTRAC_THR_ADDR, *e.entries[0].value);
|
||||
writeSetting(EXTRAC_MAX_ADDR, *e.entries[1].value);
|
||||
}
|
||||
|
||||
const AdjustMenuEntry extraSensorAdjustMenu = {
|
||||
"LIP/EC",
|
||||
{
|
||||
{ &extracThrVal, extracLoLimit, extracHiLimit },
|
||||
{ &extracMaxVal, extracLoLimit, extracHiLimit }
|
||||
},
|
||||
extracSave
|
||||
};
|
||||
|
||||
|
||||
static void ctouchThrSave(const AdjustMenuEntry& e) {
|
||||
writeSetting(CTOUCH_THR_ADDR, *e.entries[0].value);
|
||||
}
|
||||
|
||||
const AdjustMenuEntry ctouchAdjustMenu = {
|
||||
"TOUCH",
|
||||
{
|
||||
{ &ctouchThrVal, ctouchLoLimit, ctouchHiLimit },
|
||||
{ nullptr, 0, 0 }
|
||||
},
|
||||
ctouchThrSave
|
||||
};
|
||||
|
||||
|
||||
static void leverSave(const AdjustMenuEntry& e) {
|
||||
writeSetting(LEVER_THR_ADDR, *e.entries[0].value);
|
||||
writeSetting(LEVER_MAX_ADDR, *e.entries[1].value);
|
||||
}
|
||||
|
||||
const AdjustMenuEntry leverAdjustMenu = {
|
||||
"LEVER",
|
||||
{
|
||||
{ &leverThrVal, leverLoLimit, leverHiLimit },
|
||||
{ &leverMaxVal, leverLoLimit, leverHiLimit }
|
||||
},
|
||||
leverSave
|
||||
};
|
||||
|
||||
|
||||
const AdjustMenuEntry* adjustMenuEntries[] = {
|
||||
&breathAdjustMenu,
|
||||
&portamentoAdjustMenu,
|
||||
&pitchBendAdjustMenu,
|
||||
&extraSensorAdjustMenu,
|
||||
&ctouchAdjustMenu,
|
||||
&leverAdjustMenu,
|
||||
};
|
||||
|
||||
static const int numAdjustEntries = ARR_LEN(adjustMenuEntries);
|
||||
|
||||
//***********************************************************
|
||||
|
||||
|
||||
|
||||
void autoCalSelected() {
|
||||
int calRead;
|
||||
int calReadNext;
|
||||
// NuRAD/NuEVI sensor calibration
|
||||
// Extra Controller
|
||||
if(adjustOption == 3) {
|
||||
calRead = touchRead(extraPin);
|
||||
extracThrVal = constrain(calRead+200, extracLoLimit, extracHiLimit);
|
||||
extracMaxVal = constrain(extracThrVal+600, extracLoLimit, extracHiLimit);
|
||||
writeSetting(EXTRAC_THR_ADDR, extracThrVal);
|
||||
writeSetting(EXTRAC_MAX_ADDR, extracMaxVal);
|
||||
}
|
||||
// Breath sensor
|
||||
if(adjustOption == 0) {
|
||||
calRead = analogRead(breathSensorPin);
|
||||
breathThrVal = constrain(calRead+200, breathLoLimit, breathHiLimit);
|
||||
breathMaxVal = constrain(breathThrVal+1500, breathLoLimit, breathHiLimit);
|
||||
writeSetting(BREATH_THR_ADDR, breathThrVal);
|
||||
writeSetting(BREATH_MAX_ADDR, breathMaxVal);
|
||||
}
|
||||
// Pitch Bend
|
||||
if(adjustOption == 2) {
|
||||
calRead = touchRead(pbUpPin);
|
||||
calReadNext = touchRead(pbDnPin);
|
||||
if (calReadNext > calRead) calRead = calReadNext; //use highest value
|
||||
pitchbThrVal = constrain(calRead+200, pitchbLoLimit, pitchbHiLimit);
|
||||
pitchbMaxVal = constrain(pitchbThrVal+800, pitchbLoLimit, pitchbHiLimit);
|
||||
writeSetting(PITCHB_THR_ADDR, pitchbThrVal);
|
||||
writeSetting(PITCHB_MAX_ADDR, pitchbMaxVal);
|
||||
}
|
||||
// Lever
|
||||
if(adjustOption == 5) {
|
||||
#if defined(SEAMUS)
|
||||
calRead = touchRead(vibratoPin);
|
||||
#else
|
||||
calRead = 3000-touchRead(vibratoPin);
|
||||
#endif
|
||||
leverThrVal = constrain(calRead+60, leverLoLimit, leverHiLimit);
|
||||
leverMaxVal = constrain(calRead+120, leverLoLimit, leverHiLimit);
|
||||
writeSetting(LEVER_THR_ADDR, leverThrVal);
|
||||
writeSetting(LEVER_MAX_ADDR, leverMaxVal);
|
||||
}
|
||||
#if defined(NURAD) // NuRAD sensor calibration
|
||||
// Bite Pressure sensor
|
||||
if(adjustOption == 1) {
|
||||
calRead = analogRead(bitePressurePin);
|
||||
portamThrVal = constrain(calRead+300, portamLoLimit, portamHiLimit);
|
||||
portamMaxVal = constrain(portamThrVal+600, portamLoLimit, portamHiLimit);
|
||||
writeSetting(PORTAM_THR_ADDR, portamThrVal);
|
||||
writeSetting(PORTAM_MAX_ADDR, portamMaxVal);
|
||||
}
|
||||
// Touch sensors
|
||||
if(adjustOption == 4) {
|
||||
calRead = ctouchHiLimit;
|
||||
for (byte i = 0; i < 6; i++) {
|
||||
calReadNext = touchSensorRollers.filteredData(i) * (300-calOffsetRollers[i])/300;
|
||||
if (calReadNext < calRead) calRead = calReadNext; //use lowest value
|
||||
}
|
||||
for (byte i = 0; i < 12; i++) {
|
||||
calReadNext = touchSensorRH.filteredData(i) * (300-calOffsetRH[i])/300;
|
||||
if (calReadNext < calRead) calRead = calReadNext; //use lowest value
|
||||
}
|
||||
for (byte i = 0; i < 12; i++) {
|
||||
calReadNext = touchSensorLH.filteredData(i) * (300-calOffsetLH[i])/300;
|
||||
if (calReadNext < calRead) calRead = calReadNext; //use lowest value
|
||||
}
|
||||
ctouchThrVal = constrain(calRead-20, ctouchLoLimit, ctouchHiLimit);
|
||||
touch_Thr = map(ctouchThrVal,ctouchHiLimit,ctouchLoLimit,ttouchLoLimit,ttouchHiLimit);
|
||||
writeSetting(CTOUCH_THR_ADDR, ctouchThrVal);
|
||||
}
|
||||
#else // NuEVI sensor calibration
|
||||
// Bite sensor
|
||||
if(adjustOption == 1) {
|
||||
if (digitalRead(biteJumperPin)){ //PBITE (if pulled low with jumper, pressure sensor is used instead of capacitive bite sensing)
|
||||
// Capacitive sensor
|
||||
calRead = touchRead(bitePin);
|
||||
portamThrVal = constrain(calRead+200, portamLoLimit, portamHiLimit);
|
||||
portamMaxVal = constrain(portamThrVal+600, portamLoLimit, portamHiLimit);
|
||||
writeSetting(PORTAM_THR_ADDR, portamThrVal);
|
||||
writeSetting(PORTAM_MAX_ADDR, portamMaxVal);
|
||||
} else {
|
||||
// Pressure sensor
|
||||
calRead = analogRead(bitePressurePin);
|
||||
portamThrVal = constrain(calRead+300, portamLoLimit, portamHiLimit);
|
||||
portamMaxVal = constrain(portamThrVal+600, portamLoLimit, portamHiLimit);
|
||||
writeSetting(PORTAM_THR_ADDR, portamThrVal);
|
||||
writeSetting(PORTAM_MAX_ADDR, portamMaxVal);
|
||||
}
|
||||
}
|
||||
// Touch sensors
|
||||
if(adjustOption == 4) {
|
||||
calRead = ctouchHiLimit;
|
||||
for (byte i = 0; i < 12; i++) {
|
||||
calReadNext = touchSensor.filteredData(i);
|
||||
if (calReadNext < calRead) calRead = calReadNext; //use lowest value
|
||||
}
|
||||
calReadNext=map(touchRead(halfPitchBendKeyPin),ttouchLoLimit,ttouchHiLimit,ctouchHiLimit,ctouchLoLimit);
|
||||
if (calReadNext < calRead) calRead = calReadNext; //use lowest value
|
||||
calReadNext=map(touchRead(specialKeyPin),ttouchLoLimit,ttouchHiLimit,ctouchHiLimit,ctouchLoLimit);
|
||||
if (calReadNext < calRead) calRead = calReadNext; //use lowest value
|
||||
ctouchThrVal = constrain(calRead-20, ctouchLoLimit, ctouchHiLimit);
|
||||
touch_Thr = map(ctouchThrVal,ctouchHiLimit,ctouchLoLimit,ttouchLoLimit,ttouchHiLimit);
|
||||
writeSetting(CTOUCH_THR_ADDR, ctouchThrVal);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
//***********************************************************
|
||||
|
||||
static void drawAdjCursor(byte color) {
|
||||
display.drawTriangle(16,4,20,4,18,1,color);
|
||||
display.drawTriangle(16,6,20,6,18,9,color);
|
||||
}
|
||||
|
||||
static void drawAdjustFrame(int line) {
|
||||
display.drawLine(25,line,120,line,WHITE); // Top line
|
||||
display.drawLine(25,line+12,120,line+12,WHITE); // Bottom line
|
||||
|
||||
display.drawLine(25,line+1,25,line+2,WHITE);
|
||||
display.drawLine(120,line+1,120,line+2,WHITE);
|
||||
|
||||
display.drawLine(120,line+10,120,line+11,WHITE);
|
||||
display.drawLine(25,line+10,25,line+11,WHITE);
|
||||
}
|
||||
|
||||
static void drawAdjustBase(const char* title, bool all) {
|
||||
display.clearDisplay();
|
||||
|
||||
drawAdjustFrame(17);
|
||||
|
||||
// sensor marker lines.
|
||||
display.drawLine(25,36,25,40,WHITE);
|
||||
display.drawLine(120,36,120,40,WHITE);
|
||||
|
||||
display.setTextSize(1);
|
||||
display.setCursor(25,2);
|
||||
display.println(title);
|
||||
|
||||
display.setCursor(0,20);
|
||||
display.println("THR");
|
||||
display.setCursor(0,35);
|
||||
display.println("SNS");
|
||||
|
||||
|
||||
if(all) {
|
||||
drawAdjustFrame(47);
|
||||
display.setCursor(0,50);
|
||||
display.println("MAX");
|
||||
}
|
||||
cursorNow = WHITE;
|
||||
drawAdjCursor(WHITE);
|
||||
}
|
||||
|
||||
static void drawLineCursor(uint16_t hPos, uint16_t vPos, int color) {
|
||||
display.drawLine(hPos, vPos,hPos, vPos+6, color);
|
||||
}
|
||||
|
||||
static bool updateAdjustLineCursor(uint32_t timeNow, uint16_t hPos, uint16_t vPos ) {
|
||||
if ((timeNow - cursorBlinkTime) > cursorBlinkInterval) {
|
||||
if (cursorNow == WHITE) cursorNow = BLACK; else cursorNow = WHITE;
|
||||
drawLineCursor(hPos, vPos, cursorNow);
|
||||
cursorBlinkTime = timeNow;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void drawAdjustMenu(const AdjustMenuEntry *menu) {
|
||||
|
||||
bool haveSecondValue = menu->entries[1].value != nullptr;
|
||||
drawAdjustBase( menu->title, haveSecondValue );
|
||||
|
||||
pos1 = map( *menu->entries[0].value, menu->entries[0].limitLow, menu->entries[0].limitHigh, 27, 119);
|
||||
display.drawLine( pos1, 20, pos1, 26, WHITE );
|
||||
|
||||
if(haveSecondValue) {
|
||||
pos2 = map( *menu->entries[1].value, menu->entries[1].limitLow, menu->entries[1].limitHigh, 27, 119);
|
||||
display.drawLine( pos2, 50, pos2, 56, WHITE );
|
||||
}
|
||||
display.fillRect(64,0,64,9,BLACK);
|
||||
display.setTextSize(1);
|
||||
if(haveSecondValue) {
|
||||
display.setCursor(68,2);
|
||||
display.print(*menu->entries[0].value);
|
||||
display.print("|");
|
||||
display.print(*menu->entries[1].value);
|
||||
} else {
|
||||
display.setCursor(104,2);
|
||||
display.print(*menu->entries[0].value);
|
||||
}
|
||||
}
|
||||
|
||||
//***********************************************************
|
||||
|
||||
static bool updateSensorPixel(int pos, int pos2) {
|
||||
bool update = pos != sensorPixelPos1 || pos2 != sensorPixelPos2;
|
||||
if(update) {
|
||||
display.drawFastHLine(28, 38, 119-28, BLACK); // Clear old line
|
||||
display.drawPixel(pos, 38, WHITE);
|
||||
if( pos2 >= 0) display.drawPixel(pos2, 38, WHITE);
|
||||
|
||||
sensorPixelPos1 = pos;
|
||||
sensorPixelPos2 = pos2;
|
||||
}
|
||||
return update;
|
||||
}
|
||||
|
||||
void plotSensorPixels(){
|
||||
int redraw = 0;
|
||||
|
||||
if(forcePix)
|
||||
sensorPixelPos1 = -1;
|
||||
|
||||
// This is hacky. It depends on the order of items in the adjust menu list.
|
||||
if(adjustOption == 0) {
|
||||
int pos = map(constrain(pressureSensor, breathLoLimit, breathHiLimit), breathLoLimit, breathHiLimit, 28, 118);
|
||||
redraw = updateSensorPixel(pos, -1);
|
||||
}
|
||||
else if(adjustOption == 1) {
|
||||
if (biteJumper) { //PBITE (if pulled low with jumper or if on a NuRAD, use pressure sensor instead of capacitive bite sensor)
|
||||
biteSensor=analogRead(bitePressurePin); // alternative kind bite sensor (air pressure tube and sensor) PBITE
|
||||
} else {
|
||||
biteSensor = touchRead(bitePin); // get sensor data, do some smoothing - SENSOR PIN 17 - PCB PINS LABELED "BITE" (GND left, sensor pin right)
|
||||
}
|
||||
int pos = map(constrain(biteSensor,portamLoLimit,portamHiLimit), portamLoLimit, portamHiLimit, 28, 118);
|
||||
redraw = updateSensorPixel(pos, -1);
|
||||
}
|
||||
else if(adjustOption == 2) {
|
||||
int pos = map(constrain(pbUp, pitchbLoLimit, pitchbHiLimit), pitchbLoLimit, pitchbHiLimit, 28, 118);
|
||||
int pos2 = map(constrain(pbDn, pitchbLoLimit, pitchbHiLimit), pitchbLoLimit, pitchbHiLimit, 28, 118);
|
||||
redraw = updateSensorPixel(pos, pos2);
|
||||
}
|
||||
else if(adjustOption == 3) {
|
||||
int pos = map(constrain(exSensor, extracLoLimit, extracHiLimit), extracLoLimit, extracHiLimit, 28, 118);
|
||||
redraw = updateSensorPixel(pos, -1);
|
||||
}
|
||||
#if defined(NURAD)
|
||||
else if(adjustOption == 4) {
|
||||
display.drawLine(28,37,118,37,BLACK);
|
||||
for (byte i=0; i<12; i++){
|
||||
//int pos = map(constrain(touchSensorRH.filteredData(i) - calOffsetRH[i], ctouchLoLimit, ctouchHiLimit), ctouchLoLimit, ctouchHiLimit, 28, 118);
|
||||
int pos = map(constrain(touchSensorRH.filteredData(i) * (300-calOffsetRH[i])/300, ctouchLoLimit, ctouchHiLimit), ctouchLoLimit, ctouchHiLimit, 28, 118);
|
||||
display.drawPixel(pos, 37, WHITE);
|
||||
}
|
||||
display.drawLine(28,38,118,38,BLACK);
|
||||
for (byte i=0; i<12; i++){
|
||||
//int pos = map(constrain(touchSensorLH.filteredData(i) - calOffsetLH[i], ctouchLoLimit, ctouchHiLimit), ctouchLoLimit, ctouchHiLimit, 28, 118);
|
||||
int pos = map(constrain(touchSensorLH.filteredData(i) * (300-calOffsetLH[i])/300, ctouchLoLimit, ctouchHiLimit), ctouchLoLimit, ctouchHiLimit, 28, 118);
|
||||
display.drawPixel(pos, 38, WHITE);
|
||||
}
|
||||
display.drawLine(28,39,118,39,BLACK);
|
||||
for (byte i=0; i<6; i++){
|
||||
//int pos = map(constrain(touchSensorRollers.filteredData(i) - calOffsetRollers[i], ctouchLoLimit, ctouchHiLimit), ctouchLoLimit, ctouchHiLimit, 28, 118);
|
||||
int pos = map(constrain(touchSensorRollers.filteredData(i) * (300-calOffsetRollers[i])/300, ctouchLoLimit, ctouchHiLimit), ctouchLoLimit, ctouchHiLimit, 28, 118);
|
||||
display.drawPixel(pos, 39, WHITE);
|
||||
}
|
||||
redraw = 1;
|
||||
}
|
||||
#else //NuEVI
|
||||
else if(adjustOption == 4) {
|
||||
display.drawLine(28,39,118,39,BLACK);
|
||||
for (byte i=0; i<12; i++){
|
||||
int pos = map(constrain(touchSensor.filteredData(i), ctouchLoLimit, ctouchHiLimit), ctouchLoLimit, ctouchHiLimit, 28, 118);
|
||||
display.drawPixel(pos, 39, WHITE);
|
||||
}
|
||||
|
||||
int posRead = map(touchRead(halfPitchBendKeyPin),ttouchLoLimit,ttouchHiLimit,ctouchHiLimit,ctouchLoLimit);
|
||||
int pos = map(constrain(posRead, ctouchLoLimit, ctouchHiLimit), ctouchLoLimit, ctouchHiLimit, 28, 118);
|
||||
|
||||
posRead = map(touchRead(specialKeyPin),ttouchLoLimit,ttouchHiLimit,ctouchHiLimit,ctouchLoLimit);
|
||||
int pos2 = map(constrain(posRead, ctouchLoLimit, ctouchHiLimit), ctouchLoLimit, ctouchHiLimit, 28, 118);
|
||||
|
||||
updateSensorPixel(pos, pos2);
|
||||
|
||||
redraw = 1;
|
||||
}
|
||||
#endif
|
||||
else if(adjustOption == 5) {
|
||||
#if defined(SEAMUS)
|
||||
int pos = map(constrain(touchRead(vibratoPin), leverLoLimit, leverHiLimit), leverLoLimit, leverHiLimit, 28, 118);
|
||||
#else
|
||||
int pos = map(constrain(3000-touchRead(vibratoPin), leverLoLimit, leverHiLimit), leverLoLimit, leverHiLimit, 28, 118);
|
||||
#endif
|
||||
redraw = updateSensorPixel(pos, -1);
|
||||
}
|
||||
|
||||
|
||||
if (redraw){
|
||||
display.display();
|
||||
}
|
||||
forcePix = 0;
|
||||
}
|
||||
|
||||
//***********************************************************
|
||||
|
||||
static bool drawAdjustBar(uint16_t buttons, int row, const AdjustValue* entry, uint16_t *pos) {
|
||||
bool updated = false;
|
||||
uint16_t step = (entry->limitHigh-entry->limitLow)/92;
|
||||
int val = *entry->value;
|
||||
switch(buttons) {
|
||||
case BTN_UP:
|
||||
val += step;
|
||||
updated = true;
|
||||
break;
|
||||
|
||||
case BTN_DOWN:
|
||||
val -= step;
|
||||
updated = true;
|
||||
break;
|
||||
|
||||
}
|
||||
if(updated) {
|
||||
*entry->value = constrain(val, entry->limitLow, entry->limitHigh);
|
||||
auto p = *pos;
|
||||
display.drawLine(p, row, p, row+6, BLACK);
|
||||
*pos = p = map(*entry->value, entry->limitLow, entry->limitHigh, 27, 119);
|
||||
display.drawLine(p, row, p, row+6, WHITE);
|
||||
cursorNow = BLACK;
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
static bool updateAdjustCursor(uint32_t timeNow) {
|
||||
if ((timeNow - cursorBlinkTime) > cursorBlinkInterval) {
|
||||
if (cursorNow == WHITE) cursorNow = BLACK; else cursorNow = WHITE;
|
||||
drawAdjCursor(cursorNow);
|
||||
cursorBlinkTime = timeNow;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool handleInput(const AdjustMenuEntry *currentMenu, uint32_t timeNow, uint8_t buttons, uint16_t *xpos, int ypos, int index) {
|
||||
bool haveSecondValue = currentMenu->entries[1].value != nullptr;
|
||||
if (buttons) {
|
||||
if (buttons == BTN_DOWN+BTN_UP){
|
||||
display.fillRect(26,35,90,7,BLACK);
|
||||
display.setTextSize(1);
|
||||
display.setTextColor(WHITE);
|
||||
display.setCursor(53,35);
|
||||
display.println("AUTOCAL");
|
||||
display.display();
|
||||
delay(2000);
|
||||
autoCalSelected();
|
||||
display.fillRect(26,35,90,7,BLACK);
|
||||
display.display();
|
||||
drawAdjustMenu(currentMenu);
|
||||
forcePix = 1;
|
||||
plotSensorPixels();
|
||||
} else
|
||||
drawAdjustBar( buttons, ypos, ¤tMenu->entries[index], xpos );
|
||||
|
||||
display.fillRect(64,0,64,9,BLACK);
|
||||
display.setTextSize(1);
|
||||
if(haveSecondValue) {
|
||||
display.setCursor(68,2);
|
||||
display.print(*currentMenu->entries[0].value);
|
||||
display.print("|");
|
||||
display.print(*currentMenu->entries[1].value);
|
||||
} else {
|
||||
display.setCursor(104,2);
|
||||
display.print(*currentMenu->entries[0].value);
|
||||
}
|
||||
|
||||
int last = adjustCurrent;
|
||||
if(buttons == BTN_ENTER) adjustCurrent += 1;
|
||||
else if( buttons == BTN_MENU) adjustCurrent = 0;
|
||||
|
||||
if(last != adjustCurrent) drawLineCursor(*xpos, ypos, WHITE);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return updateAdjustLineCursor(timeNow, *xpos, ypos);
|
||||
}
|
||||
}
|
||||
|
||||
int updateAdjustMenu(uint32_t timeNow, KeyState &input, bool firstRun, bool drawSensor) {
|
||||
bool redraw = false;
|
||||
int result = 0;
|
||||
|
||||
const AdjustMenuEntry *currentMenu = adjustMenuEntries[adjustOption];
|
||||
|
||||
uint8_t buttons = input.changed ? input.current : 0;
|
||||
|
||||
if(firstRun || refreshScreen) {
|
||||
adjustCurrent = 0;
|
||||
refreshScreen = false;
|
||||
drawAdjustMenu(currentMenu);
|
||||
// the sensor pixel stuff should be refactored (to work again)
|
||||
forcePix = 1;
|
||||
sensorPixelPos1 = -1; // Force draw of sensor pixels
|
||||
}
|
||||
|
||||
if(adjustCurrent == 0) {
|
||||
// Currently selecting what option to modify
|
||||
redraw |= updateAdjustCursor(timeNow);
|
||||
|
||||
bool save = false;
|
||||
if( buttons == BTN_DOWN ) {
|
||||
adjustOption += 1;
|
||||
refreshScreen = 1;
|
||||
save = true;
|
||||
}
|
||||
else if ( buttons == BTN_UP ) {
|
||||
adjustOption -= 1;
|
||||
refreshScreen = 1;
|
||||
save = true;
|
||||
}
|
||||
else if ( buttons == BTN_ENTER ) {
|
||||
adjustCurrent = 1;
|
||||
}
|
||||
else if (buttons == BTN_MENU ) {
|
||||
adjustCurrent = 0;
|
||||
result = -1;
|
||||
save = true;
|
||||
}
|
||||
|
||||
if(save && currentMenu->saveFunc)
|
||||
currentMenu->saveFunc(*currentMenu);
|
||||
|
||||
} else if( adjustCurrent == 1) {
|
||||
redraw |= handleInput(currentMenu, timeNow, buttons, &pos1, 20, 0);
|
||||
} else {
|
||||
redraw |= handleInput(currentMenu, timeNow, buttons, &pos2, 50, 1);
|
||||
}
|
||||
|
||||
// Keep adjustCurrent in range
|
||||
if( (adjustCurrent > 2) || ((adjustCurrent == 2) && (currentMenu->entries[1].value == nullptr)))
|
||||
adjustCurrent = 0;
|
||||
|
||||
// Keep adjust option in range.
|
||||
if(adjustOption < 0)
|
||||
adjustOption = numAdjustEntries-1;
|
||||
else if (adjustOption >= numAdjustEntries)
|
||||
adjustOption = 0;
|
||||
|
||||
|
||||
if(drawSensor) {
|
||||
plotSensorPixels();
|
||||
}
|
||||
|
||||
if(result >= 0)
|
||||
result = redraw;
|
||||
|
||||
return result;
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
|
||||
#ifndef __CONFIG_H
|
||||
#define __CONFIG_H
|
||||
|
||||
|
||||
// Compile options, comment/uncomment to change
|
||||
|
||||
#define FIRMWARE_VERSION "1.6.0" // FIRMWARE VERSION NUMBER HERE <<<<<<<<<<<<<<<<<<<<<<<
|
||||
|
||||
#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 CCN_PortSE02 9 // Controller number for portamento type on Roland SE-02
|
||||
|
||||
// Send breath CC data no more than every CC_BREATH_INTERVAL
|
||||
// milliseconds
|
||||
#define CC_BREATH_INTERVAL 5
|
||||
#define SLOW_MIDI_ADD 7
|
||||
#define CC_INTERVAL 9
|
||||
#define CC_INTERVAL2 13
|
||||
#define CC_INTERVAL3 37
|
||||
#define LVL_TIMER_INTERVAL 15
|
||||
#define CVPORTATUNE 2
|
||||
|
||||
#define maxSamplesNum 120
|
||||
|
||||
#define breathLoLimit 0
|
||||
#define breathHiLimit 4095
|
||||
#define portamLoLimit 700
|
||||
#define portamHiLimit 4700
|
||||
#define pitchbLoLimit 500
|
||||
#define pitchbHiLimit 4000
|
||||
#define extracLoLimit 500
|
||||
#define extracHiLimit 4000
|
||||
#define ctouchLoLimit 50
|
||||
#define ctouchHiLimit 350
|
||||
#define ttouchLoLimit 50
|
||||
#define ttouchHiLimit 1900
|
||||
#define leverLoLimit 1400
|
||||
#define leverHiLimit 2000
|
||||
|
||||
|
||||
#define MIN_LED_BRIGHTNESS 5 // lowest PWM value that still is visible
|
||||
#define BREATH_LED_BRIGHTNESS 600 // up to 4095, PWM
|
||||
#define PORTAM_LED_BRIGHTNESS 300 // up to 4095, PWM
|
||||
#define EXTCON_LED_BRIGHTNESS 300 // up to 4095, PWM
|
||||
#define SPCKEY_LED_BRIGHTNESS 700 // up to 4095, PWM
|
||||
|
||||
#define ALK_BAT_FULL 2800 // about 4.6V
|
||||
#define NMH_BAT_FULL 2380 // about 3.9V
|
||||
#define LIP_BAT_FULL 2540 // about 4.2V
|
||||
#define ALK_BAT_LOW 2300 // about 3.8V
|
||||
#define NMH_BAT_LOW 2200 // about 3.6V
|
||||
#define LIP_BAT_LOW 2250 // about 3.7V
|
||||
|
||||
|
||||
#endif
|
234
NuEVI/globals.h
234
NuEVI/globals.h
|
@ -1,234 +0,0 @@
|
|||
#ifndef __GLOBALS_H
|
||||
#define __GLOBALS_H
|
||||
|
||||
#include "wiring.h"
|
||||
|
||||
|
||||
// The three states of our main state machine
|
||||
|
||||
// No note is sounding
|
||||
#define NOTE_OFF 1
|
||||
|
||||
// We've observed a transition from below to above the
|
||||
// threshold value. We wait a while to see how fast the
|
||||
// breath velocity is increasing
|
||||
#define RISE_WAIT 2
|
||||
|
||||
// A note is sounding
|
||||
#define NOTE_ON 3
|
||||
|
||||
|
||||
//Magic values
|
||||
#define PBD 12
|
||||
#define EC2 25
|
||||
#define ECSW 26
|
||||
#define LVL 27
|
||||
#define LVLP 28
|
||||
#define GLD 29
|
||||
#define ECH 30
|
||||
#define QTN 31
|
||||
|
||||
#define MOD 13
|
||||
|
||||
//Vibrato direction
|
||||
#define UPWD 1
|
||||
#define DNWD 0
|
||||
|
||||
|
||||
enum PolySelect : unsigned short {
|
||||
EHarmonizerOff = 0,
|
||||
ETriadMajorGospelRoot = 1, // MGR
|
||||
ETriadMajorGospelDominant = 2, // MGD
|
||||
EMajorAddNine = 3,
|
||||
EMinorDorian = 4,
|
||||
EMinorAeolian = 5,
|
||||
EMinorFourVoiceHip = 6,
|
||||
EFourWayCloseHarmonizer = 7,
|
||||
ERotatorA = 8,
|
||||
ERotatorB = 9,
|
||||
ERotatorC = 10,
|
||||
};
|
||||
|
||||
struct Rotator
|
||||
{
|
||||
uint16_t parallel; // Semitones
|
||||
uint16_t rotations[4];
|
||||
};
|
||||
|
||||
|
||||
extern const unsigned short* const curves[];
|
||||
extern const unsigned short curveIn[];
|
||||
|
||||
#if defined(NURAD)
|
||||
extern int calOffsetRollers[6];
|
||||
extern int calOffsetRH[12];
|
||||
extern int calOffsetLH[12];
|
||||
#endif
|
||||
|
||||
extern unsigned short breathThrVal;
|
||||
extern unsigned short breathMaxVal;
|
||||
extern unsigned short portamThrVal;
|
||||
extern unsigned short portamMaxVal;
|
||||
extern unsigned short pitchbThrVal;
|
||||
extern unsigned short pitchbMaxVal;
|
||||
extern unsigned short extracThrVal;
|
||||
extern unsigned short extracMaxVal;
|
||||
extern unsigned short ctouchThrVal;
|
||||
extern unsigned short leverThrVal;
|
||||
extern unsigned short leverMaxVal;
|
||||
extern unsigned short transpose;
|
||||
extern unsigned short MIDIchannel;
|
||||
extern unsigned short breathCC; // OFF:MW:BR:VL:EX:MW+:BR+:VL+:EX+:CF:UNO
|
||||
extern unsigned short breathCC2; // OFF:1-127
|
||||
extern unsigned short breathCC2Rise; // 1X:2X:3X:4X:5X
|
||||
extern unsigned short breathAT;
|
||||
extern unsigned short velocity;
|
||||
extern unsigned short portamento;// switching on cc65? just cc5 enabled? SW:ON:OFF
|
||||
extern unsigned short portLimit; // 1-127
|
||||
extern unsigned short PBdepth; // OFF:1-12 divider
|
||||
extern unsigned short extraCT; // OFF:MW:FP:CF:SP
|
||||
extern unsigned short vibrato; // OFF:1-9
|
||||
extern unsigned short deglitch; // 0-70 ms in steps of 5
|
||||
extern unsigned short patch; // 1-128
|
||||
extern unsigned short octave;
|
||||
extern unsigned short curve;
|
||||
extern unsigned short velSmpDl; // 0-30 ms
|
||||
extern unsigned short velBias; // 0-9
|
||||
extern unsigned short pinkySetting; // 0 - 11 (QuickTranspose -12 to -1), 12 (pb/2), 13 - 24 (QuickTranspose +1 to +12), 25 (EC2), 26 (ECSW), 27 (LVL), 28 (LVLP)
|
||||
extern unsigned short dipSwBits; // virtual dip switch settings for special modes (work in progress)
|
||||
extern unsigned short priority; // mono priority for rotator chords
|
||||
extern unsigned short vibSens; // vibrato sensitivity
|
||||
extern unsigned short vibRetn; // vibrato return speed
|
||||
extern unsigned short vibSquelch; //vibrato signal squelch
|
||||
extern unsigned short vibDirection; //direction of first vibrato wave UPWD or DNWD
|
||||
extern unsigned short vibSensBite; // vibrato sensitivity (bite)
|
||||
extern unsigned short vibSquelchBite; //vibrato signal squelch (bite)
|
||||
extern unsigned short vibControl;
|
||||
extern unsigned short fastPatch[7];
|
||||
extern unsigned short extraCT2; // OFF:1-127
|
||||
extern unsigned short levelCC; // 0-127
|
||||
extern unsigned short levelVal; // 0-127
|
||||
extern unsigned short fingering; // 0-4 EWI,EWX,SAX,EVI,EVR
|
||||
extern unsigned short rollerMode; //0-2
|
||||
extern unsigned short lpinky3; // 0-25 (OFF, -12 - MOD - +12)
|
||||
extern unsigned short batteryType; // 0-2 ALK,NIM,LIP
|
||||
extern unsigned short harmSetting; // 0-7
|
||||
extern unsigned short harmSelect; // 0-5
|
||||
extern unsigned short brHarmSetting; // 0-7
|
||||
extern unsigned short brHarmSelect; // 0-3
|
||||
extern PolySelect polySelect; // OFF, MGR, MGD, MND, MNH, FWC, RTA, RTB or RTC
|
||||
extern unsigned short fwcType; // 6, m6, 7, m7
|
||||
extern unsigned short fwcLockH; // OFF:ON
|
||||
extern unsigned short fwcDrop2; // OFF:ON
|
||||
extern unsigned short hmzKey; // 0-11 (0 is C)
|
||||
extern unsigned short hmzLimit; // 2-5
|
||||
extern unsigned short otfKey; //OFF:ON
|
||||
extern unsigned short breathInterval; // 3-15
|
||||
extern unsigned short biteControl; // OFF, VIB, GLD, CC
|
||||
extern unsigned short leverControl; // OFF, VIB, GLD, CC
|
||||
extern unsigned short biteCC; // 0 - 127
|
||||
extern unsigned short leverCC; // 0 -127
|
||||
extern unsigned short cvTune; // 1 - 199 representing -99 to +99 in menu (offset of 100 to keep postitive)
|
||||
extern unsigned short cvScale; // 1 - 199 representing -99 to +99 in menu (offset of 100 to keep postitive)
|
||||
extern unsigned short cvVibRate; // OFF, 1 - 8 CV extra controller LFO vibrato rate 4.5Hz to 8Hz
|
||||
extern uint16_t gateOpenEnable;
|
||||
extern uint16_t specialKeyEnable;
|
||||
extern byte rotatorOn;
|
||||
extern byte currentRotation;
|
||||
|
||||
extern Rotator rotations_a;
|
||||
extern Rotator rotations_b;
|
||||
extern Rotator rotations_c;
|
||||
|
||||
extern uint16_t bcasMode; //Legacy CASSIDY compile flag
|
||||
extern uint16_t trill3_interval;
|
||||
extern uint16_t fastBoot;
|
||||
extern uint16_t dacMode;
|
||||
|
||||
|
||||
extern int touch_Thr;
|
||||
|
||||
extern int leverPortZero;
|
||||
|
||||
extern unsigned long cursorBlinkTime; // the last time the cursor was toggled
|
||||
|
||||
extern byte activePatch;
|
||||
extern byte doPatchUpdate;
|
||||
|
||||
extern uint16_t legacy;
|
||||
extern uint16_t legacyBrAct;
|
||||
|
||||
extern byte widiOn;
|
||||
|
||||
extern int pressureSensor; // pressure data from breath sensor, for midi breath cc and breath threshold checks
|
||||
extern int lastPressure;
|
||||
|
||||
extern int biteSensor; // capacitance data from bite sensor, for midi cc and threshold checks
|
||||
extern int lastBite;
|
||||
extern byte biteJumper;
|
||||
extern byte widiJumper;
|
||||
extern int exSensor;
|
||||
extern int exSensorIndicator;
|
||||
|
||||
extern int pitchBend;
|
||||
extern int pbSend;
|
||||
|
||||
extern int pbUp;
|
||||
extern int pbDn;
|
||||
|
||||
extern byte vibLedOff;
|
||||
extern byte oldpkey;
|
||||
|
||||
extern int vibThr; // this gets auto calibrated in setup
|
||||
extern int vibThrLo;
|
||||
extern int vibZero;
|
||||
extern int vibZeroBite;
|
||||
extern int vibThrBite;
|
||||
extern int vibThrBiteLo;
|
||||
|
||||
extern int battAvg;
|
||||
|
||||
extern int breathLevel;
|
||||
extern byte portIsOn;
|
||||
extern int oldport;
|
||||
|
||||
#if defined(NURAD)
|
||||
// Key variables, TRUE (1) for pressed, FALSE (0) for not pressed
|
||||
extern byte LHs;
|
||||
extern byte LH1; // Left Hand key 1 (pitch change -2)
|
||||
extern byte LHb; // Left Hand bis key (pitch change -1 unless both LH1 and LH2 are pressed)
|
||||
extern byte LH2; // Left Hand key 2 (with LH1 also pressed pitch change is -2, otherwise -1)
|
||||
extern byte LH3; // Left Hand key 3 (pitch change -2)
|
||||
extern byte LHp1; // Left Hand pinky key 1 (pitch change +1)
|
||||
extern byte LHp2; // Left Hand pinky key 2 (pitch change -1)
|
||||
extern byte LHp3;
|
||||
extern byte RHs; // Right Hand side key (pitch change -2 unless LHp1 is pressed)
|
||||
extern byte RH1; // Right Hand key 1 (with LH3 also pressed pitch change is -2, otherwise -1)
|
||||
extern byte RH2; // Right Hand key 2 (pitch change -1)
|
||||
extern byte RH3; // Right Hand key 3 (pitch change -2)
|
||||
extern byte RHp1; // Right Hand pinky key 1 (pitch change +1)
|
||||
extern byte RHp2; // Right Hand pinky key 2 (pitch change -1)
|
||||
extern byte RHp3; // Right Hand pinky key 3 (pitch change -2)
|
||||
extern byte Tr1; // Trill key 1 (pitch change +2) (EVI fingering)
|
||||
extern byte Tr2; // Trill key 2 (pitch change +1)
|
||||
extern byte Tr3; // Trill key 3 (pitch change +4)
|
||||
#endif
|
||||
|
||||
// Key variables, TRUE (1) for pressed, FALSE (0) for not pressed
|
||||
extern byte K1; // Valve 1 (pitch change -2)
|
||||
extern byte K2; // Valve 2 (pitch change -1)
|
||||
extern byte K3; // Valve 3 (pitch change -3)
|
||||
extern byte K4; // Left Hand index finger (pitch change -5)
|
||||
extern byte K5; // Trill key 1 (pitch change +2)
|
||||
extern byte K6; // Trill key 2 (pitch change +1)
|
||||
extern byte K7; // Trill key 3 (pitch change +4)
|
||||
|
||||
extern byte halfPitchBendKey;
|
||||
extern byte quarterToneTrigger;
|
||||
extern byte specialKey;
|
||||
extern byte pinkyKey;
|
||||
extern byte patchKey;
|
||||
|
||||
extern unsigned int multiMap(unsigned short val, const unsigned short * _in, const unsigned short * _out, uint8_t size);
|
||||
|
||||
#endif
|
199
NuEVI/hardware.h
199
NuEVI/hardware.h
|
@ -1,199 +0,0 @@
|
|||
#ifndef __HARDWARE_H
|
||||
#define __HARDWARE_H
|
||||
|
||||
#define REVB
|
||||
//#define NURAD
|
||||
//#define SEAMUS
|
||||
|
||||
//#define I2CSCANNER
|
||||
|
||||
#if defined(NURAD) //NuRAD <<<<<<<<<<<<<<<<<<<<<<<
|
||||
|
||||
// Pin definitions
|
||||
|
||||
// Teensy pins
|
||||
|
||||
//Capacitive sensor pins (on-board teensy)
|
||||
#define bitePin 17
|
||||
#define extraPin 16
|
||||
#define pbUpPin 1
|
||||
#define pbDnPin 0
|
||||
#define vibratoPin 15
|
||||
|
||||
//Analog pressure sensors. Breath and optional bite
|
||||
#define breathSensorPin A0
|
||||
#define bitePressurePin A7
|
||||
|
||||
//Digital pins for menu buttons
|
||||
#define dPin 3
|
||||
#define ePin 4
|
||||
#define uPin 5
|
||||
#define mPin 6
|
||||
|
||||
//Output pins for LEDs (breath, power, status)
|
||||
#define bLedPin 10
|
||||
#define pLedPin 9
|
||||
#define eLedPin 22
|
||||
#define sLedPin 23
|
||||
#define statusLedPin 13
|
||||
|
||||
//Pins for WIDI board management
|
||||
#define widiJumperPin 28
|
||||
#define widiJumperGndPin 27
|
||||
#define widiPowerPin 33
|
||||
|
||||
//Analog input for measuring voltage
|
||||
#define vMeterPin A11
|
||||
|
||||
//DAC outputs for analog and pwm
|
||||
#define dacPin A14
|
||||
#define pwmDacPin A6
|
||||
|
||||
//Which serial port to use for MIDI
|
||||
#define MIDI_SERIAL Serial3
|
||||
#define WIDI_SERIAL Serial2
|
||||
|
||||
// MPR121 Rollers 0x5D
|
||||
|
||||
#define rPin1 0
|
||||
#define rPin2 1
|
||||
#define rPin3 2
|
||||
#define rPin4 3
|
||||
#define rPin5 4
|
||||
#define rPin6 5
|
||||
|
||||
// MPR121 RH 0x5C
|
||||
|
||||
#define RHsPin 3
|
||||
#define RH1Pin 4
|
||||
#define RH2Pin 2
|
||||
#define RH3Pin 1
|
||||
#define RHp1Pin 0
|
||||
#define RHp2Pin 8
|
||||
#define RHp3Pin 7
|
||||
#define spec1Pin 10
|
||||
#define spec2Pin 9
|
||||
#define patchPin 5
|
||||
|
||||
// MPR121 LH 0x5B
|
||||
|
||||
#define LHsPin 8
|
||||
#define LH1Pin 7
|
||||
#define LHbPin 1
|
||||
#define LH2Pin 9
|
||||
#define LH3Pin 10
|
||||
#define LHp1Pin 11
|
||||
#define LHp2Pin 3
|
||||
#define LHp3Pin 4
|
||||
|
||||
#else //NuEVI <<<<<<<<<<<<<<<<<<<<<<<
|
||||
|
||||
// Pin definitions
|
||||
|
||||
// Teensy pins
|
||||
|
||||
#define specialKeyPin 0 // SK or S2
|
||||
#define halfPitchBendKeyPin 1 // PD or S1
|
||||
|
||||
|
||||
//Capacitive sensor pins (on-board teensy)
|
||||
#define bitePin 17
|
||||
#define extraPin 16
|
||||
#define pbUpPin 23
|
||||
#define pbDnPin 22
|
||||
#define vibratoPin 15
|
||||
|
||||
//Pins jumpered to enable bite pressure sensor
|
||||
#define biteJumperPin 11
|
||||
#define biteJumperGndPin 12
|
||||
|
||||
//Pins for WIDI board management
|
||||
#define widiJumperPin 28
|
||||
#define widiJumperGndPin 27
|
||||
#define widiPowerPin 33
|
||||
|
||||
//Analog pressure sensors. Breath and optional bite
|
||||
#define breathSensorPin A0
|
||||
#define bitePressurePin A7
|
||||
|
||||
//Digital pins for menu buttons
|
||||
#define dPin 3
|
||||
#define ePin 4
|
||||
#define uPin 5
|
||||
#define mPin 6
|
||||
|
||||
//Output pins for LEDs (breath, power, status)
|
||||
#define bLedPin 10
|
||||
#define pLedPin 9
|
||||
#define statusLedPin 13
|
||||
|
||||
//Analog input for measuring voltage
|
||||
#define vMeterPin A11
|
||||
|
||||
//DAC outputs for analog and pwm
|
||||
#define dacPin A14
|
||||
#define pwmDacPin 20
|
||||
|
||||
//Which serial port to use for MIDI
|
||||
#define MIDI_SERIAL Serial3
|
||||
#define WIDI_SERIAL Serial2
|
||||
|
||||
#if defined(REVB)
|
||||
|
||||
// MPR121 pins Rev B (angled pins at top edge for main keys and rollers)
|
||||
|
||||
#define R1Pin 0
|
||||
#define R2Pin 2
|
||||
#define R3Pin 4
|
||||
#define R4Pin 6
|
||||
#define R5Pin 8
|
||||
|
||||
#define K4Pin 10
|
||||
#define K1Pin 1
|
||||
#define K2Pin 3
|
||||
#define K3Pin 5
|
||||
#define K5Pin 7
|
||||
#define K6Pin 9
|
||||
#define K7Pin 11
|
||||
|
||||
/*
|
||||
* PINOUT ON PCB vs PINS ON MPR121 - Rev. B
|
||||
*
|
||||
* (R1) (R2) (R3/6) (R4) (R5) (K4) <-> (00) (02) (04) (06) (08) (10)
|
||||
*
|
||||
* (K1) (K2) (K3) (K5) (K6) (K7) <-> (01) (03) (05) (07) (09) (11)
|
||||
*
|
||||
*/
|
||||
|
||||
# else //REV A
|
||||
|
||||
// MPR121 pins Rev A (upright pins below MPR121 for main keys and rollers)
|
||||
|
||||
#define R1Pin 10
|
||||
#define R2Pin 11
|
||||
#define R3Pin 8
|
||||
#define R4Pin 9
|
||||
#define R5Pin 6
|
||||
|
||||
#define K4Pin 7
|
||||
#define K1Pin 4
|
||||
#define K2Pin 5
|
||||
#define K3Pin 2
|
||||
#define K5Pin 3
|
||||
#define K6Pin 0
|
||||
#define K7Pin 1
|
||||
|
||||
/*
|
||||
* PINOUT ON PCB vs PINS ON MPR121 - Rev. A
|
||||
*
|
||||
* (R2) (R4) (K4) (K2) (K5) (K7) <-> (11) (09) (07) (05) (03) (01)
|
||||
*
|
||||
* (R1) (R3/6) (R5) (K1) (K3) (K6) <-> (10) (08) (06) (04) (02) (00)
|
||||
*
|
||||
*/
|
||||
|
||||
#endif //REVB
|
||||
#endif //NURAD
|
||||
|
||||
|
||||
#endif
|
|
@ -1,72 +0,0 @@
|
|||
#include <Arduino.h>
|
||||
#include "hardware.h"
|
||||
#include "globals.h"
|
||||
#include "config.h"
|
||||
|
||||
// Do things with status LED.
|
||||
void statusLedOn() {
|
||||
digitalWrite(statusLedPin, HIGH);
|
||||
#if defined(SEAMUS)
|
||||
analogWrite(sLedPin, SPCKEY_LED_BRIGHTNESS);
|
||||
#endif
|
||||
}
|
||||
|
||||
void statusLedOff() {
|
||||
digitalWrite(statusLedPin, LOW);
|
||||
#if defined(SEAMUS)
|
||||
analogWrite(sLedPin, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
void statusLed(bool state) {
|
||||
digitalWrite(statusLedPin, state);
|
||||
#if defined(SEAMUS)
|
||||
analogWrite(sLedPin, state*SPCKEY_LED_BRIGHTNESS);
|
||||
#endif
|
||||
}
|
||||
|
||||
void statusLedFlip() {
|
||||
digitalWrite(statusLedPin, !digitalRead(statusLedPin));
|
||||
#if defined(SEAMUS)
|
||||
if (digitalRead(statusLedPin)) analogWrite(sLedPin, SPCKEY_LED_BRIGHTNESS); else analogWrite(sLedPin, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
void statusLedFlash(uint16_t delayTime) {
|
||||
statusLedOff();
|
||||
delay(delayTime/2);
|
||||
statusLedOn();
|
||||
delay(delayTime/2);
|
||||
}
|
||||
|
||||
void statusLedBlink() {
|
||||
statusLedFlash(300);
|
||||
statusLedFlash(300);
|
||||
}
|
||||
|
||||
void updateSensorLEDs() {
|
||||
if (breathLevel > breathThrVal) { // breath indicator LED, labeled "B" on PCB
|
||||
//analogWrite(bLedPin, map(breathLevel,0,4096,5,breathLedBrightness));
|
||||
analogWrite(bLedPin, map(constrain(breathLevel, breathThrVal, breathMaxVal), breathThrVal, breathMaxVal, MIN_LED_BRIGHTNESS, BREATH_LED_BRIGHTNESS));
|
||||
} else {
|
||||
analogWrite(bLedPin, 0);
|
||||
}
|
||||
if (portIsOn) { // portamento indicator LED, labeled "P" on PCB
|
||||
//analogWrite(pLedPin, map(biteSensor,0,4096,5,portamLedBrightness));
|
||||
analogWrite(pLedPin, map(constrain(oldport, 0, 127), 0, 127, MIN_LED_BRIGHTNESS, PORTAM_LED_BRIGHTNESS));
|
||||
} else {
|
||||
analogWrite(pLedPin, 0);
|
||||
}
|
||||
#if defined(NURAD)
|
||||
if (exSensorIndicator){
|
||||
analogWrite(eLedPin, map(constrain(exSensorIndicator, 0, 127), 0, 127, MIN_LED_BRIGHTNESS, EXTCON_LED_BRIGHTNESS));
|
||||
} else {
|
||||
analogWrite(eLedPin, 0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ledMeter(byte indicatedValue){
|
||||
analogWrite(bLedPin, map(constrain(indicatedValue, 0, 127), 0, 127, 0, BREATH_LED_BRIGHTNESS)); // full glow at maximum value
|
||||
analogWrite(pLedPin, map(constrain(indicatedValue, 0, 127), 127, 0, 0, PORTAM_LED_BRIGHTNESS)); // full glow at minimum value
|
||||
}
|
2453
NuEVI/menu.cpp
2453
NuEVI/menu.cpp
File diff suppressed because it is too large
Load diff
52
NuEVI/menu.h
52
NuEVI/menu.h
|
@ -1,52 +0,0 @@
|
|||
#ifndef __MENU_H
|
||||
#define __MENU_H
|
||||
|
||||
#include "wiring.h"
|
||||
#include "numenu.h"
|
||||
|
||||
#define MENU_ROW_HEIGHT 9
|
||||
#define MENU_HEADER_OFFSET 12
|
||||
#define MENU_NUM_ROWS 6
|
||||
|
||||
//display states
|
||||
#define DISPLAYOFF_IDL 0
|
||||
#define MAIN_MENU 1
|
||||
#define PATCH_VIEW 2
|
||||
#define ADJUST_MENU 3
|
||||
#define SETUP_BR_MENU 4
|
||||
#define SETUP_CT_MENU 5
|
||||
#define ROTATOR_MENU 6
|
||||
#define VIBRATO_MENU 7
|
||||
#define ABOUT_MENU 8
|
||||
#define EXTRAS_MENU 9
|
||||
#define ROTA_MENU 10
|
||||
#define ROTB_MENU 11
|
||||
#define ROTC_MENU 12
|
||||
|
||||
#define ARR_LEN(a) (sizeof (a) / sizeof (a[0]))
|
||||
|
||||
#define BTN_DOWN 1
|
||||
#define BTN_ENTER 2
|
||||
#define BTN_UP 4
|
||||
#define BTN_MENU 8
|
||||
|
||||
|
||||
extern const unsigned long debounceDelay; // the debounce time; increase if the output flickers
|
||||
extern const unsigned long buttonRepeatInterval;
|
||||
extern const unsigned long buttonRepeatDelay;
|
||||
extern const unsigned long cursorBlinkInterval; // the cursor blink toggle interval time
|
||||
extern const unsigned long patchViewTimeUp; // ms until patch view shuts off
|
||||
extern const unsigned long menuTimeUp; // menu shuts off after one minute of button inactivity
|
||||
|
||||
extern byte subVibSquelch; // TODO: This is broken <- subVibSquelch is never set, we need another way to expose what menu is open.
|
||||
|
||||
void initDisplay();
|
||||
void showVersion();
|
||||
void menu();
|
||||
void drawSensorPixels();
|
||||
void i2cScanDisplay();
|
||||
|
||||
int updateAdjustMenu(uint32_t timeNow, KeyState &input, bool firstRun, bool drawSensor);
|
||||
bool adjustPageUpdate(KeyState &input, uint32_t timeNow);
|
||||
|
||||
#endif
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
Notes on the original menu implementation
|
||||
|
||||
# Menus
|
||||
|
||||
## Main Menu
|
||||
|
||||
### Transpose
|
||||
Sub menu with values -12 to 12.
|
||||
|
||||
### Octave
|
||||
Sub menu with values -3 to +3
|
||||
|
||||
### MIDI CH
|
||||
|
||||
Sub menu with values 0 to 16. Should be 1 to 16, but there might be a bug
|
||||
either in my simulation code, my changes to the menu or a bug in the original
|
||||
menu.
|
||||
|
||||
### Adjust
|
||||
|
||||
This is a special option where the Adjust menu mode is entered. It take over
|
||||
the display and draw horizontal indicators for threshold and such. More on
|
||||
this in a later section.
|
||||
|
||||
### SETUP BR
|
||||
|
||||
Breath setup. Opens a new menu with breath specific stuff.
|
||||
|
||||
|
||||
### SETUP CTL
|
||||
|
||||
Controls setup. Opens a new menu.
|
||||
|
||||
|
||||
|
||||
*/
|
|
@ -1,90 +0,0 @@
|
|||
#ifndef __NUMENU_H
|
||||
#define __NUMENU_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
//***********************************************************
|
||||
|
||||
struct KeyState {
|
||||
uint8_t current;
|
||||
uint8_t changed;
|
||||
};
|
||||
|
||||
//***********************************************************
|
||||
|
||||
enum MenuType {
|
||||
ESub,
|
||||
EStateChange,
|
||||
};
|
||||
|
||||
enum MenuEntryFlags {
|
||||
ENone = 0,
|
||||
EMenuEntryWrap = (1u<<0),
|
||||
EMenuEntryCustom = (1u<<1),
|
||||
EMenuEntryEnterHandler = (1u<<2),
|
||||
};
|
||||
|
||||
enum MenuPageFlags {
|
||||
EMenuPageCustom = (1u<<0),
|
||||
EMenuPageRoot = (1u<<1),
|
||||
EMenuCustomTitle = (1u << 2),
|
||||
};
|
||||
|
||||
|
||||
struct MenuEntry {
|
||||
enum MenuType type;
|
||||
const char* title;
|
||||
};
|
||||
|
||||
struct MenuEntrySub;
|
||||
typedef const MenuEntrySub& SubMenuRef;
|
||||
|
||||
struct MenuEntrySub {
|
||||
enum MenuType type;
|
||||
const char* title;
|
||||
const char* subTitle;
|
||||
uint16_t* valuePtr;
|
||||
uint16_t min;
|
||||
uint16_t max;
|
||||
uint16_t flags;
|
||||
void (*getSubTextFunc)(SubMenuRef, char*textBuffer, const char**label);
|
||||
void (*applyFunc)(SubMenuRef);
|
||||
bool (*onEnterFunc)(void);
|
||||
};
|
||||
|
||||
struct MenuEntryStateCh {
|
||||
enum MenuType type;
|
||||
const char* title;
|
||||
uint8_t state;
|
||||
};
|
||||
|
||||
struct MenuPage {
|
||||
const char* title;
|
||||
uint16_t flags;
|
||||
uint8_t cursor;
|
||||
uint8_t parentPage;
|
||||
uint8_t numEntries;
|
||||
const MenuEntry** entries;
|
||||
};
|
||||
|
||||
struct MenuPageCustom {
|
||||
const char* title;
|
||||
uint16_t flags;
|
||||
bool (*menuUpdateFunc)(KeyState &input, uint32_t timeNow);
|
||||
};
|
||||
|
||||
//***********************************************************
|
||||
|
||||
struct AdjustValue {
|
||||
uint16_t *value;
|
||||
uint16_t limitLow;
|
||||
uint16_t limitHigh;
|
||||
};
|
||||
|
||||
struct AdjustMenuEntry {
|
||||
const char* title;
|
||||
AdjustValue entries[2];
|
||||
void (*saveFunc)(const AdjustMenuEntry&);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -8,9 +8,16 @@
|
|||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[env:teensy31]
|
||||
[env:teensy40]
|
||||
platform = teensy
|
||||
board = teensy31
|
||||
board = teensy40
|
||||
framework = arduino
|
||||
build_flags = -D USB_MIDI -D TEENSY_OPT_FASTER
|
||||
board_build.f_cpu = 96000000L
|
||||
build_flags = -D USB_MIDI_SERIAL -D TEENSY_OPT_FASTER
|
||||
board_build.f_cpu = 528000000L
|
||||
lib_deps =
|
||||
adafruit/Adafruit MPR121@^1.1.1
|
||||
adafruit/Adafruit MPRLS Library@^1.2.0
|
||||
adafruit/Adafruit SSD1306@^2.5.7
|
||||
mkalkbrenner/WS2812Serial@^0.1.0
|
||||
adafruit/Adafruit ICM20X@^2.0.5
|
||||
paulstoffregen/Encoder@^1.4.2
|
||||
|
|
|
@ -1,618 +0,0 @@
|
|||
#include <Arduino.h>
|
||||
#include <EEPROM.h>
|
||||
#include <Adafruit_SSD1306.h>
|
||||
|
||||
|
||||
#include "settings.h"
|
||||
#include "globals.h"
|
||||
#include "menu.h"
|
||||
#include "hardware.h"
|
||||
#include "config.h"
|
||||
#include "midi.h"
|
||||
#include "led.h"
|
||||
|
||||
//Read settings from eeprom. Returns wether or not anything was written (due to factory reset or upgrade)
|
||||
void readEEPROM(const bool factoryReset) {
|
||||
|
||||
// if stored settings are not for current version, or Enter+Menu are pressed at startup, they are replaced by factory settings
|
||||
uint16_t settingsVersion = readSetting(VERSION_ADDR);
|
||||
|
||||
// blank eeprom will be 0xFFFF. For a full reset, call it "version 0" so everything gets overwritten.
|
||||
if (factoryReset || settingsVersion == 0xffffu) {
|
||||
settingsVersion = 0;
|
||||
}
|
||||
|
||||
|
||||
if(settingsVersion != EEPROM_VERSION) {
|
||||
|
||||
if(settingsVersion < 24) { //Oldest version from which any settings are recognized
|
||||
writeSetting(BREATH_THR_ADDR, BREATH_THR_FACTORY);
|
||||
writeSetting(BREATH_MAX_ADDR, BREATH_MAX_FACTORY);
|
||||
#if defined(NURAD)
|
||||
writeSetting(PORTAM_THR_ADDR, PORTPR_THR_FACTORY);
|
||||
writeSetting(PORTAM_MAX_ADDR, PORTPR_MAX_FACTORY);
|
||||
#else
|
||||
if (digitalRead(biteJumperPin)){ //PBITE (if pulled low with jumper, pressure sensor is used instead of capacitive bite sensing)
|
||||
writeSetting(PORTAM_THR_ADDR, PORTAM_THR_FACTORY);
|
||||
writeSetting(PORTAM_MAX_ADDR, PORTAM_MAX_FACTORY);
|
||||
} else {
|
||||
writeSetting(PORTAM_THR_ADDR, PORTPR_THR_FACTORY);
|
||||
writeSetting(PORTAM_MAX_ADDR, PORTPR_MAX_FACTORY);
|
||||
}
|
||||
#endif
|
||||
writeSetting(PITCHB_THR_ADDR, PITCHB_THR_FACTORY);
|
||||
writeSetting(PITCHB_MAX_ADDR, PITCHB_MAX_FACTORY);
|
||||
writeSetting(EXTRAC_THR_ADDR, EXTRAC_THR_FACTORY);
|
||||
writeSetting(EXTRAC_MAX_ADDR, EXTRAC_MAX_FACTORY);
|
||||
writeSetting(CTOUCH_THR_ADDR, CTOUCH_THR_FACTORY);
|
||||
|
||||
writeSetting(TRANSP_ADDR, TRANSP_FACTORY);
|
||||
writeSetting(MIDI_ADDR, MIDI_FACTORY);
|
||||
writeSetting(BREATH_CC_ADDR, BREATH_CC_FACTORY);
|
||||
writeSetting(BREATH_AT_ADDR, BREATH_AT_FACTORY);
|
||||
writeSetting(VELOCITY_ADDR, VELOCITY_FACTORY);
|
||||
writeSetting(PORTAM_ADDR, PORTAM_FACTORY);
|
||||
writeSetting(PB_ADDR, PB_FACTORY);
|
||||
writeSetting(EXTRA_ADDR, EXTRA_FACTORY);
|
||||
writeSetting(VIBRATO_ADDR, VIBRATO_FACTORY);
|
||||
writeSetting(DEGLITCH_ADDR, DEGLITCH_FACTORY);
|
||||
writeSetting(PATCH_ADDR, PATCH_FACTORY);
|
||||
writeSetting(OCTAVE_ADDR, OCTAVE_FACTORY);
|
||||
writeSetting(BREATHCURVE_ADDR, BREATHCURVE_FACTORY);
|
||||
writeSetting(VEL_SMP_DL_ADDR, VEL_SMP_DL_FACTORY);
|
||||
writeSetting(VEL_BIAS_ADDR, VEL_BIAS_FACTORY);
|
||||
writeSetting(PINKY_KEY_ADDR, PINKY_KEY_FACTORY);
|
||||
}
|
||||
|
||||
if(settingsVersion < 26) {
|
||||
writeSetting(FP1_ADDR, 0);
|
||||
writeSetting(FP2_ADDR, 0);
|
||||
writeSetting(FP3_ADDR, 0);
|
||||
writeSetting(FP4_ADDR, 0);
|
||||
writeSetting(FP5_ADDR, 0);
|
||||
writeSetting(FP6_ADDR, 0);
|
||||
writeSetting(FP7_ADDR, 0);
|
||||
writeSetting(DIPSW_BITS_ADDR, DIPSW_BITS_FACTORY);
|
||||
}
|
||||
|
||||
if(settingsVersion < 28) {
|
||||
writeSetting(PARAL_ADDR, PARAL_FACTORY);
|
||||
writeSetting(ROTN1_ADDR, ROTN1_FACTORY);
|
||||
writeSetting(ROTN2_ADDR, ROTN2_FACTORY);
|
||||
writeSetting(ROTN3_ADDR, ROTN3_FACTORY);
|
||||
writeSetting(ROTN4_ADDR, ROTN4_FACTORY);
|
||||
writeSetting(PRIO_ADDR, PRIO_FACTORY);
|
||||
}
|
||||
|
||||
if(settingsVersion < 29) {
|
||||
writeSetting(VIB_SENS_ADDR, VIB_SENS_FACTORY);
|
||||
writeSetting(VIB_RETN_ADDR, VIB_RETN_FACTORY);
|
||||
}
|
||||
|
||||
if(settingsVersion < 31) {
|
||||
writeSetting(VIB_SQUELCH_ADDR, VIB_SQUELCH_FACTORY);
|
||||
writeSetting(VIB_DIRECTION_ADDR, VIB_DIRECTION_FACTORY);
|
||||
}
|
||||
|
||||
if(settingsVersion < 32) {
|
||||
writeSetting(BREATH_CC2_ADDR, BREATH_CC2_FACTORY);
|
||||
writeSetting(BREATH_CC2_RISE_ADDR, BREATH_CC2_RISE_FACTORY);
|
||||
writeSetting(VIB_SENS_BITE_ADDR, VIB_SENS_BITE_FACTORY);
|
||||
writeSetting(VIB_SQUELCH_BITE_ADDR, VIB_SQUELCH_BITE_FACTORY);
|
||||
writeSetting(VIB_CONTROL_ADDR, VIB_CONTROL_FACTORY);
|
||||
writeSetting(TRILL3_INTERVAL_ADDR, TRILL3_INTERVAL_FACTORY);
|
||||
writeSetting(DAC_MODE_ADDR, DAC_MODE_FACTORY);
|
||||
}
|
||||
|
||||
if(settingsVersion < 33) {
|
||||
writeSetting(EXTRA2_ADDR, EXTRA2_FACTORY);
|
||||
writeSetting(LEVEL_CC_ADDR, LEVEL_CC_FACTORY);
|
||||
writeSetting(LEVEL_VAL_ADDR, LEVEL_VAL_FACTORY);
|
||||
}
|
||||
|
||||
if(settingsVersion < 34) {
|
||||
writeSetting(FINGER_ADDR, FINGER_FACTORY);
|
||||
writeSetting(LPINKY3_ADDR, LPINKY3_FACTORY);
|
||||
}
|
||||
|
||||
if(settingsVersion < 35) {
|
||||
writeSetting(BATTYPE_ADDR, BATTYPE_FACTORY);
|
||||
writeSetting(HARMSET_ADDR, HARMSET_FACTORY);
|
||||
writeSetting(HARMSEL_ADDR, HARMSEL_FACTORY);
|
||||
}
|
||||
|
||||
if(settingsVersion < 36) {
|
||||
writeSetting(PARAB_ADDR, PARAB_FACTORY);
|
||||
writeSetting(ROTB1_ADDR, ROTB1_FACTORY);
|
||||
writeSetting(ROTB2_ADDR, ROTB2_FACTORY);
|
||||
writeSetting(ROTB3_ADDR, ROTB3_FACTORY);
|
||||
writeSetting(ROTB4_ADDR, ROTB4_FACTORY);
|
||||
writeSetting(PARAC_ADDR, PARAC_FACTORY);
|
||||
writeSetting(ROTC1_ADDR, ROTC1_FACTORY);
|
||||
writeSetting(ROTC2_ADDR, ROTC2_FACTORY);
|
||||
writeSetting(ROTC3_ADDR, ROTC3_FACTORY);
|
||||
writeSetting(ROTC4_ADDR, ROTC4_FACTORY);
|
||||
writeSetting(POLYSEL_ADDR, POLYSEL_FACTORY);
|
||||
writeSetting(FWCTYPE_ADDR, FWCTYPE_FACTORY);
|
||||
writeSetting(HMZKEY_ADDR, HMZKEY_FACTORY);
|
||||
}
|
||||
|
||||
if(settingsVersion < 37) {
|
||||
writeSetting(FWCLCH_ADDR, FWCLCH_FACTORY);
|
||||
writeSetting(FWCDP2_ADDR, FWCDP2_FACTORY);
|
||||
}
|
||||
|
||||
|
||||
if(settingsVersion < 38) {
|
||||
writeSetting(HMZLIMIT_ADDR, HMZLIMIT_FACTORY);
|
||||
}
|
||||
|
||||
if(settingsVersion < 39) {
|
||||
writeSetting(BRINTERV_ADDR, BRINTERV_FACTORY);
|
||||
writeSetting(OTFKEY_ADDR, OTFKEY_FACTORY);
|
||||
}
|
||||
|
||||
if(settingsVersion < 40) {
|
||||
writeSetting(PORTLIMIT_ADDR, PORTLIMIT_FACTORY);
|
||||
writeSetting(LEVER_THR_ADDR, LEVER_THR_FACTORY);
|
||||
writeSetting(LEVER_MAX_ADDR, LEVER_MAX_FACTORY);
|
||||
}
|
||||
|
||||
if(settingsVersion < 41) {
|
||||
writeSetting(BRHARMSET_ADDR, BRHARMSET_FACTORY);
|
||||
writeSetting(BRHARMSEL_ADDR, BRHARMSEL_FACTORY);
|
||||
}
|
||||
|
||||
if(settingsVersion < 42) {
|
||||
writeSetting(BITECTL_ADDR, BITECTL_FACTORY);
|
||||
writeSetting(BITECC_ADDR, BITECC_FACTORY);
|
||||
writeSetting(LEVERCTL_ADDR, LEVERCTL_FACTORY);
|
||||
writeSetting(LEVERCC_ADDR, LEVERCC_FACTORY);
|
||||
}
|
||||
|
||||
if(settingsVersion < 43) {
|
||||
writeSetting(CVTUNE_ADDR, CVTUNE_FACTORY);
|
||||
writeSetting(CVSCALE_ADDR, CVSCALE_FACTORY);
|
||||
}
|
||||
|
||||
if(settingsVersion < 44) {
|
||||
writeSetting(CVRATE_ADDR, CVRATE_FACTORY);
|
||||
}
|
||||
|
||||
if(settingsVersion < 45) {
|
||||
writeSetting(ROLLER_ADDR, ROLLER_FACTORY);
|
||||
}
|
||||
|
||||
writeSetting(VERSION_ADDR, EEPROM_VERSION);
|
||||
}
|
||||
|
||||
// read all settings from EEPROM
|
||||
breathThrVal = readSettingBounded(BREATH_THR_ADDR, breathLoLimit, breathHiLimit, BREATH_THR_FACTORY);
|
||||
breathMaxVal = readSettingBounded(BREATH_MAX_ADDR, breathLoLimit, breathHiLimit, BREATH_MAX_FACTORY);
|
||||
portamThrVal = readSettingBounded(PORTAM_THR_ADDR, portamLoLimit, portamHiLimit, PORTAM_THR_FACTORY);
|
||||
portamMaxVal = readSettingBounded(PORTAM_MAX_ADDR, portamLoLimit, portamHiLimit, PORTAM_MAX_FACTORY);
|
||||
pitchbThrVal = readSettingBounded(PITCHB_THR_ADDR, pitchbLoLimit, pitchbHiLimit, PITCHB_THR_FACTORY);
|
||||
pitchbMaxVal = readSettingBounded(PITCHB_MAX_ADDR, pitchbLoLimit, pitchbHiLimit, PITCHB_MAX_FACTORY);
|
||||
transpose = readSettingBounded(TRANSP_ADDR, 0, 24, TRANSP_FACTORY);
|
||||
MIDIchannel = readSettingBounded(MIDI_ADDR, 1, 16, MIDI_FACTORY);
|
||||
breathCC = readSettingBounded(BREATH_CC_ADDR, 0, 10, BREATH_CC_FACTORY);
|
||||
breathAT = readSettingBounded(BREATH_AT_ADDR, 0, 1, BREATH_AT_FACTORY);
|
||||
velocity = readSettingBounded(VELOCITY_ADDR, 0, 127, VELOCITY_FACTORY);
|
||||
portamento = readSettingBounded(PORTAM_ADDR, 0, 5, PORTAM_FACTORY);
|
||||
PBdepth = readSettingBounded(PB_ADDR, 0, 12, PB_FACTORY);
|
||||
extraCT = readSettingBounded(EXTRA_ADDR, 0, 4, EXTRA_FACTORY);
|
||||
vibrato = readSettingBounded(VIBRATO_ADDR, 0, 9, VIBRATO_FACTORY);
|
||||
deglitch = readSettingBounded(DEGLITCH_ADDR, 0, 70, DEGLITCH_FACTORY);
|
||||
extracThrVal = readSettingBounded(EXTRAC_THR_ADDR, extracLoLimit, extracHiLimit, EXTRAC_THR_FACTORY);
|
||||
extracMaxVal = readSettingBounded(EXTRAC_MAX_ADDR, extracLoLimit, extracHiLimit, EXTRAC_MAX_FACTORY);
|
||||
patch = readSettingBounded(PATCH_ADDR, 0, 127, PATCH_FACTORY);
|
||||
octave = readSettingBounded(OCTAVE_ADDR, 0, 6, OCTAVE_FACTORY);
|
||||
ctouchThrVal = readSettingBounded(CTOUCH_THR_ADDR, ctouchLoLimit, ctouchHiLimit, CTOUCH_THR_FACTORY);
|
||||
curve = readSettingBounded(BREATHCURVE_ADDR, 0, 12, BREATHCURVE_FACTORY);
|
||||
velSmpDl = readSettingBounded(VEL_SMP_DL_ADDR, 0, 30, VEL_SMP_DL_FACTORY);
|
||||
velBias = readSettingBounded(VEL_BIAS_ADDR, 0, 9, VEL_BIAS_FACTORY);
|
||||
pinkySetting = readSettingBounded(PINKY_KEY_ADDR, 0, 31, PINKY_KEY_FACTORY);
|
||||
fastPatch[0] = readSettingBounded(FP1_ADDR, 0, 127, 0);
|
||||
fastPatch[1] = readSettingBounded(FP2_ADDR, 0, 127, 0);
|
||||
fastPatch[2] = readSettingBounded(FP3_ADDR, 0, 127, 0);
|
||||
fastPatch[3] = readSettingBounded(FP4_ADDR, 0, 127, 0);
|
||||
fastPatch[4] = readSettingBounded(FP5_ADDR, 0, 127, 0);
|
||||
fastPatch[5] = readSettingBounded(FP6_ADDR, 0, 127, 0);
|
||||
fastPatch[6] = readSettingBounded(FP7_ADDR, 0, 127, 0);
|
||||
dipSwBits = readSetting(DIPSW_BITS_ADDR);
|
||||
rotations_a.parallel = readSettingBounded(PARAL_ADDR, 0, 48, PARAL_FACTORY);
|
||||
rotations_a.rotations[0] = readSettingBounded(ROTN1_ADDR, 0, 48, ROTN1_FACTORY);
|
||||
rotations_a.rotations[1] = readSettingBounded(ROTN2_ADDR, 0, 48, ROTN2_FACTORY);
|
||||
rotations_a.rotations[2] = readSettingBounded(ROTN3_ADDR, 0, 48, ROTN3_FACTORY);
|
||||
rotations_a.rotations[3] = readSettingBounded(ROTN4_ADDR, 0, 48, ROTN4_FACTORY);
|
||||
priority = readSettingBounded(PRIO_ADDR, 0, 1, PRIO_FACTORY);
|
||||
vibSens = readSettingBounded(VIB_SENS_ADDR, 1, 12, VIB_SENS_FACTORY);
|
||||
vibRetn = readSettingBounded(VIB_RETN_ADDR, 0, 4, VIB_RETN_FACTORY);
|
||||
vibSquelch = readSettingBounded(VIB_SQUELCH_ADDR, 1, 30, VIB_SQUELCH_FACTORY);
|
||||
vibDirection = readSettingBounded(VIB_DIRECTION_ADDR, 0, 1, VIB_DIRECTION_FACTORY);
|
||||
breathCC2 = readSettingBounded(BREATH_CC2_ADDR, 0, 127, BREATH_CC2_FACTORY);
|
||||
breathCC2Rise = readSettingBounded(BREATH_CC2_RISE_ADDR, 1, 10, BREATH_CC2_RISE_FACTORY);
|
||||
vibSensBite = readSettingBounded(VIB_SENS_BITE_ADDR, 1, 17, VIB_SENS_BITE_FACTORY);
|
||||
vibSquelchBite = readSettingBounded(VIB_SQUELCH_BITE_ADDR, 1, 30, VIB_SQUELCH_BITE_FACTORY);
|
||||
vibControl = readSettingBounded(VIB_CONTROL_ADDR, 0, 2, VIB_CONTROL_FACTORY);
|
||||
dacMode = readSettingBounded(DAC_MODE_ADDR, DAC_MODE_BREATH, DAC_MODE_PITCH, DAC_MODE_FACTORY);
|
||||
trill3_interval = readSettingBounded(TRILL3_INTERVAL_ADDR, 3, 4, TRILL3_INTERVAL_FACTORY);
|
||||
extraCT2 = readSettingBounded(EXTRA2_ADDR, 0, 127, EXTRA2_FACTORY);
|
||||
levelCC = readSettingBounded(LEVEL_CC_ADDR, 0, 127, LEVEL_CC_FACTORY);
|
||||
levelVal = readSettingBounded(LEVEL_VAL_ADDR, 0, 127, LEVEL_VAL_FACTORY);
|
||||
#if defined(NURAD)
|
||||
fingering = readSettingBounded(FINGER_ADDR, 0, 4, FINGER_FACTORY);
|
||||
#else
|
||||
fingering = readSettingBounded(FINGER_ADDR, 0, 3, FINGER_FACTORY);
|
||||
#endif
|
||||
lpinky3 = readSettingBounded(LPINKY3_ADDR, 0, 25, LPINKY3_FACTORY);
|
||||
batteryType = readSettingBounded(BATTYPE_ADDR, 0, 2, BATTYPE_FACTORY);
|
||||
harmSetting = readSettingBounded(HARMSET_ADDR, 0, 6, HARMSET_FACTORY);
|
||||
harmSelect = readSettingBounded(HARMSEL_ADDR, 0, 7, HARMSEL_FACTORY);
|
||||
polySelect = (PolySelect)readSettingBounded(POLYSEL_ADDR, 0, 10, POLYSEL_FACTORY);
|
||||
fwcType = readSettingBounded(FWCTYPE_ADDR, 0, 4, FWCTYPE_FACTORY);
|
||||
fwcLockH = readSettingBounded(FWCLCH_ADDR, 0, 1, FWCLCH_FACTORY);
|
||||
fwcDrop2 = readSettingBounded(FWCDP2_ADDR, 0, 1, FWCDP2_FACTORY);
|
||||
hmzKey = readSettingBounded(HMZKEY_ADDR, 0, 11, HMZKEY_FACTORY);
|
||||
hmzLimit = readSettingBounded(HMZLIMIT_ADDR, 2, 5, HMZLIMIT_FACTORY);
|
||||
rotations_b.parallel = readSettingBounded(PARAB_ADDR, 0, 48, PARAB_FACTORY);
|
||||
rotations_b.rotations[0] = readSettingBounded(ROTB1_ADDR, 0, 48, ROTB1_FACTORY);
|
||||
rotations_b.rotations[1] = readSettingBounded(ROTB2_ADDR, 0, 48, ROTB2_FACTORY);
|
||||
rotations_b.rotations[2] = readSettingBounded(ROTB3_ADDR, 0, 48, ROTB3_FACTORY);
|
||||
rotations_b.rotations[3] = readSettingBounded(ROTB4_ADDR, 0, 48, ROTB4_FACTORY);
|
||||
rotations_c.parallel = readSettingBounded(PARAC_ADDR, 0, 48, PARAC_FACTORY);
|
||||
rotations_c.rotations[0] = readSettingBounded(ROTC1_ADDR, 0, 48, ROTC1_FACTORY);
|
||||
rotations_c.rotations[1] = readSettingBounded(ROTC2_ADDR, 0, 48, ROTC2_FACTORY);
|
||||
rotations_c.rotations[2] = readSettingBounded(ROTC3_ADDR, 0, 48, ROTC3_FACTORY);
|
||||
rotations_c.rotations[3] = readSettingBounded(ROTC4_ADDR, 0, 48, ROTC4_FACTORY);
|
||||
otfKey = readSettingBounded(OTFKEY_ADDR, 0, 1, OTFKEY_FACTORY);
|
||||
breathInterval = readSettingBounded(BRINTERV_ADDR, 3, 15, BRINTERV_FACTORY);
|
||||
portLimit = readSettingBounded(PORTLIMIT_ADDR, 1, 127, PORTLIMIT_FACTORY);
|
||||
leverThrVal = readSettingBounded(LEVER_THR_ADDR, leverLoLimit, leverHiLimit, LEVER_THR_FACTORY);
|
||||
leverMaxVal = readSettingBounded(LEVER_MAX_ADDR, leverLoLimit, leverHiLimit, LEVER_MAX_FACTORY);
|
||||
brHarmSetting = readSettingBounded(BRHARMSET_ADDR, 0, 6, BRHARMSET_FACTORY);
|
||||
brHarmSelect = readSettingBounded(BRHARMSEL_ADDR, 0, 3, BRHARMSEL_FACTORY);
|
||||
biteControl = readSettingBounded(BITECTL_ADDR, 0, 3, BITECTL_FACTORY);
|
||||
leverControl = readSettingBounded(LEVERCTL_ADDR, 0, 3, LEVERCTL_FACTORY);
|
||||
biteCC = readSettingBounded(BITECC_ADDR, 0, 127, BITECC_FACTORY);
|
||||
leverCC = readSettingBounded(LEVERCC_ADDR, 0, 127, LEVERCC_FACTORY);
|
||||
cvTune = readSettingBounded(CVTUNE_ADDR, 1, 199, CVTUNE_FACTORY);
|
||||
cvScale = readSettingBounded(CVSCALE_ADDR, 1, 199, CVSCALE_FACTORY);
|
||||
cvVibRate = readSettingBounded(CVRATE_ADDR, 0, 8, CVRATE_FACTORY);
|
||||
rollerMode = readSettingBounded(ROLLER_ADDR, 0, 3, ROLLER_FACTORY);
|
||||
|
||||
//Flags stored in bit field
|
||||
fastBoot = (dipSwBits & (1<<DIPSW_FASTBOOT))?1:0;
|
||||
legacy = (dipSwBits & (1<<DIPSW_LEGACY))?1:0;
|
||||
legacyBrAct = (dipSwBits & (1<<DIPSW_LEGACYBRACT))?1:0;
|
||||
widiOn = (dipSwBits & (1<<DIPSW_WIDION))?1:0;
|
||||
gateOpenEnable = (dipSwBits & (1<<DIPSW_GATEOPEN))?1:0;
|
||||
specialKeyEnable = (dipSwBits & (1<<DIPSW_SPKEYENABLE))?1:0;
|
||||
bcasMode = (dipSwBits & (1<<DIPSW_BCASMODE))?1:0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
//Poke at a certain bit in a bit field
|
||||
void setBit(uint16_t &bitfield, const uint8_t pos, const uint16_t value) {
|
||||
bitfield = (bitfield & ~(1<<pos)) | ((value?1:0)<<pos);
|
||||
}
|
||||
|
||||
|
||||
//Read and write EEPROM data
|
||||
void writeSetting(const uint16_t address, const uint16_t value) {
|
||||
union {
|
||||
uint8_t v[2];
|
||||
uint16_t val;
|
||||
} data;
|
||||
data.val = value;
|
||||
EEPROM.update(address, data.v[0]);
|
||||
EEPROM.update(address+1, data.v[1]);
|
||||
}
|
||||
|
||||
uint16_t readSetting(const uint16_t address) {
|
||||
union {
|
||||
uint8_t v[2];
|
||||
uint16_t val;
|
||||
} data;
|
||||
data.v[0] = EEPROM.read(address);
|
||||
data.v[1] = EEPROM.read(address+1);
|
||||
return data.val;
|
||||
}
|
||||
|
||||
uint16_t readSettingBounded(const uint16_t address, const uint16_t min, const uint16_t max, const uint16_t defaultValue) {
|
||||
uint16_t val = readSetting(address);
|
||||
if(val < min || val > max) {
|
||||
val = defaultValue;
|
||||
writeSetting(address, val);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//Functions to send and receive config (and other things) via USB MIDI SysEx messages
|
||||
uint32_t crc32(const uint8_t *message, const size_t length) {
|
||||
size_t pos=0;
|
||||
uint32_t crc=0xFFFFFFFF;
|
||||
|
||||
while (pos<length) {
|
||||
crc ^= message[pos++]; //Get next byte and increment position
|
||||
for (uint8_t j=0; j<8; ++j) { //Mask off 8 next bits
|
||||
crc = (crc >> 1) ^ (0xEDB88320 & -(crc & 1));
|
||||
}
|
||||
}
|
||||
return ~crc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
||||
Send EEPROM config dump as sysex message. Message format is structured like this:
|
||||
|
||||
+------------------------------------------------------------------------------------+
|
||||
| vendor(3) | "NuEVIc01" (8) | Payload size (2) | EEPROM data (variable) | crc32 (4) |
|
||||
+------------------------------------------------------------------------------------+
|
||||
|
||||
Payload size is for the EEPROM data chunk (not including anything else before or after
|
||||
CRC32 covers the entire buffer up to and including the eeprom data (but not the checksum itself)
|
||||
|
||||
This currently operates under the assumption that the whole EEPROM chunk only consists of unsigned 16 bit ints, only using the range 0-16383
|
||||
|
||||
*/
|
||||
void sendSysexSettings() {
|
||||
const char *header = "NuEVIc01"; //NuEVI config dump 01
|
||||
|
||||
//Build a send buffer of all the things
|
||||
size_t sysex_size = 3 + strlen(header) + 2 + EEPROM_SIZE + 4;
|
||||
uint8_t *sysex_data = (uint8_t*)malloc(sysex_size);
|
||||
|
||||
//Positions (offsets) of parts in send buffer
|
||||
int header_pos = 3;
|
||||
int size_pos = header_pos + strlen(header);
|
||||
int payload_pos = size_pos + 2;
|
||||
int checksum_pos = payload_pos + EEPROM_SIZE;
|
||||
|
||||
//SysEX manufacturer ID
|
||||
memcpy(sysex_data, sysex_id, 3);
|
||||
|
||||
//Header with command code
|
||||
memcpy(sysex_data+header_pos, header, strlen(header));
|
||||
|
||||
//Payload length
|
||||
*(uint16_t*)(sysex_data+size_pos) = convertToMidiValue(EEPROM_SIZE);
|
||||
|
||||
//Config data
|
||||
uint16_t* config_buffer_start = (uint16_t*)(sysex_data+payload_pos);
|
||||
|
||||
//Read one settings item at a time, change data format, and put in send buffer
|
||||
for(uint16_t idx=0; idx<EEPROM_SIZE/2; idx++) {
|
||||
uint16_t eepromval = readSetting(idx*2);
|
||||
config_buffer_start[idx] = convertToMidiValue(eepromval);
|
||||
}
|
||||
|
||||
uint32_t checksum = crc32(sysex_data, checksum_pos);
|
||||
|
||||
*(uint32_t*)(sysex_data+checksum_pos) = convertToMidiCRC(checksum);
|
||||
|
||||
usbMIDI.sendSysEx(sysex_size, sysex_data);
|
||||
|
||||
free(sysex_data);
|
||||
}
|
||||
|
||||
//Send a simple 3-byte message code as sysex
|
||||
void sendSysexMessage(const char* messageCode) {
|
||||
char sysexMessage[] = "vvvNuEVIccc"; //Placeholders for vendor and code
|
||||
|
||||
memcpy(sysexMessage, sysex_id, 3);
|
||||
memcpy(sysexMessage+8, messageCode, 3);
|
||||
|
||||
usbMIDI.sendSysEx(11, (const uint8_t *)sysexMessage);
|
||||
}
|
||||
|
||||
|
||||
bool receiveSysexSettings(const uint8_t* data, const uint16_t length) {
|
||||
|
||||
//Expected size of data (vendor+NuEVIc02+len+payload+crc32)
|
||||
uint16_t expected_size = 3 + 8 + 2 + EEPROM_SIZE + 4;
|
||||
|
||||
|
||||
//Positions (offsets) of parts in buffer
|
||||
int size_pos = 11;
|
||||
int payload_pos = size_pos + 2;
|
||||
int checksum_pos = payload_pos + EEPROM_SIZE;
|
||||
|
||||
//Make sure length of receive buffer is enough to read all we need to. We can accept extra junk at the end though.
|
||||
if(length<expected_size) {
|
||||
configShowMessage("Invalid config format");
|
||||
return false;
|
||||
}
|
||||
|
||||
//No need to verify vendor or header/command, already done before we get here.
|
||||
|
||||
//Calculate checksum of stuff received (everything before checksum), transform to midi format
|
||||
//(being a one-way operation, we can't do the reverse anyway)
|
||||
uint32_t crc=convertToMidiCRC(crc32(data, checksum_pos));
|
||||
uint32_t crc_rcv;
|
||||
memcpy(&crc_rcv, data+checksum_pos, 4);
|
||||
if(crc != crc_rcv && crc_rcv != NO_CHECKSUM) {
|
||||
configShowMessage("Invalid checksum");
|
||||
return false;
|
||||
}
|
||||
|
||||
//Verify that payload size matches the size of our EEPROM config
|
||||
uint16_t payload_size = convertFromMidiValue(data+size_pos);
|
||||
if(payload_size != EEPROM_SIZE) {
|
||||
configShowMessage("Invalid config size");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
uint16_t eeprom_version_rcv = convertFromMidiValue(data+(payload_pos+VERSION_ADDR));
|
||||
if(eeprom_version_rcv != EEPROM_VERSION) {
|
||||
configShowMessage("Invalid config version");
|
||||
return false;
|
||||
}
|
||||
|
||||
//Grab all the items in payload and save to EEPROM
|
||||
for(uint16_t i=0; i<payload_size/2; i++) {
|
||||
uint16_t addr = i*2;
|
||||
uint16_t val;
|
||||
val = convertFromMidiValue(data+(payload_pos+addr));
|
||||
|
||||
//Skip sensor calibration values if they are "out of bounds". This makes it possible to send a config that does
|
||||
//not overwrite sensor calibration.
|
||||
if(addr == BREATH_THR_ADDR || addr == BREATH_MAX_ADDR) {
|
||||
if(val<breathLoLimit || val>breathHiLimit) continue;
|
||||
}
|
||||
|
||||
if(addr == PORTAM_THR_ADDR || addr == PORTAM_MAX_ADDR) {
|
||||
if(val<portamLoLimit || val>portamHiLimit) continue;
|
||||
}
|
||||
|
||||
if(addr == PITCHB_THR_ADDR || addr == PITCHB_MAX_ADDR) {
|
||||
if(val<pitchbLoLimit || val>pitchbHiLimit) continue;
|
||||
}
|
||||
|
||||
if(addr == EXTRAC_THR_ADDR || addr == EXTRAC_MAX_ADDR) {
|
||||
if(val<extracLoLimit || val>extracHiLimit) continue;
|
||||
}
|
||||
|
||||
if(addr == CTOUCH_THR_ADDR) {
|
||||
if(val<ctouchLoLimit || val>ctouchHiLimit) continue;
|
||||
}
|
||||
|
||||
writeSetting(addr, val);
|
||||
}
|
||||
|
||||
//All went well
|
||||
return true;
|
||||
}
|
||||
|
||||
//Send EEPROM and firmware versions
|
||||
void sendSysexVersion() {
|
||||
char sysexMessage[] = "vvvNuEVIc04eevvvvvvvv"; //Placeholders for vendor and code
|
||||
uint8_t fwStrLen = min(strlen(FIRMWARE_VERSION), 8); //Limit firmware version string to 8 bytes
|
||||
|
||||
memcpy(sysexMessage, sysex_id, 3);
|
||||
memcpy(sysexMessage+13, FIRMWARE_VERSION, fwStrLen);
|
||||
|
||||
*(uint16_t*)(sysexMessage+11) = convertToMidiValue(EEPROM_VERSION);
|
||||
|
||||
uint8_t message_length = 13+fwStrLen;
|
||||
|
||||
usbMIDI.sendSysEx(message_length, (const uint8_t *)sysexMessage);
|
||||
}
|
||||
|
||||
extern Adafruit_SSD1306 display;
|
||||
|
||||
void configShowMessage(const char* message) {
|
||||
display.fillRect(0,32,128,64,BLACK);
|
||||
display.setCursor(0,32);
|
||||
display.setTextColor(WHITE);
|
||||
|
||||
display.print(message);
|
||||
|
||||
display.display();
|
||||
}
|
||||
|
||||
uint8_t* sysex_rcv_buffer = NULL;
|
||||
uint16_t sysex_buf_size = 0;
|
||||
|
||||
|
||||
void handleSysexChunk(const uint8_t *data, uint16_t length, bool last) {
|
||||
uint16_t pos;
|
||||
|
||||
if(!sysex_rcv_buffer) {
|
||||
//Start out with an empty buffer
|
||||
pos = 0;
|
||||
sysex_buf_size = length;
|
||||
sysex_rcv_buffer = (uint8_t *)malloc(sysex_buf_size);
|
||||
} else {
|
||||
//Increase size of current buffer
|
||||
pos = sysex_buf_size;
|
||||
sysex_buf_size += length;
|
||||
sysex_rcv_buffer = (uint8_t *)realloc(sysex_rcv_buffer, sysex_buf_size);
|
||||
}
|
||||
|
||||
//Append this chunk to buffer
|
||||
memcpy(sysex_rcv_buffer + pos, data, length);
|
||||
|
||||
//If it's the last one, call the regular handler to process it
|
||||
if(last) {
|
||||
handleSysex(sysex_rcv_buffer, sysex_buf_size);
|
||||
|
||||
//Discard the buffer
|
||||
free(sysex_rcv_buffer);
|
||||
sysex_rcv_buffer = NULL;
|
||||
sysex_buf_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void handleSysex(uint8_t *data, unsigned int length) {
|
||||
//Note: Sysex data as received here contains sysex start and end markers (0xF0 and 0xF7)
|
||||
|
||||
//Too short to even contain a 3-byte vendor id is not for us.
|
||||
if(length<4) return;
|
||||
|
||||
//Verify vendor
|
||||
if(strncmp((char*)(data+1), sysex_id, 3)) return; //Silently ignore different vendor id
|
||||
|
||||
//Verify header. Min length is 3+5+3 bytes (vendor+header+message code)
|
||||
if(length<12 || strncmp((char*)(data+4), "NuEVI", 5)) {
|
||||
configShowMessage("Invalid message.");
|
||||
sendSysexMessage("e00");
|
||||
return;
|
||||
}
|
||||
|
||||
//Get message code
|
||||
char messageCode[3];
|
||||
strncpy(messageCode, (char*)(data+9), 3);
|
||||
|
||||
if(!strncmp(messageCode, "c00", 3)) { //Config dump request
|
||||
configShowMessage("Sending config...");
|
||||
sendSysexSettings();
|
||||
configShowMessage("Config sent.");
|
||||
} else if(!strncmp(messageCode, "c03", 3)) { //Version info request
|
||||
configShowMessage("Sending version.");
|
||||
sendSysexVersion();
|
||||
} else if(!strncmp(messageCode, "c02", 3)) { //New config incoming
|
||||
configShowMessage("Receiving config...");
|
||||
|
||||
//Tell receiveSysexSettings about what's between sysex start and end markers
|
||||
if(receiveSysexSettings(data+1, length-2)) configShowMessage("New config saved.");
|
||||
} else {
|
||||
configShowMessage("Unknown message.");
|
||||
sendSysexMessage("e01"); //Unimplemented message code
|
||||
}
|
||||
}
|
||||
|
||||
void configModeSetup() {
|
||||
statusLedFlash(500);
|
||||
|
||||
display.clearDisplay();
|
||||
display.setCursor(0,0);
|
||||
display.setTextColor(WHITE);
|
||||
display.setTextSize(0);
|
||||
|
||||
display.println("Config mgmt");
|
||||
display.println("Power off NuEVI");
|
||||
display.println("to exit");
|
||||
display.display();
|
||||
|
||||
usbMIDI.setHandleSystemExclusive(handleSysexChunk);
|
||||
|
||||
statusLedFlash(500);
|
||||
|
||||
sendSysexVersion(); //Friendly hello
|
||||
|
||||
configShowMessage("Ready.");
|
||||
}
|
||||
|
||||
//"Main loop". Just sits and wait for midi messages and lets the sysex handler do all the work.
|
||||
void configModeLoop() {
|
||||
usbMIDI.read();
|
||||
}
|
226
NuEVI/settings.h
226
NuEVI/settings.h
|
@ -1,226 +0,0 @@
|
|||
#ifndef __SETTINGS_H
|
||||
#define __SETTINGS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// EEPROM addresses for settings
|
||||
#define VERSION_ADDR 0
|
||||
#define BREATH_THR_ADDR 2
|
||||
#define BREATH_MAX_ADDR 4
|
||||
#define PORTAM_THR_ADDR 6
|
||||
#define PORTAM_MAX_ADDR 8
|
||||
#define PITCHB_THR_ADDR 10
|
||||
#define PITCHB_MAX_ADDR 12
|
||||
#define TRANSP_ADDR 14
|
||||
#define MIDI_ADDR 16
|
||||
#define BREATH_CC_ADDR 18
|
||||
#define BREATH_AT_ADDR 20
|
||||
#define VELOCITY_ADDR 22
|
||||
#define PORTAM_ADDR 24
|
||||
#define PB_ADDR 26
|
||||
#define EXTRA_ADDR 28
|
||||
#define VIBRATO_ADDR 30
|
||||
#define DEGLITCH_ADDR 32
|
||||
#define EXTRAC_THR_ADDR 34
|
||||
#define EXTRAC_MAX_ADDR 36
|
||||
#define PATCH_ADDR 38
|
||||
#define OCTAVE_ADDR 40
|
||||
#define CTOUCH_THR_ADDR 42
|
||||
#define BREATHCURVE_ADDR 44
|
||||
#define VEL_SMP_DL_ADDR 46
|
||||
#define VEL_BIAS_ADDR 48
|
||||
#define PINKY_KEY_ADDR 50
|
||||
#define FP1_ADDR 52
|
||||
#define FP2_ADDR 54
|
||||
#define FP3_ADDR 56
|
||||
#define FP4_ADDR 58
|
||||
#define FP5_ADDR 60
|
||||
#define FP6_ADDR 62
|
||||
#define FP7_ADDR 64
|
||||
#define DIPSW_BITS_ADDR 66
|
||||
#define PARAL_ADDR 68
|
||||
#define ROTN1_ADDR 70
|
||||
#define ROTN2_ADDR 72
|
||||
#define ROTN3_ADDR 74
|
||||
#define ROTN4_ADDR 76
|
||||
#define PRIO_ADDR 78
|
||||
#define VIB_SENS_ADDR 80
|
||||
#define VIB_RETN_ADDR 82
|
||||
#define VIB_SQUELCH_ADDR 84
|
||||
#define VIB_DIRECTION_ADDR 86
|
||||
#define BREATH_CC2_ADDR 88
|
||||
#define BREATH_CC2_RISE_ADDR 90
|
||||
#define VIB_SENS_BITE_ADDR 92
|
||||
#define VIB_SQUELCH_BITE_ADDR 94
|
||||
#define VIB_CONTROL_ADDR 96
|
||||
#define TRILL3_INTERVAL_ADDR 98
|
||||
#define DAC_MODE_ADDR 100
|
||||
#define EXTRA2_ADDR 102
|
||||
#define LEVEL_CC_ADDR 104
|
||||
#define LEVEL_VAL_ADDR 106
|
||||
#define FINGER_ADDR 108
|
||||
#define LPINKY3_ADDR 110
|
||||
#define BATTYPE_ADDR 112
|
||||
#define HARMSET_ADDR 114
|
||||
#define HARMSEL_ADDR 116
|
||||
#define PARAB_ADDR 118
|
||||
#define ROTB1_ADDR 120
|
||||
#define ROTB2_ADDR 122
|
||||
#define ROTB3_ADDR 124
|
||||
#define ROTB4_ADDR 126
|
||||
#define PARAC_ADDR 128
|
||||
#define ROTC1_ADDR 130
|
||||
#define ROTC2_ADDR 132
|
||||
#define ROTC3_ADDR 134
|
||||
#define ROTC4_ADDR 136
|
||||
#define POLYSEL_ADDR 138
|
||||
#define FWCTYPE_ADDR 140
|
||||
#define HMZKEY_ADDR 150
|
||||
#define FWCLCH_ADDR 152
|
||||
#define FWCDP2_ADDR 154
|
||||
#define HMZLIMIT_ADDR 156
|
||||
#define BRINTERV_ADDR 158
|
||||
#define OTFKEY_ADDR 160
|
||||
#define PORTLIMIT_ADDR 162
|
||||
#define LEVER_THR_ADDR 164
|
||||
#define LEVER_MAX_ADDR 166
|
||||
#define BRHARMSET_ADDR 168
|
||||
#define BRHARMSEL_ADDR 170
|
||||
#define BITECTL_ADDR 172
|
||||
#define BITECC_ADDR 174
|
||||
#define LEVERCTL_ADDR 176
|
||||
#define LEVERCC_ADDR 178
|
||||
#define CVTUNE_ADDR 180
|
||||
#define CVSCALE_ADDR 182
|
||||
#define CVRATE_ADDR 184
|
||||
#define ROLLER_ADDR 186
|
||||
|
||||
#define EEPROM_SIZE 188 //Last address +2
|
||||
|
||||
|
||||
//DAC output modes
|
||||
#define DAC_MODE_BREATH 0
|
||||
#define DAC_MODE_PITCH 1
|
||||
|
||||
#define DIPSW_FASTBOOT 0
|
||||
#define DIPSW_LEGACY 1
|
||||
#define DIPSW_LEGACYBRACT 2
|
||||
#define DIPSW_WIDION 3
|
||||
#define DIPSW_GATEOPEN 4
|
||||
#define DIPSW_SPKEYENABLE 5
|
||||
#define DIPSW_BCASMODE 6
|
||||
|
||||
|
||||
//"factory" values for settings
|
||||
#define EEPROM_VERSION 45
|
||||
|
||||
#define BREATH_THR_FACTORY 1400
|
||||
#define BREATH_MAX_FACTORY 4000
|
||||
#define PORTAM_THR_FACTORY 2600
|
||||
#define PORTAM_MAX_FACTORY 3300
|
||||
#define PORTPR_THR_FACTORY 1200
|
||||
#define PORTPR_MAX_FACTORY 2000
|
||||
#define PITCHB_THR_FACTORY 2000
|
||||
#define PITCHB_MAX_FACTORY 3000
|
||||
#define EXTRAC_THR_FACTORY 2800
|
||||
#define EXTRAC_MAX_FACTORY 3500
|
||||
#define LEVER_THR_FACTORY 1700
|
||||
#define LEVER_MAX_FACTORY 1800
|
||||
#define TRANSP_FACTORY 12 // 12 is 0 transpose
|
||||
#define MIDI_FACTORY 1 // 1-16
|
||||
#define BREATH_CC_FACTORY 2 //thats CC#2, see ccList
|
||||
#define BREATH_AT_FACTORY 0 //aftertouch default off
|
||||
#define VELOCITY_FACTORY 0 // 0 is dynamic/breath controlled velocity
|
||||
#define PORTAM_FACTORY 2 // 0 - OFF, 1 - ON, 2 - SW
|
||||
#define PB_FACTORY 1 // 0 - OFF, 1 - 12
|
||||
#define EXTRA_FACTORY 1 // 0 - OFF, 1 - Modulation wheel, 2 - Foot pedal, 3 - Filter Cutoff, 4 - Sustain pedal
|
||||
#define VIBRATO_FACTORY 4 // 0 - OFF, 1 - 9 depth
|
||||
#define DEGLITCH_FACTORY 20 // 0 - OFF, 5 to 70 ms in steps of 5
|
||||
#define PATCH_FACTORY 1 // MIDI program change 1-128
|
||||
#define OCTAVE_FACTORY 3 // 3 is 0 octave change
|
||||
#define CTOUCH_THR_FACTORY 125 // MPR121 touch threshold
|
||||
#define BREATHCURVE_FACTORY 4 // 0 to 12 (-4 to +4, S1 to S4)
|
||||
#define VEL_SMP_DL_FACTORY 20 // 0 to 30
|
||||
#define VEL_BIAS_FACTORY 0 // 0 to 9
|
||||
#define PINKY_KEY_FACTORY 12 // 0 - 11 (QuickTranspose -12 to -1), 12 (pb/2), 13 - 22 (QuickTranspose +1 to +12)
|
||||
#define DIPSW_BITS_FACTORY 0 // virtual dip switch settings for special modes (work in progress)
|
||||
#define PARAL_FACTORY 31 // 7 (+ 24) Rotator parallel
|
||||
#define ROTN1_FACTORY 19 // -5 (+24) Rotation 1
|
||||
#define ROTN2_FACTORY 14 // -10 (+24) Rotation 2
|
||||
#define ROTN3_FACTORY 17 // -7 (+24) Rotation 3
|
||||
#define ROTN4_FACTORY 10 // -14 (+24) Rotation 4
|
||||
#define PRIO_FACTORY 0 // Mono priority 0 - BAS(e note), 1 - ROT(ating note)
|
||||
#define VIB_SENS_FACTORY 6 // 1 least sensitive, higher more sensitive
|
||||
#define VIB_RETN_FACTORY 2 // 0, no return, 1 slow return, higher faster return
|
||||
#define VIB_SQUELCH_FACTORY 12 // 0 to 30, vib signal squelch
|
||||
#define VIB_DIRECTION_FACTORY 0
|
||||
#define BREATH_CC2_FACTORY 0 //OFF,1-127
|
||||
#define BREATH_CC2_RISE_FACTORY 1
|
||||
#define VIB_SENS_BITE_FACTORY 8
|
||||
#define VIB_SQUELCH_BITE_FACTORY 15
|
||||
#define VIB_CONTROL_FACTORY 0
|
||||
#define TRILL3_INTERVAL_FACTORY 4
|
||||
#define DAC_MODE_FACTORY DAC_MODE_PITCH
|
||||
#define EXTRA2_FACTORY 0
|
||||
#define LEVEL_CC_FACTORY 11
|
||||
#define LEVEL_VAL_FACTORY 127
|
||||
#define FINGER_FACTORY 0
|
||||
#define LPINKY3_FACTORY 0
|
||||
#define BATTYPE_FACTORY 0
|
||||
#define HARMSET_FACTORY 0
|
||||
#define HARMSEL_FACTORY 0
|
||||
#define PARAB_FACTORY 31 // 7 (+ 24) Rotator parallel
|
||||
#define ROTB1_FACTORY 19 // -5 (+24) Rotation 1
|
||||
#define ROTB2_FACTORY 14 // -10 (+24) Rotation 2
|
||||
#define ROTB3_FACTORY 17 // -7 (+24) Rotation 3
|
||||
#define ROTB4_FACTORY 10 // -14 (+24) Rotation 4
|
||||
#define PARAC_FACTORY 31 // 7 (+ 24) Rotator parallel
|
||||
#define ROTC1_FACTORY 19 // -5 (+24) Rotation 1
|
||||
#define ROTC2_FACTORY 14 // -10 (+24) Rotation 2
|
||||
#define ROTC3_FACTORY 17 // -7 (+24) Rotation 3
|
||||
#define ROTC4_FACTORY 10 // -14 (+24) Rotation 4
|
||||
#define POLYSEL_FACTORY 0
|
||||
#define FWCTYPE_FACTORY 0
|
||||
#define HMZKEY_FACTORY 0
|
||||
#define FWCLCH_FACTORY 0
|
||||
#define FWCDP2_FACTORY 0
|
||||
#define HMZLIMIT_FACTORY 5
|
||||
#define BRINTERV_FACTORY 6
|
||||
#define OTFKEY_FACTORY 0
|
||||
#define PORTLIMIT_FACTORY 127
|
||||
#define BRHARMSET_FACTORY 0
|
||||
#define BRHARMSEL_FACTORY 0
|
||||
#define BITECTL_FACTORY 2 // GLD
|
||||
#define LEVERCTL_FACTORY 1 // VIB
|
||||
#define BITECC_FACTORY 1 //Mod Wheel
|
||||
#define LEVERCC_FACTORY 11 //Expression
|
||||
#define CVTUNE_FACTORY 100 // 100 is zero tuning
|
||||
#define CVSCALE_FACTORY 100 // 100 is zero scaling
|
||||
#define CVRATE_FACTORY 3 // 3 is 5.5Hz
|
||||
#define ROLLER_FACTORY 1
|
||||
|
||||
#define NO_CHECKSUM 0x7F007F00
|
||||
|
||||
void readEEPROM(const bool factoryReset);
|
||||
void setBit(uint16_t &bitfield, const uint8_t pos, const uint16_t value);
|
||||
uint16_t readSetting(const uint16_t address);
|
||||
void writeSetting(const uint16_t address, const uint16_t value);
|
||||
uint16_t readSettingBounded(const uint16_t address, const uint16_t min, const uint16_t max, const uint16_t defaultValue);
|
||||
|
||||
//Functions for config management mode
|
||||
void sendSysexSettings();
|
||||
void sendSysexMessage(const char* messageCode);
|
||||
void sendSysexVersion();
|
||||
|
||||
void handleSysex(uint8_t *data, unsigned int length);
|
||||
void handleSysexChunk(const uint8_t *data, uint16_t length, bool last);
|
||||
|
||||
uint32_t crc32(const uint8_t *message, const size_t length);
|
||||
|
||||
void configInitScreen();
|
||||
void configShowMessage(const char* message);
|
||||
|
||||
void configModeSetup();
|
||||
void configModeLoop();
|
||||
|
||||
#endif
|
2
NuEVI/FilterOnePole.cpp → NuEVI/src/FilterOnePole.cpp
Executable file → Normal file
2
NuEVI/FilterOnePole.cpp → NuEVI/src/FilterOnePole.cpp
Executable file → Normal file
|
@ -219,8 +219,6 @@ void FilterOnePoleCascade::test() {
|
|||
float maxVal = 0;
|
||||
float valWasOutputThisCycle = true;
|
||||
|
||||
__unused float lastFilterVal = 0;
|
||||
|
||||
while( true ) {
|
||||
float now = 1e-3*millis();
|
||||
|
0
NuEVI/FilterOnePole.h → NuEVI/src/FilterOnePole.h
Executable file → Normal file
0
NuEVI/FilterOnePole.h → NuEVI/src/FilterOnePole.h
Executable file → Normal file
8
NuEVI/src/TODO
Normal file
8
NuEVI/src/TODO
Normal file
|
@ -0,0 +1,8 @@
|
|||
1. LED abstraction code
|
||||
2. Encoder code
|
||||
3. Menu refactor
|
||||
4. Refactor note play behavior into module
|
||||
5. Refactor CV behavior into module
|
||||
6. 9dof sensor code
|
||||
7. Alternate fingerings
|
||||
8. Encoder midi
|
175
NuEVI/src/adjustmenu.cpp
Normal file
175
NuEVI/src/adjustmenu.cpp
Normal file
|
@ -0,0 +1,175 @@
|
|||
#include <array>
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Adafruit_SSD1306.h>
|
||||
#include "menu.h"
|
||||
#include "globals.h"
|
||||
#include "config.h"
|
||||
#include "hardware.h"
|
||||
#include "settings.h"
|
||||
|
||||
//***********************************************************
|
||||
|
||||
extern Adafruit_SSD1306 display;
|
||||
extern Adafruit_MPR121 touchSensorUtil;
|
||||
extern Adafruit_MPR121 touchSensorKeys;
|
||||
extern Adafruit_MPRLS pressureSensorMain;
|
||||
extern Adafruit_MPRLS pressureSensorAlt;
|
||||
extern byte cursorNow;
|
||||
|
||||
int16_t ctouchVal = 0;
|
||||
|
||||
// Track pixels for faster redrawing
|
||||
struct AdjustDrawing {
|
||||
int row;
|
||||
int thrX;
|
||||
int maxX;
|
||||
int valX;
|
||||
};
|
||||
|
||||
struct AdjustValue {
|
||||
const char *title;
|
||||
const int16_t &value;
|
||||
int16_t &thrVal;
|
||||
int16_t &maxVal;
|
||||
const int16_t limitLow;
|
||||
const int16_t limitHigh;
|
||||
|
||||
// If not null, thr and max are relative to zeroPoint
|
||||
const int16_t *zeroPoint;
|
||||
};
|
||||
|
||||
template<size_t N>
|
||||
class AdjustMenuScreen : public MenuScreen {
|
||||
public:
|
||||
AdjustMenuScreen(const char* title, std::array<AdjustValue, N> entries) : _title(title), _entries(entries) { }
|
||||
void update(InputState input, bool redraw) {
|
||||
bool redrawIndicators = false;
|
||||
if (input.changed) {
|
||||
if (input.knobMenu) {
|
||||
_selectedEntry = _selectedEntry + input.knobMenu;
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
AdjustValue value = _entries[_selectedEntry];
|
||||
if (input.knobVal1) {
|
||||
value.thrVal += input.knobVal1;
|
||||
redrawIndicators = true;
|
||||
}
|
||||
|
||||
if (input.knobVal2) {
|
||||
value.maxVal += input.knobVal2;
|
||||
redrawIndicators = true;
|
||||
}
|
||||
} else {
|
||||
draw(redrawIndicators, redraw);
|
||||
}
|
||||
}
|
||||
const char *title() {
|
||||
return _title;
|
||||
}
|
||||
private:
|
||||
void draw(bool redrawIndicators, bool redraw) {
|
||||
for (size_t i = 0; i < N; i++) {
|
||||
if (redraw) {
|
||||
drawAdjustRow(_entries[i], _rowDrawings[i], i == _selectedEntry);
|
||||
} else if (redrawIndicators && i == _selectedEntry) {
|
||||
drawAdjustIndicators(_entries[i], _rowDrawings[i]);
|
||||
} else {
|
||||
drawAdjustValues(_entries[i], _rowDrawings[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char* _title;
|
||||
size_t _selectedEntry = 0;
|
||||
std::array<AdjustValue, N> _entries;
|
||||
std::array<AdjustDrawing, N> _rowDrawings;
|
||||
};
|
||||
|
||||
std::array<AdjustValue, 8> adjustValues = {{
|
||||
{"BREATH", state.breathSignal, calibration.breathThrValOffset, calibration.breathMaxValOffset,
|
||||
BREATH_LO_LIMIT, BREATH_HI_LIMIT, &state.breathZero},
|
||||
{"BR ALT", state.breathAltSignal, calibration.breathAltThrValOffset, calibration.breathAltMaxValOffset,
|
||||
BREATH_LO_LIMIT, BREATH_HI_LIMIT, &state.breathAltZero},
|
||||
{"BITE",state.biteSignal, calibration.biteThrVal, calibration.biteMaxVal, BITE_LO_LIMIT, BITE_HI_LIMIT, NULL},
|
||||
{"PB DOWN",state.pbDnSignal, calibration.pbDnThrVal, calibration.pbDnMaxVal, PITCHB_LO_LIMIT, PITCHB_HI_LIMIT, NULL},
|
||||
{"PB UP", state.pbUpSignal, calibration.pbUpThrVal, calibration.pbUpMaxVal, PITCHB_LO_LIMIT, PITCHB_HI_LIMIT, NULL},
|
||||
{"EXTRA", state.extraSignal, calibration.extraThrVal, calibration.extraMaxVal, EXTRA_LO_LIMIT, EXTRA_HI_LIMIT, NULL},
|
||||
{"LEVER", state.leverSignal, calibration.leverThrVal, calibration.leverMaxVal, LEVER_LO_LIMIT, LEVER_HI_LIMIT, NULL},
|
||||
{"TOUCH", ctouchVal, calibration.ctouchThrVal, calibration.ctouchThrVal, CTOUCH_LO_LIMIT, CTOUCH_HI_LIMIT, NULL},
|
||||
}};
|
||||
|
||||
const MenuScreen adjustMenu = AdjustMenuScreen<8>("ADJUST", adjustValues);
|
||||
|
||||
void autoCalSelected() {
|
||||
}
|
||||
|
||||
//***********************************************************
|
||||
|
||||
|
||||
static void drawIndicator(int x, int row, int color) {
|
||||
display.fillTriangle(x-2, row+1, x+2, row+1, x, row+3, color);
|
||||
display.fillTriangle(x-2, row+10, x+2, row+10, x, row+7, color);
|
||||
}
|
||||
|
||||
static void drawAdjustIndicators(const AdjustValue &value, AdjustDrawing &drawing) {
|
||||
const int thrX = mapConstrain(value.thrVal, value.limitLow, value.limitHigh, 1, 127);
|
||||
const int maxX = mapConstrain(value.maxVal, value.limitLow, value.limitHigh, 1, 127);
|
||||
if (drawing.maxX != maxX) {
|
||||
drawIndicator(drawing.thrX, drawing.row, BLACK);
|
||||
drawIndicator(thrX, drawing.row, WHITE);
|
||||
drawing.maxX = maxX;
|
||||
}
|
||||
|
||||
if (drawing.thrX != thrX) {
|
||||
drawIndicator(drawing.thrX, drawing.row, BLACK);
|
||||
drawIndicator(thrX, drawing.row, WHITE);
|
||||
drawing.thrX = thrX;
|
||||
}
|
||||
}
|
||||
|
||||
static void drawAdjustTitle(const AdjustValue &value, AdjustDrawing &drawing, bool highlight) {
|
||||
display.setTextSize(1);
|
||||
if (highlight) {
|
||||
display.setTextColor(BLACK, WHITE);
|
||||
} else {
|
||||
display.setTextColor(WHITE, BLACK);
|
||||
}
|
||||
display.setCursor(0, drawing.row);
|
||||
display.println(value.title);
|
||||
}
|
||||
|
||||
static void drawAdjustValues(const AdjustValue &value, AdjustDrawing &drawing) {
|
||||
char buffer[13];
|
||||
snprintf(buffer, 13, "%d>%d<%d", value.thrVal, value.value, value.maxVal);
|
||||
display.setTextSize(1);
|
||||
display.setCursor(128 - 6 * strlen(buffer), drawing.row);
|
||||
display.println(buffer);
|
||||
|
||||
const int valX = mapConstrain(value.value, value.limitLow, value.limitHigh, 1, 127);
|
||||
if (drawing.valX != valX) {
|
||||
display.drawFastVLine(drawing.valX, drawing.row+4, 4, BLACK);
|
||||
display.drawFastVLine(valX, drawing.row+4, 4, WHITE);
|
||||
drawing.valX = valX;
|
||||
}
|
||||
}
|
||||
|
||||
static void drawAdjustFrame(int line) {
|
||||
display.drawLine(25,line,120,line,WHITE); // Top line
|
||||
display.drawLine(25,line+12,120,line+12,WHITE); // Bottom line
|
||||
|
||||
display.drawLine(25,line+1,25,line+2,WHITE);
|
||||
display.drawLine(120,line+1,120,line+2,WHITE);
|
||||
|
||||
display.drawLine(120,line+10,120,line+11,WHITE);
|
||||
display.drawLine(25,line+10,25,line+11,WHITE);
|
||||
}
|
||||
|
||||
static void drawAdjustRow(const AdjustValue &value, AdjustDrawing &drawing, bool highlight) {
|
||||
display.fillRect(0, drawing.row, 128, 21, BLACK);
|
||||
drawAdjustFrame(drawing.row);
|
||||
drawAdjustTitle(value, drawing, highlight);
|
||||
drawAdjustValues(value, drawing);
|
||||
drawAdjustIndicators(value, drawing);
|
||||
}
|
57
NuEVI/src/config.h
Normal file
57
NuEVI/src/config.h
Normal file
|
@ -0,0 +1,57 @@
|
|||
|
||||
#ifndef __CONFIG_H
|
||||
#define __CONFIG_H
|
||||
|
||||
|
||||
// Compile options, comment/uncomment to change
|
||||
|
||||
#define FIRMWARE_VERSION "0.0.1" // FIRMWARE VERSION NUMBER HERE <<<<<<<<<<<<<<<<<<<<<<<
|
||||
|
||||
#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 FILTER_FREQ 30.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
|
||||
|
||||
|
||||
// Statup buttons
|
||||
#define STARTUP_FACTORY_RESET 0x3
|
||||
#define STARTUP_CONFIG 0xC
|
||||
#define TEST_CONFIG 0xA
|
||||
#define DEBUG_CONFIG 0x1
|
||||
|
||||
// Buttons
|
||||
#define BTN_MENU 0x1
|
||||
#define BTN_VAL1 0x2
|
||||
#define BTN_VAL2 0x4
|
||||
#define BTN_PRESET 0x8
|
||||
|
||||
// Send breath CC data no more than every CC_BREATH_INTERVAL
|
||||
// milliseconds
|
||||
#define CC_BREATH_INTERVAL 5
|
||||
#define SLOW_MIDI_ADD 7
|
||||
#define CC_INTERVAL_PRIMARY 9
|
||||
#define CC_INTERVAL_PORT 13
|
||||
#define CC_INTERVAL_OTHER 37
|
||||
#define LVL_TIMER_INTERVAL 15
|
||||
#define CVPORTATUNE 2
|
||||
|
||||
#define maxSamplesNum 120
|
||||
|
||||
#define BREATH_LO_LIMIT 8000
|
||||
#define BREATH_HI_LIMIT 10000
|
||||
#define BITE_LO_LIMIT 0
|
||||
#define BITE_HI_LIMIT 1000
|
||||
#define PITCHB_LO_LIMIT 0
|
||||
#define PITCHB_HI_LIMIT 1000
|
||||
#define EXTRA_LO_LIMIT 0
|
||||
#define EXTRA_HI_LIMIT 1000
|
||||
#define CTOUCH_LO_LIMIT 0
|
||||
#define CTOUCH_HI_LIMIT 1000
|
||||
#define LEVER_LO_LIMIT 0
|
||||
#define LEVER_HI_LIMIT 1000
|
||||
|
||||
#endif
|
139
NuEVI/src/globals.h
Normal file
139
NuEVI/src/globals.h
Normal file
|
@ -0,0 +1,139 @@
|
|||
#ifndef __GLOBALS_H
|
||||
#define __GLOBALS_H
|
||||
|
||||
#include "wiring.h"
|
||||
#include <array>
|
||||
|
||||
|
||||
// The three states of our main state machine
|
||||
|
||||
// No note is sounding
|
||||
#define NOTE_OFF 1
|
||||
|
||||
// We've observed a transition from below to above the
|
||||
// threshold value. We wait a while to see how fast the
|
||||
// breath velocity is increasing
|
||||
#define RISE_WAIT 2
|
||||
|
||||
// A note is sounding
|
||||
#define NOTE_ON 3
|
||||
|
||||
enum PinkyMode : uint8_t {
|
||||
PBD = 12,
|
||||
GLD = 25,
|
||||
MOD = 26,
|
||||
QTN = 27,
|
||||
};
|
||||
|
||||
enum FingeringMode : uint8_t {
|
||||
EVI = 0,
|
||||
EVR = 1,
|
||||
TPT = 2,
|
||||
HRN = 3,
|
||||
};
|
||||
|
||||
enum RollerMode : uint8_t {
|
||||
HIGHEST = 1,
|
||||
HIGHEST_EXTEND = 2,
|
||||
HIGHEST_PAIR = 3,
|
||||
HIGHEST_PAIR_EXTEND = 4, // Releasing the roller from the highest/lowest moves
|
||||
PARTIAL = 5,
|
||||
PARTIAL_EXTEND = 6,
|
||||
};
|
||||
|
||||
enum VibratoMode : uint8_t {
|
||||
VSTART_DOWN = 0,
|
||||
VSTART_UP = 1,
|
||||
};
|
||||
|
||||
enum BreathMode : uint8_t {
|
||||
BREATH_STD = 0,
|
||||
BREATH_LSB = 1,
|
||||
BREATH_AT = 2,
|
||||
BREATH_LSB_AT = 3,
|
||||
};
|
||||
|
||||
enum ExtraControl : uint8_t {
|
||||
OFF = 0,
|
||||
VIBRATO = 1,
|
||||
GLIDE = 2,
|
||||
CC = 3,
|
||||
};
|
||||
|
||||
enum PolySelect : uint8_t {
|
||||
EHarmonizerOff = 0,
|
||||
EDuo = 1,
|
||||
EChord = 2,
|
||||
};
|
||||
|
||||
enum PortamentoMode : uint8_t {
|
||||
POFF = 0,
|
||||
PON = 1,
|
||||
PSWITCH_ONLY = 2,
|
||||
PGLIDE_ONLY = 3,
|
||||
};
|
||||
|
||||
struct instrument_state_t {
|
||||
int mainState; // The state of the main state machine
|
||||
uint8_t patch; // 1-128
|
||||
byte activeMIDIchannel = 1; // MIDI channel
|
||||
byte activeNote = 0; // note playing
|
||||
byte activePatch = 0;
|
||||
byte doPatchUpdate = 0;
|
||||
int8_t transpose = 0;
|
||||
uint8_t octave = 0;
|
||||
PolySelect polyMode = PolySelect::EHarmonizerOff;
|
||||
|
||||
// Raw sensor signals
|
||||
int16_t breathSignal = 0; // breath level (smoothed) not mapped to CC value
|
||||
int16_t breathAltSignal = 0;
|
||||
int16_t biteSignal = 0; // capacitance data from bite sensor, for midi cc and threshold checks
|
||||
int16_t leverSignal = 0;
|
||||
int16_t pbUpSignal = 0;
|
||||
int16_t pbDnSignal = 0;
|
||||
int16_t extraSignal = 0;
|
||||
int16_t vibSignal = 0;
|
||||
|
||||
// MIDI values
|
||||
int breathCCVal = 0;
|
||||
byte biteVal = 0; // keep track and make sure we send CC with 0 value when off threshold
|
||||
byte portamentoVal = 0; // keep track and make sure we send CC with 0 value when off threshold
|
||||
byte extraVal = 0; // keep track and make sure we send CC with 0 value when off threshold
|
||||
byte leverVal = 0; // keep track and make sure we send CC with 0 value when off threshold
|
||||
int pitchBend = 8192;
|
||||
int pbSend = 8192; // Pitch bend actually sent, modified by vibrato, etc
|
||||
|
||||
// Key states
|
||||
byte quarterToneTrigger;
|
||||
byte pinkyKey = 0;
|
||||
|
||||
// CV values
|
||||
int cvPitch;
|
||||
int targetPitch;
|
||||
|
||||
// Calibration
|
||||
int16_t breathZero; // this gets auto calibrated in setup
|
||||
int16_t breathThrVal; // this gets auto calibrated in setup
|
||||
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
|
||||
int16_t breathAltMaxVal; // this gets auto calibrated in setup
|
||||
|
||||
int16_t vibThr; // this gets auto calibrated in setup
|
||||
int16_t vibThrLo;
|
||||
int16_t vibZero;
|
||||
int16_t vibZeroBite;
|
||||
int16_t vibThrBite;
|
||||
int16_t vibThrBiteLo;
|
||||
};
|
||||
|
||||
extern instrument_state_t state;
|
||||
|
||||
extern const std::array<const unsigned short*, 13> curves;
|
||||
extern const unsigned short curveIn[];
|
||||
|
||||
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)
|
||||
|
||||
#endif
|
131
NuEVI/src/hardware.cpp
Normal file
131
NuEVI/src/hardware.cpp
Normal file
|
@ -0,0 +1,131 @@
|
|||
#include <Wire.h>
|
||||
#include "hardware.h"
|
||||
|
||||
#define ENCODER_OPTIMIZE_INTERRUPTS
|
||||
#include <Encoder.h>
|
||||
#include "menu.h"
|
||||
#include "config.h"
|
||||
#include "FilterOnePole.h" // for the breath signal low-pass filtering, from https://github.com/JonHub/Filters
|
||||
FilterOnePole breathFilter;
|
||||
FilterOnePole breathAltFilter;
|
||||
|
||||
Adafruit_MPR121 touchSensorKeys = Adafruit_MPR121();
|
||||
Adafruit_MPR121 touchSensorUtil = Adafruit_MPR121();
|
||||
Adafruit_MPRLS pressureSensorMain = Adafruit_MPRLS();
|
||||
Adafruit_MPRLS pressureSensorAlt = Adafruit_MPRLS();
|
||||
|
||||
byte drawingMemory[numLeds*3]; // 3 bytes per LED
|
||||
DMAMEM byte displayMemory[numLeds*12]; // 12 bytes per LED
|
||||
WS2812Serial ledStrip(numLeds, displayMemory, drawingMemory, ledStripPin, WS2812_GRB);
|
||||
Adafruit_ICM20948 icmSensor;
|
||||
Adafruit_Sensor *accelSensor;
|
||||
Encoder knobs[] = {
|
||||
Encoder(e1aPin, e1bPin),
|
||||
Encoder(e2aPin, e2bPin),
|
||||
Encoder(e3aPin, e3bPin),
|
||||
Encoder(e4aPin, e4bPin),
|
||||
};
|
||||
|
||||
void errorWait();
|
||||
|
||||
|
||||
void initHardware() {
|
||||
MainI2CBus.setClock(1000000);
|
||||
AuxI2CBus.setClock(1000000);
|
||||
pinMode(statusLedPin,OUTPUT); // Teensy onboard LED
|
||||
|
||||
// Buttons
|
||||
pinMode(b1Pin,INPUT_PULLUP);
|
||||
pinMode(b2Pin,INPUT_PULLUP);
|
||||
pinMode(b3Pin,INPUT_PULLUP);
|
||||
pinMode(b4Pin,INPUT_PULLUP);
|
||||
|
||||
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
|
||||
|
||||
ledStrip.begin();
|
||||
|
||||
if (!touchSensorKeys.begin(KeysI2CAddr, &MainI2CBus)) {
|
||||
displayError("Keys touch error");
|
||||
errorWait();
|
||||
}
|
||||
|
||||
if (!touchSensorUtil.begin(UtilI2CAddr, &MainI2CBus)) {
|
||||
displayError("Roller/Util touch error");
|
||||
errorWait();
|
||||
}
|
||||
|
||||
if (!pressureSensorMain.begin(MPRLS_DEFAULT_ADDR, &MainI2CBus)) {
|
||||
displayError("Main pressure sensor error");
|
||||
errorWait();
|
||||
}
|
||||
|
||||
if (!pressureSensorAlt.begin(MPRLS_DEFAULT_ADDR, &AuxI2CBus)) {
|
||||
displayError("Alt pressure sensor error");
|
||||
errorWait();
|
||||
}
|
||||
|
||||
if (!icmSensor.begin_I2C(ICM20948_I2CADDR_DEFAULT, &MainI2CBus)) {
|
||||
displayError("ICM sensor error");
|
||||
errorWait();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Return true if the given button bitmask is pressed
|
||||
*/
|
||||
bool checkButtonState(uint8_t mask) {
|
||||
return buttonState() & mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the state of the switches (note that they are active low, so we invert the values)
|
||||
*/
|
||||
uint8_t buttonState() {
|
||||
return 0x0f ^(digitalRead(b1Pin)
|
||||
| (digitalRead(b2Pin) << 1)
|
||||
| (digitalRead(b3Pin) << 2)
|
||||
| (digitalRead(b4Pin) << 3));
|
||||
}
|
||||
|
||||
|
||||
void errorWait() {
|
||||
while (1) {
|
||||
if (!digitalRead(b1Pin)) {
|
||||
_reboot_Teensyduino_(); // reboot to program mode if b1 pressed
|
||||
}
|
||||
|
||||
if (!digitalRead(b4Pin)) {
|
||||
break; // Continue if b4 pressed
|
||||
}
|
||||
|
||||
delay(10);
|
||||
}
|
||||
}
|
||||
|
||||
int readKnob(uint8_t n) {
|
||||
return knobs[n].readAndReset();
|
||||
}
|
||||
|
||||
int readTouchKey(uint8_t n) {
|
||||
return CAP_SENS_ABSOLUTE_MAX - touchSensorKeys.filteredData(n);
|
||||
}
|
||||
int readTouchUtil(uint8_t n) {
|
||||
return CAP_SENS_ABSOLUTE_MAX - touchSensorUtil.filteredData(n);
|
||||
}
|
||||
|
||||
uint16_t keysTouched() {
|
||||
return touchSensorKeys.touched();
|
||||
}
|
||||
|
||||
uint16_t utilTouched() {
|
||||
return touchSensorKeys.touched();
|
||||
}
|
||||
|
||||
int readPressure() {
|
||||
return breathFilter.input(pressureSensorMain.readPressure()) * PRESSURE_SENS_MULTIPLIER;
|
||||
}
|
||||
|
||||
int readAltPressure() {
|
||||
return breathAltFilter.input(pressureSensorAlt.readPressure()) * PRESSURE_SENS_MULTIPLIER;
|
||||
}
|
96
NuEVI/src/hardware.h
Normal file
96
NuEVI/src/hardware.h
Normal file
|
@ -0,0 +1,96 @@
|
|||
#ifndef __HARDWARE_H
|
||||
#define __HARDWARE_H
|
||||
|
||||
#include <Adafruit_MPR121.h>
|
||||
#include <Adafruit_MPRLS.h>
|
||||
#include <Adafruit_ICM20X.h>
|
||||
#include <WS2812Serial.h>
|
||||
#include <Adafruit_ICM20948.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Hardware sensor definitions
|
||||
// TODO: remove these
|
||||
|
||||
extern WS2812Serial ledStrip;
|
||||
extern Adafruit_Sensor *accelSensor;
|
||||
extern Adafruit_ICM20948 icmSensor;
|
||||
|
||||
void initHardware();
|
||||
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);
|
||||
int readTouchKey(uint8_t n);
|
||||
int readTouchUtil(uint8_t n);
|
||||
uint16_t keysTouched();
|
||||
uint16_t utilTouched();
|
||||
int readPressure();
|
||||
int readAltPressure();
|
||||
|
||||
// xEVI hardware setup
|
||||
|
||||
// I2C
|
||||
#define MainI2CBus Wire1
|
||||
#define AuxI2CBus Wire
|
||||
#define KeysI2CAddr 0x5B
|
||||
#define UtilI2CAddr 0x5A
|
||||
|
||||
// Digital pins for encoder buttons
|
||||
#define b1Pin 0
|
||||
#define b2Pin 2
|
||||
#define b3Pin 3
|
||||
#define b4Pin 4
|
||||
|
||||
// Digital pins for encoder quadrature
|
||||
#define e1aPin 5
|
||||
#define e2aPin 6
|
||||
#define e3aPin 7
|
||||
#define e4aPin 8
|
||||
|
||||
#define e1bPin 20
|
||||
#define e2bPin 21
|
||||
#define e3bPin 22
|
||||
#define e4bPin 23
|
||||
|
||||
// CV pins
|
||||
#define cvGatePin 9
|
||||
#define cvPitchPin 10
|
||||
#define cvBreathPin 11
|
||||
#define cvBitePin 12
|
||||
|
||||
//Output pins for LEDs
|
||||
#define statusLedPin 13
|
||||
#define ledStripPin 1
|
||||
#define numLeds 8
|
||||
|
||||
// Key pins
|
||||
// RH keys
|
||||
#define K1Pin 0
|
||||
#define K2Pin 1
|
||||
#define K3Pin 2
|
||||
#define K4Pin 3
|
||||
#define K5Pin 4
|
||||
#define K6Pin 5
|
||||
#define K7Pin 6
|
||||
#define K8Pin 7
|
||||
|
||||
// LH keys
|
||||
#define K9Pin 8
|
||||
#define K10Pin 9
|
||||
#define K11Pin 10
|
||||
#define K12Pin 11
|
||||
|
||||
// Octave roller pins
|
||||
#define R1Pin 0
|
||||
#define R2Pin 1
|
||||
#define R3Pin 2
|
||||
#define R4Pin 3
|
||||
#define R5Pin 4
|
||||
#define R6Pin 5
|
||||
|
||||
// Additional pins
|
||||
#define bitePin 6
|
||||
#define pbUpPin 7
|
||||
#define pbDnPin 8
|
||||
#define vibratoPin 9
|
||||
|
||||
#endif
|
49
NuEVI/src/led.cpp
Normal file
49
NuEVI/src/led.cpp
Normal file
|
@ -0,0 +1,49 @@
|
|||
#include <Arduino.h>
|
||||
#include "hardware.h"
|
||||
#include "globals.h"
|
||||
#include "config.h"
|
||||
|
||||
void singleLED(int n, int color) {
|
||||
}
|
||||
|
||||
void ledFullMeter(byte indicatedValue, int color){
|
||||
}
|
||||
|
||||
void ledHalfMeter(int n, byte indicatedValue, int color){
|
||||
}
|
||||
|
||||
void ledQuarterMeter(int n, byte indicatedValue, int color){
|
||||
}
|
||||
|
||||
void statusLedOn() {
|
||||
digitalWrite(statusLedPin, HIGH);
|
||||
}
|
||||
|
||||
void statusLedOff() {
|
||||
digitalWrite(statusLedPin, LOW);
|
||||
}
|
||||
|
||||
void statusLed(bool state) {
|
||||
digitalWrite(statusLedPin, state);
|
||||
}
|
||||
|
||||
void statusLedFlip() {
|
||||
digitalWrite(statusLedPin, !digitalRead(statusLedPin));
|
||||
}
|
||||
|
||||
void statusLedFlash(uint16_t delayTime) {
|
||||
statusLedOff();
|
||||
delay(delayTime/2);
|
||||
statusLedOn();
|
||||
delay(delayTime/2);
|
||||
}
|
||||
|
||||
void statusLedBlink() {
|
||||
statusLedFlash(300);
|
||||
statusLedFlash(300);
|
||||
}
|
||||
|
||||
void updateSensorLEDs() {
|
||||
ledHalfMeter(1, state.breathCCVal, 0x00FF00);
|
||||
ledQuarterMeter(3, state.biteVal, 0x0000FF);
|
||||
}
|
0
NuEVI/led.h → NuEVI/src/led.h
Executable file → Normal file
0
NuEVI/led.h → NuEVI/src/led.h
Executable file → Normal file
755
NuEVI/src/menu.cpp
Normal file
755
NuEVI/src/menu.cpp
Normal file
|
@ -0,0 +1,755 @@
|
|||
#include <Adafruit_GFX.h>
|
||||
#include <Adafruit_SSD1306.h>
|
||||
#include <Adafruit_MPR121.h>
|
||||
#include <Arduino.h>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
|
||||
#include "menu.h"
|
||||
#include "hardware.h"
|
||||
#include "config.h"
|
||||
#include "settings.h"
|
||||
#include "globals.h"
|
||||
#include "midi.h"
|
||||
#include "led.h"
|
||||
|
||||
// constants
|
||||
const unsigned long debounceDelay = 30; // the debounce time; increase if the output flickers
|
||||
const unsigned long buttonRepeatInterval = 50;
|
||||
const unsigned long buttonRepeatDelay = 400;
|
||||
const unsigned long cursorBlinkInterval = 300; // the cursor blink toggle interval time
|
||||
const unsigned long patchViewTimeUp = 2000; // ms until patch view shuts off
|
||||
const unsigned long menuTimeUp = 60000; // menu shuts off after one minute of button inactivity
|
||||
|
||||
static unsigned long menuTime = 0;
|
||||
static unsigned long patchViewTime = 0;
|
||||
unsigned long cursorBlinkTime = 0; // the last time the cursor was toggled
|
||||
|
||||
std::array<const char *, 128> CC_NAMES = {
|
||||
"Bank Select", // 0
|
||||
"Mod Wheel", // 1
|
||||
"Breath", // 2
|
||||
"Undefined", // 3
|
||||
"Foot Pedal", // 4
|
||||
"Port. Time", // 5
|
||||
"Data Entry", // 6
|
||||
"Volume", // 7
|
||||
"Balance", // 8
|
||||
"Undefined", // 9
|
||||
"Pan", // 10
|
||||
"Expression", // 11
|
||||
"Effect 1", // 12
|
||||
"Effect 2", // 13
|
||||
"Undefined", // 14
|
||||
"Undefined", // 15
|
||||
"GP 1", // 16
|
||||
"GP 2", // 17
|
||||
"GP 3", // 18
|
||||
"GP 3", // 19
|
||||
"Undefined", // 20
|
||||
"Undefined", // 21
|
||||
"Undefined", // 22
|
||||
"Undefined", // 23
|
||||
"Undefined", // 24
|
||||
"Undefined", // 25
|
||||
"Undefined", // 26
|
||||
"Undefined", // 27
|
||||
"Undefined", // 28
|
||||
"Undefined", // 29
|
||||
"Undefined", // 30
|
||||
"Undefined", // 31
|
||||
"LSB 0", // 32
|
||||
"LSB 1", // 33
|
||||
"LSB 2", // 34
|
||||
"LSB 3", // 35
|
||||
"LSB 4", // 36
|
||||
"LSB 5", // 37
|
||||
"LSB 6", // 38
|
||||
"LSB 7", // 39
|
||||
"LSB 8", // 40
|
||||
"LSB 9", // 41
|
||||
"LSB 10", // 42
|
||||
"LSB 11", // 43
|
||||
"LSB 12", // 44
|
||||
"LSB 13", // 45
|
||||
"LSB 14", // 46
|
||||
"LSB 15", // 47
|
||||
"LSB 16", // 48
|
||||
"LSB 17", // 49
|
||||
"LSB 18", // 50
|
||||
"LSB 19", // 51
|
||||
"LSB 20", // 52
|
||||
"LSB 21", // 53
|
||||
"LSB 22", // 54
|
||||
"LSB 23", // 55
|
||||
"LSB 24", // 56
|
||||
"LSB 25", // 57
|
||||
"LSB 26", // 58
|
||||
"LSB 27", // 59
|
||||
"LSB 28", // 60
|
||||
"LSB 29", // 61
|
||||
"LSB 30", // 62
|
||||
"LSB 31", // 63
|
||||
"Sustain", // 64
|
||||
"Portamento", // 65
|
||||
"Sostenuto", // 66
|
||||
"Soft Pedal", // 67
|
||||
"Legato", // 68
|
||||
"Hold 2", // 69
|
||||
"Variation", // 70
|
||||
"Resonance", // 71
|
||||
"Release", // 72
|
||||
"Attack", // 73
|
||||
"Cutoff", // 74
|
||||
"Sound 6", // 75
|
||||
"Sound 7", // 76
|
||||
"Sound 8", // 77
|
||||
"Sound 9", // 78
|
||||
"Sound 10", // 79
|
||||
"Decay", // 80
|
||||
"Hi Pass", // 81
|
||||
"GP Button 3", // 82
|
||||
"GP Button 4", // 83
|
||||
"Port. Amount", // 84
|
||||
"Undefined", // 85
|
||||
"Undefined", // 86
|
||||
"Undefined", // 87
|
||||
"Undefined", // 88
|
||||
"Undefined", // 89
|
||||
"Undefined", // 90
|
||||
"Reverb", // 91
|
||||
"Tremolo", // 92
|
||||
"Chorus", // 93
|
||||
"Detune", // 94
|
||||
"Phaser", // 95
|
||||
"Data Inc", // 96
|
||||
"Data Dec", // 97
|
||||
"NRPN LSB", // 98
|
||||
"NRPN MSB", // 99
|
||||
"RP LSB", // 100
|
||||
"RP MSB", // 101
|
||||
"Undefined", // 102
|
||||
"Undefined", // 103
|
||||
"Undefined", // 104
|
||||
"Undefined", // 105
|
||||
"Undefined", // 106
|
||||
"Undefined", // 107
|
||||
"Undefined", // 108
|
||||
"Undefined", // 109
|
||||
"Undefined", // 110
|
||||
"Undefined", // 111
|
||||
"Undefined", // 112
|
||||
"Undefined", // 113
|
||||
"Undefined", // 114
|
||||
"Undefined", // 115
|
||||
"Undefined", // 116
|
||||
"Undefined", // 117
|
||||
"Undefined", // 118
|
||||
"Undefined", // 119
|
||||
"All Sound Off", // 120
|
||||
"All CCC Off", // 121
|
||||
"Keyboard On", // 122
|
||||
"All Notes Off", // 123
|
||||
"Omni Mode Off", // 124
|
||||
"Omni Mode On", // 125
|
||||
"Mono", // 126
|
||||
"Poly Mode", // 127
|
||||
};
|
||||
|
||||
// 'NuEVI' or 'NuRAD' logo
|
||||
#define LOGO16_GLCD_WIDTH 128
|
||||
#define LOGO16_GLCD_HEIGHT 64
|
||||
static const unsigned char PROGMEM nuevi_logo_bmp[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xe3, 0x60, 0x00, 0x07, 0x73, 0x60, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xe3, 0x60, 0x00, 0x0e, 0xe3, 0x60, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x03, 0x60, 0x00, 0x1d, 0xc3, 0x60, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xbf, 0xff, 0xff, 0xe3, 0x60, 0x00, 0x3b, 0x83, 0x60, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xbf, 0xff, 0xff, 0xe3, 0x60, 0x00, 0x77, 0x03, 0x60, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xb0, 0x00, 0x00, 0x03, 0x60, 0x00, 0xee, 0x03, 0x60, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xb0, 0x00, 0x00, 0x03, 0x60, 0x01, 0xdc, 0x03, 0x60, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xb0, 0x00, 0x00, 0x03, 0x60, 0x03, 0xb8, 0x03, 0x60, 0x00,
|
||||
0x00, 0x00, 0x00, 0x20, 0x00, 0x01, 0xb0, 0x00, 0x00, 0x03, 0x60, 0x07, 0x70, 0x03, 0x60, 0x00,
|
||||
0x00, 0x00, 0x00, 0x60, 0x00, 0x01, 0xbf, 0xff, 0xff, 0xe3, 0x60, 0x0e, 0xe0, 0x03, 0x60, 0x00,
|
||||
0x00, 0x00, 0x00, 0x60, 0x00, 0x01, 0xbf, 0xff, 0xff, 0xe3, 0x60, 0x1d, 0xc0, 0x03, 0x60, 0x00,
|
||||
0x00, 0x03, 0x00, 0x60, 0x00, 0x01, 0x80, 0x00, 0x00, 0x03, 0x60, 0x3b, 0x80, 0x03, 0x60, 0x00,
|
||||
0x00, 0x03, 0x00, 0xe0, 0x00, 0x01, 0xbf, 0xff, 0xff, 0xe3, 0x60, 0x77, 0x00, 0x03, 0x60, 0x00,
|
||||
0x00, 0x03, 0x00, 0xc0, 0x00, 0x01, 0xbf, 0xff, 0xff, 0xe3, 0x60, 0xee, 0x00, 0x03, 0x60, 0x00,
|
||||
0x00, 0x03, 0x80, 0xc0, 0x00, 0x01, 0xb0, 0x00, 0x00, 0x03, 0x61, 0xdc, 0x00, 0x03, 0x60, 0x00,
|
||||
0x00, 0x07, 0x80, 0xc0, 0x00, 0x01, 0xb0, 0x00, 0x00, 0x03, 0x63, 0xb8, 0x00, 0x03, 0x60, 0x00,
|
||||
0x00, 0x07, 0xc0, 0xc0, 0x00, 0x01, 0xb0, 0x00, 0x00, 0x03, 0x67, 0x70, 0x00, 0x03, 0x60, 0x00,
|
||||
0x00, 0x06, 0xc0, 0xc0, 0x00, 0x01, 0xb0, 0x00, 0x00, 0x03, 0x6e, 0xe0, 0x00, 0x03, 0x60, 0x00,
|
||||
0x00, 0x06, 0x60, 0xc1, 0x01, 0x01, 0xb0, 0x00, 0x00, 0x03, 0x7d, 0xc0, 0x00, 0x03, 0x60, 0x00,
|
||||
0x00, 0x06, 0x30, 0xc3, 0x03, 0x01, 0xbf, 0xff, 0xff, 0xe3, 0x7b, 0x80, 0x00, 0x03, 0x60, 0x00,
|
||||
0x00, 0x0c, 0x30, 0xc3, 0x07, 0x01, 0xbf, 0xff, 0xff, 0xe3, 0x77, 0x00, 0x00, 0x03, 0x60, 0x00,
|
||||
0x00, 0x0c, 0x1c, 0xc3, 0x06, 0x01, 0x80, 0x00, 0x00, 0x03, 0x0e, 0x00, 0x00, 0x03, 0x60, 0x00,
|
||||
0x00, 0x0c, 0x0c, 0xc2, 0x0e, 0x01, 0xff, 0xff, 0xff, 0xe3, 0xfc, 0x00, 0x00, 0x03, 0x60, 0x00,
|
||||
0x00, 0x0c, 0x0e, 0xc6, 0x1e, 0x01, 0xff, 0xff, 0xff, 0xe3, 0xf8, 0x00, 0x00, 0x03, 0x60, 0x00,
|
||||
0x00, 0x0c, 0x07, 0xc6, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x0c, 0x03, 0xc6, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x0c, 0x01, 0xc7, 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x0c, 0x00, 0xc7, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x0c, 0x00, 0x03, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
|
||||
extern void readSwitches(void);
|
||||
#define OLED_RESET 4
|
||||
Adafruit_SSD1306 display(128, 64, &Wire1, OLED_RESET,1000000,1000000);
|
||||
|
||||
MenuScreen *currentMenu = NULL;
|
||||
|
||||
void plotSubOption(const char* label, const char* unit = nullptr) {
|
||||
int text_x, unit_x;
|
||||
int label_pixel_width = strlen(label)*12;
|
||||
|
||||
if(unit == nullptr) {
|
||||
text_x = 96 - (label_pixel_width/2);
|
||||
} else {
|
||||
int unit_pixel_width = strlen(unit)*6;
|
||||
int halfSum = (label_pixel_width + unit_pixel_width)/2;
|
||||
text_x = 96 - halfSum;
|
||||
unit_x = 96 + halfSum - unit_pixel_width;
|
||||
display.setCursor(unit_x,40);
|
||||
display.setTextSize(1);
|
||||
display.println(unit);
|
||||
}
|
||||
|
||||
display.setTextSize(2);
|
||||
display.setCursor(text_x,33);
|
||||
display.println(label);
|
||||
}
|
||||
|
||||
template<size_t N>
|
||||
static void plotMenuEntries(std::array<MenuScreen, N> entries, size_t cursorPos) {
|
||||
display.fillRect(0, MENU_HEADER_OFFSET, 63, 64-MENU_HEADER_OFFSET, BLACK);
|
||||
display.setTextSize(1);
|
||||
|
||||
size_t scrollPos = 0;
|
||||
if (entries.size() >= MENU_NUM_ROWS) {
|
||||
if ((cursorPos - scrollPos) > (MENU_NUM_ROWS-2) ) {
|
||||
scrollPos = cursorPos - (MENU_NUM_ROWS-2);
|
||||
} else if( (cursorPos - scrollPos) < 1) {
|
||||
scrollPos = cursorPos - 1;
|
||||
}
|
||||
|
||||
scrollPos = constrain(scrollPos, 0, entries.size() - MENU_NUM_ROWS);
|
||||
}
|
||||
|
||||
int row = 0;
|
||||
int end = constrain(scrollPos + MENU_NUM_ROWS, 0, entries.size());
|
||||
for (size_t i = scrollPos; i < end; i++, row++) {
|
||||
int rowPixel = (row)*MENU_ROW_HEIGHT + MENU_HEADER_OFFSET;
|
||||
display.setCursor(0,rowPixel);
|
||||
if (cursorPos == i) {
|
||||
display.setTextColor(BLACK, WHITE);
|
||||
} else {
|
||||
display.setTextColor(WHITE);
|
||||
}
|
||||
|
||||
display.println(entries[i].title());
|
||||
}
|
||||
}
|
||||
|
||||
class AboutMenu : public MenuScreen {
|
||||
const char *title() {
|
||||
return "ABOUT";
|
||||
}
|
||||
|
||||
void update(InputState input, bool redraw) {
|
||||
if (redraw) {
|
||||
display.clearDisplay();
|
||||
display.setCursor(49,0);
|
||||
display.setTextColor(WHITE);
|
||||
display.setTextSize(0);
|
||||
display.println("xEVI");
|
||||
display.setCursor(16,12);
|
||||
display.print("firmware v.");
|
||||
display.println(FIRMWARE_VERSION);
|
||||
display.print("eeprom v.");
|
||||
display.println(EEPROM_VERSION);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<size_t N>
|
||||
class MainMenu : public MenuScreen {
|
||||
public:
|
||||
MainMenu(std::array<MenuScreen, N> entries) : _entries(entries) { }
|
||||
void update(InputState input, bool redraw) {
|
||||
if (input.changed && input.btnMenu) {
|
||||
_inSubMenu = !_inSubMenu;
|
||||
input.changed = false;
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
if (_inSubMenu) {
|
||||
_entries[_cursorPos].update(input, redraw);
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.changed) {
|
||||
_cursorPos = (_cursorPos + input.knobMenu) % _entries.size();
|
||||
}
|
||||
|
||||
draw(redraw);
|
||||
};
|
||||
|
||||
const char *title() {
|
||||
return "MENU";
|
||||
}
|
||||
|
||||
private:
|
||||
void draw(bool redraw) {
|
||||
if (redraw) {
|
||||
display.clearDisplay();
|
||||
display.setTextSize(1);
|
||||
display.setCursor(0,0);
|
||||
display.drawLine(0, MENU_ROW_HEIGHT, 127, MENU_ROW_HEIGHT, WHITE);
|
||||
display.println("MENU");
|
||||
}
|
||||
|
||||
plotMenuEntries(_entries, _cursorPos);
|
||||
|
||||
}
|
||||
|
||||
bool _inSubMenu = false;
|
||||
size_t _cursorPos;
|
||||
std::array<MenuScreen, N> _entries;
|
||||
};
|
||||
|
||||
template<size_t N>
|
||||
class SubMenu : public MenuScreen {
|
||||
public:
|
||||
SubMenu(const char *title, std::array<MenuScreen, N> entries) : _title(title), _entries(entries) {}
|
||||
void update(InputState input, bool redraw) {
|
||||
bool redrawValue = false;
|
||||
if (input.changed && input.knobMenu != 0) {
|
||||
_cursorPos = (_cursorPos + input.knobMenu) % _entries.size();
|
||||
draw(false);
|
||||
input.changed = false;
|
||||
redrawValue = true;
|
||||
}
|
||||
|
||||
if (redraw) {
|
||||
draw(redraw);
|
||||
}
|
||||
|
||||
_entries[_cursorPos].update(input, redrawValue);
|
||||
};
|
||||
|
||||
const char *title() {
|
||||
return _title;
|
||||
}
|
||||
|
||||
private:
|
||||
void draw(bool redraw) {
|
||||
if (redraw) {
|
||||
display.clearDisplay();
|
||||
display.setTextSize(1);
|
||||
display.setCursor(0,0);
|
||||
display.drawLine(0, MENU_ROW_HEIGHT, 127, MENU_ROW_HEIGHT, WHITE);
|
||||
display.println(_title);
|
||||
}
|
||||
|
||||
plotMenuEntries(_entries, _cursorPos);
|
||||
}
|
||||
const char *_title;
|
||||
size_t _cursorPos;
|
||||
std::array<MenuScreen, N> _entries;
|
||||
};
|
||||
|
||||
template<
|
||||
size_t L,
|
||||
typename T
|
||||
>
|
||||
class ValueMenu : public MenuScreen {
|
||||
public:
|
||||
ValueMenu(
|
||||
const char * title, T &value,
|
||||
const T min, const T max, const bool wrap = false,
|
||||
const std::array<const char *, L> labels = {}
|
||||
) : _title(title), _value(value), _min(min), _max(max), _wrap(wrap), _labels(labels) {}
|
||||
void update(InputState input, bool redraw) {
|
||||
if (input.knobMenu) {
|
||||
_value = (_value + input.knobMenu);
|
||||
if (_value > _max) {
|
||||
_value = _min;
|
||||
} else if (_value < _min) {
|
||||
_value = _max;
|
||||
}
|
||||
|
||||
draw(redraw);
|
||||
} else if (redraw) {
|
||||
draw(redraw);
|
||||
}
|
||||
}
|
||||
private:
|
||||
T &_value;
|
||||
const T _min;
|
||||
const T _max;
|
||||
const bool _wrap;
|
||||
const std::array<const char *, L> _labels;
|
||||
const char *_title;
|
||||
|
||||
void draw(bool redraw) {
|
||||
if (redraw) {
|
||||
display.fillRect(63,11,64,52,BLACK);
|
||||
display.drawRect(63,11,64,52,WHITE);
|
||||
display.setTextSize(1);
|
||||
int len = strlen(this->_title);
|
||||
|
||||
display.setCursor(95-len*3,15);
|
||||
display.println(this->_title);
|
||||
}
|
||||
|
||||
char buffer[12];
|
||||
snprintf(buffer, 12, "%+d", _value);
|
||||
size_t label_idx = _value - _min;
|
||||
if (_labels.size() > 0 && label_idx >= 0 && label_idx <= _labels.size()) {
|
||||
plotSubOption(_labels[label_idx], buffer);
|
||||
} else {
|
||||
plotSubOption(buffer);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void drawFlash(int x, int y){
|
||||
display.drawLine(x+5,y,x,y+6,WHITE);
|
||||
display.drawLine(x,y+6,x+5,y+6,WHITE);
|
||||
display.drawLine(x,y+12,x+5,y+6,WHITE);
|
||||
}
|
||||
|
||||
void initDisplay() {
|
||||
|
||||
// by default, we'll generate the high voltage from the 3.3v line internally! (neat!)
|
||||
display.begin(SSD1306_SWITCHCAPVCC, 0x3D); // initialize with the I2C addr 0x3D (for the 128x64)
|
||||
|
||||
// Show image buffer on the display hardware.
|
||||
// Since the buffer is intialized with an Adafruit splashscreen
|
||||
// internally, this will display the splashscreen.
|
||||
|
||||
display.clearDisplay();
|
||||
#if defined(NURAD)
|
||||
display.drawBitmap(0,0,nurad_logo_bmp,LOGO16_GLCD_WIDTH,LOGO16_GLCD_HEIGHT,1);
|
||||
#else
|
||||
display.drawBitmap(0,0,nuevi_logo_bmp,LOGO16_GLCD_WIDTH,LOGO16_GLCD_HEIGHT,1);
|
||||
#endif
|
||||
display.display();
|
||||
}
|
||||
|
||||
void displayError(const char *error) {
|
||||
display.clearDisplay();
|
||||
display.setTextColor(WHITE);
|
||||
display.setTextSize(1);
|
||||
display.setCursor(0,0);
|
||||
display.println(error);
|
||||
display.display();
|
||||
Serial.print("ERROR: ");
|
||||
Serial.println(error);
|
||||
}
|
||||
|
||||
void showVersion() {
|
||||
display.setTextColor(WHITE);
|
||||
display.setTextSize(1);
|
||||
display.setCursor(85,52);
|
||||
display.print("v.");
|
||||
display.println(FIRMWARE_VERSION);
|
||||
display.display();
|
||||
}
|
||||
|
||||
static void clearSub(){
|
||||
display.fillRect(63,11,64,52,BLACK);
|
||||
}
|
||||
|
||||
static void clearSubValue() {
|
||||
display.fillRect(65, 24, 60, 37, BLACK);
|
||||
}
|
||||
|
||||
//***********************************************************
|
||||
const MenuScreen breathModeMenu = ValueMenu<4, BreathMode>("BREATH MODE", currentPreset->breathMode, 0, 1, true, {{ "STANDARD", "LSB", "AT", "LSB_AT" }});
|
||||
const MenuScreen breathCCMenu = ValueMenu<128, uint8_t>("BREATH CC", currentPreset->breathCC, 0, 127, true, CC_NAMES);
|
||||
const MenuScreen velocityMenu = ValueMenu<1, uint8_t>("VELOCITY", currentPreset->fixedVelocity, 0, 127, true, {{ "DYN" }});
|
||||
const MenuScreen curveMenu = ValueMenu<0, uint8_t>("CURVE", currentPreset->breathCurve, 0, 12); // TODO: curve display
|
||||
const MenuScreen velSmpDlMenu = ValueMenu<1, uint8_t>("VEL DELAY", currentPreset->velSmpDl, 0, 30, true, { "OFF" }); // TODO: unit ms
|
||||
const MenuScreen velBiasMenu = ValueMenu<1, uint8_t>("VEL BOOST", currentPreset->velBias, 0, 30, true, { "OFF" });
|
||||
const MenuScreen breathIntervalMenu = ValueMenu<0, uint8_t>("BR INTERV", currentPreset->breathInterval, 0, 30, true);
|
||||
const MenuScreen trill3Menu = ValueMenu<0, int8_t>("TRILL3", currentPreset->trill3_interval, 3, 4, true, {});
|
||||
const MenuScreen cvTuneMenu = ValueMenu<0, int8_t>("CV Tune", currentPreset->cvTune, -100, 100, false, {});
|
||||
const MenuScreen cvVibMenu = ValueMenu<0, uint8_t>("CV Vib LFO", currentPreset->cvVibRate, 0, 8, false, {});
|
||||
const MenuScreen cvScaleMenu = ValueMenu<0, int8_t>("CV SCALING", currentPreset->cvScale, -100, 100, false, {});
|
||||
const std::array<const char *, 25> transposeLabels = {
|
||||
"C>", "C#>", "D>", "D#>", "E>", "F>", "F#>", "G>", "G#>", "A>", "Bb>", "B>",
|
||||
">C<", "<C#", "<D", "<D#", "<E", "<F", "<F#", "<G", "<G#", "<A", "<Bb", "<B", "<C"
|
||||
};
|
||||
|
||||
const MenuScreen transposeMenu = ValueMenu<25, int8_t>("TRANSPOSE", state.transpose, -12, 12, true, transposeLabels);
|
||||
const MenuScreen octaveMenu = ValueMenu<0, uint8_t>("OCTAVE", state.octave, -3, 3, true, {});
|
||||
const MenuScreen midiMenu = ValueMenu<0, byte>("MIDI CH", currentPreset->MIDIchannel, 1, 16, false, {});
|
||||
const MenuScreen vibDepthMenu = ValueMenu<1, uint8_t>("DEPTH", currentPreset->vibratoDepth, 0, 9, true, {"OFF"});
|
||||
const MenuScreen vibRetnMenu = ValueMenu<1, uint8_t>("RETURN", currentPreset->vibRetn, 0, 4, true, {"OFF"});
|
||||
const MenuScreen vibSenseMenu = ValueMenu<0, uint8_t>("SENSE LVR", currentPreset->vibSens, 0, 12);
|
||||
const MenuScreen vibSquelchMenu = ValueMenu<0, uint8_t>("SQUELCH LVR", currentPreset->vibSquelch, 0, 12);
|
||||
const MenuScreen vibDirMenu = ValueMenu<2, VibratoMode>("DIRECTION", currentPreset->vibratoMode, VSTART_DOWN, VSTART_UP, true, { "START DOWN", "START UP"});
|
||||
const MenuScreen biteCtlMenu = ValueMenu<4, ExtraControl>("BITE CTL", currentPreset->biteControl, 0, 3, true, {{
|
||||
"OFF",
|
||||
"VIBRATO",
|
||||
"GLIDE",
|
||||
"CC"
|
||||
}});
|
||||
const MenuScreen biteCCMenu = ValueMenu<128, uint8_t>("BITE CC", currentPreset->biteCC, 0, 127, true, CC_NAMES);
|
||||
const MenuScreen leverCtlMenu = ValueMenu<4, ExtraControl>("LEVER CTL", currentPreset->leverControl, 0, 3, true, {
|
||||
"OFF",
|
||||
"VIBRATO",
|
||||
"GLIDE",
|
||||
"CC"
|
||||
});
|
||||
const MenuScreen leverCCMenu = ValueMenu<128, uint8_t>("LEVER CC", currentPreset->leverCC, 0, 127, true, CC_NAMES);
|
||||
const MenuScreen portMenu = ValueMenu<4, PortamentoMode>("GLIDE MOD", currentPreset->portamentoMode, 0, 3, true, {
|
||||
"OFF",
|
||||
"ON",
|
||||
"SWITCH_ONLY",
|
||||
"GLIDE_ONLY",
|
||||
});
|
||||
const MenuScreen portLimitMenu = ValueMenu<0, uint8_t>("GLIDE LMT", currentPreset->portamentoLimit, 1, 127, true);
|
||||
const MenuScreen pitchBendMenu = ValueMenu<0, uint8_t>("PITCHBEND", currentPreset->PBdepth, 0, 12, true);
|
||||
const MenuScreen extraCtlMenu = ValueMenu<4, ExtraControl>("EXCT CC A", currentPreset->extraControl, 0,4, true, {
|
||||
"OFF",
|
||||
"ON",
|
||||
"SWITCH_ONLY",
|
||||
"GLIDE_ONLY",
|
||||
});
|
||||
const MenuScreen extraCCMenu = ValueMenu<128, uint8_t>("EXCT CC", currentPreset->extraCC, 0,4, true, CC_NAMES);
|
||||
const MenuScreen deglitchMenu = ValueMenu<1, uint8_t>("DEGLITCH", currentPreset->deglitch, 0, 70, true, {"OFF"});
|
||||
const MenuScreen pinkyMenu = ValueMenu<29, uint8_t>("PINKY KEY", currentPreset->pinkySetting, 0, 29, true, {
|
||||
"-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 MenuScreen fingeringMenu = ValueMenu<4, FingeringMode>("FINGERING", currentPreset->fingering, 0, 3, true, {
|
||||
"EVI",
|
||||
"EVR",
|
||||
"TPT",
|
||||
"HRN",
|
||||
});
|
||||
const MenuScreen rollerMenu = ValueMenu<6, RollerMode>("ROLLRMODE", currentPreset->rollerMode, 1, 6, true, {
|
||||
"HIGHEST",
|
||||
"HIGHEST_EXTEND",
|
||||
"HIGHEST_PAIR",
|
||||
"HIGHEST_PAIR_EXTEND",
|
||||
"PARTIAL",
|
||||
"PARTIAL_EXTEND",
|
||||
});
|
||||
|
||||
std::array<MenuScreen, 7> breathMenuEntries = {
|
||||
breathModeMenu,
|
||||
breathCCMenu,
|
||||
velocityMenu,
|
||||
curveMenu,
|
||||
velSmpDlMenu,
|
||||
velBiasMenu,
|
||||
breathIntervalMenu,
|
||||
};
|
||||
const MenuScreen breathMenu = SubMenu<7>("BREATH SETUP", breathMenuEntries);
|
||||
|
||||
const std::array<MenuScreen, 13> controlMenuEntries = {
|
||||
fingeringMenu,
|
||||
rollerMenu,
|
||||
biteCtlMenu,
|
||||
biteCCMenu,
|
||||
leverCtlMenu,
|
||||
leverCCMenu,
|
||||
extraCtlMenu,
|
||||
extraCCMenu,
|
||||
portMenu,
|
||||
portLimitMenu,
|
||||
deglitchMenu,
|
||||
pinkyMenu,
|
||||
pitchBendMenu
|
||||
};
|
||||
const MenuScreen controlMenu = SubMenu<13>("CONTROL SETUP", controlMenuEntries);
|
||||
|
||||
const std::array<MenuScreen, 5> vibratoMenuEntries = {
|
||||
vibDepthMenu,
|
||||
vibRetnMenu,
|
||||
vibDirMenu,
|
||||
vibSenseMenu,
|
||||
vibSquelchMenu,
|
||||
};
|
||||
const MenuScreen vibratoMenu = SubMenu<5>("VIBRATO", vibratoMenuEntries);
|
||||
|
||||
|
||||
const MenuScreen aboutMenu = AboutMenu();
|
||||
|
||||
std::array <MenuScreen, 4> extrasMenuEntries = {
|
||||
trill3Menu,
|
||||
cvTuneMenu,
|
||||
cvScaleMenu,
|
||||
cvVibMenu,
|
||||
};
|
||||
const MenuScreen extrasMenu = SubMenu<4>("EXTRAS", extrasMenuEntries);
|
||||
|
||||
|
||||
// Top-level screens
|
||||
const std::array<MenuScreen, 9> mainMenuEntries = {
|
||||
transposeMenu,
|
||||
octaveMenu,
|
||||
midiMenu,
|
||||
breathMenu,
|
||||
controlMenu,
|
||||
vibratoMenu,
|
||||
adjustMenu,
|
||||
extrasMenu,
|
||||
aboutMenu,
|
||||
};
|
||||
const MenuScreen mainMenuPage = MainMenu<9>(mainMenuEntries);
|
||||
// const MenuScreen patchPage
|
||||
// const MenuScreen presetPage
|
||||
// const MenuScreen ccPage
|
||||
|
||||
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) {
|
||||
|
||||
static uint32_t lastDebounceTime = 0; // the last time the output pin was toggled
|
||||
static uint32_t buttonRepeatTime = 0;
|
||||
static uint32_t buttonPressedTime = 0;
|
||||
static uint8_t lastDeumButtons = 0;
|
||||
static uint8_t deumButtonState = 0;
|
||||
static int lastKnobs[] = {0, 0, 0, 0};
|
||||
|
||||
InputState input;
|
||||
|
||||
uint8_t deumButtons = buttonState();
|
||||
// check to see if you just pressed the button
|
||||
// (i.e. the input went from LOW to HIGH), and you've waited long enough
|
||||
// since the last press to ignore any noise:
|
||||
|
||||
// If the switch changed, due to noise or pressing:
|
||||
if (deumButtons != lastDeumButtons) {
|
||||
// reset the debouncing timer
|
||||
lastDebounceTime = timeNow;
|
||||
}
|
||||
|
||||
if ((timeNow - lastDebounceTime) > debounceDelay) {
|
||||
// whatever the reading is at, it's been there for longer than the debounce
|
||||
// delay, so take it as the actual current state:
|
||||
|
||||
// if the button state has changed:
|
||||
if (deumButtons != deumButtonState) {
|
||||
// keys.current = deumButtons;
|
||||
input.btnMenu = deumButtons & BTN_MENU;
|
||||
input.btnVal1 = deumButtons & BTN_VAL1;
|
||||
input.btnVal2 = deumButtons & BTN_VAL2;
|
||||
input.btnPreset = deumButtons & BTN_PRESET;
|
||||
input.changed = true;
|
||||
|
||||
deumButtonState = deumButtons;
|
||||
menuTime = timeNow;
|
||||
buttonPressedTime = timeNow;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int val = readKnob(i);
|
||||
if (val != lastKnobs[i]) {
|
||||
input.changed = 1;
|
||||
switch (i) {
|
||||
case 0:
|
||||
input.knobMenu = val;
|
||||
break;
|
||||
case 1:
|
||||
input.knobVal1 = val;
|
||||
break;
|
||||
case 2:
|
||||
input.knobVal2 = val;
|
||||
break;
|
||||
case 3:
|
||||
input.knobPreset = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// save the reading. Next time through the loop, it'll be the lastButtonState:
|
||||
lastDeumButtons = deumButtons;
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void handleMenu(bool draw) {
|
||||
unsigned long timeNow = millis();
|
||||
|
||||
InputState input = readInput(timeNow);
|
||||
|
||||
// shut off menu system if not used for a while (changes not stored by exiting a setting manually will not be stored in EEPROM)
|
||||
if (currentMenu && ((timeNow - menuTime) > menuTimeUp)) {
|
||||
display.ssd1306_command(SSD1306_DISPLAYOFF);
|
||||
display.clearDisplay();
|
||||
currentMenu = NULL;
|
||||
}
|
||||
|
||||
if (currentMenu && (draw || input.changed)) {
|
||||
currentMenu->update(input, false);
|
||||
}
|
||||
}
|
44
NuEVI/src/menu.h
Normal file
44
NuEVI/src/menu.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
#ifndef __MENU_H
|
||||
#define __MENU_H
|
||||
|
||||
#include "wiring.h"
|
||||
|
||||
#define MENU_ROW_HEIGHT 9
|
||||
#define MENU_HEADER_OFFSET 12
|
||||
#define MENU_NUM_ROWS 6
|
||||
#define ADJUST_NUM_ROWS 3
|
||||
#define ADJUST_ROW_HEIGHT 21
|
||||
|
||||
extern const unsigned long debounceDelay; // the debounce time; increase if the output flickers
|
||||
extern const unsigned long buttonRepeatInterval;
|
||||
extern const unsigned long buttonRepeatDelay;
|
||||
extern const unsigned long cursorBlinkInterval; // the cursor blink toggle interval time
|
||||
extern const unsigned long patchViewTimeUp; // ms until patch view shuts off
|
||||
extern const unsigned long menuTimeUp; // menu shuts off after one minute of button inactivity
|
||||
|
||||
struct InputState {
|
||||
bool changed = false;
|
||||
bool btnMenu = false;
|
||||
bool btnVal1 = false;
|
||||
bool btnVal2 = false;
|
||||
bool btnPreset = false;
|
||||
int knobMenu = 0;
|
||||
int knobVal1 = 0;
|
||||
int knobVal2 = 0;
|
||||
int knobPreset = 0;
|
||||
};
|
||||
|
||||
struct MenuScreen {
|
||||
MenuScreen() {};
|
||||
virtual const char *title() { return ""; };
|
||||
virtual void update(InputState input, bool redraw) {};
|
||||
virtual ~MenuScreen() {};
|
||||
};
|
||||
extern const MenuScreen adjustMenu;
|
||||
|
||||
void initDisplay();
|
||||
void showVersion();
|
||||
void displayError(const char *error);
|
||||
void handleMenu(bool draw);
|
||||
|
||||
#endif
|
|
@ -74,12 +74,14 @@ void midiPanic() { // all notes off
|
|||
}
|
||||
|
||||
void midiInitialize(uint8_t channel) {
|
||||
/*
|
||||
MIDI_SERIAL.begin(31250); // start serial with midi baudrate 31250
|
||||
MIDI_SERIAL.flush();
|
||||
if(widiJumper){
|
||||
WIDI_SERIAL.begin(31250); // start serial with midi baudrate 31250
|
||||
WIDI_SERIAL.flush();
|
||||
}
|
||||
*/
|
||||
midiSetChannel(channel);
|
||||
}
|
||||
|
||||
|
@ -89,6 +91,7 @@ void midiInitialize(uint8_t channel) {
|
|||
|
||||
// Send a three byte din midi message
|
||||
void midiSend3B(uint8_t midistatus, uint8_t data1, uint8_t data2) {
|
||||
/*
|
||||
MIDI_SERIAL.write(midistatus);
|
||||
MIDI_SERIAL.write(data1);
|
||||
MIDI_SERIAL.write(data2);
|
||||
|
@ -97,18 +100,21 @@ void midiSend3B(uint8_t midistatus, uint8_t data1, uint8_t data2) {
|
|||
WIDI_SERIAL.write(data1);
|
||||
WIDI_SERIAL.write(data2);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
//**************************************************************
|
||||
|
||||
// Send a two byte din midi message
|
||||
void midiSend2B(uint8_t midistatus, uint8_t data) {
|
||||
/*
|
||||
MIDI_SERIAL.write(midistatus);
|
||||
MIDI_SERIAL.write(data);
|
||||
if (widiJumper && widiOn){
|
||||
WIDI_SERIAL.write(midistatus);
|
||||
WIDI_SERIAL.write(data);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
//**************************************************************
|
||||
|
@ -157,11 +163,11 @@ void dinMIDIsendProgramChange(uint8_t value, uint8_t ch) {
|
|||
|
||||
// Send sysex commands to wireless module
|
||||
void dinMIDIsendSysex(const uint8_t data[], const uint8_t length) {
|
||||
MIDI_SERIAL.write(0xF0); //Sysex command
|
||||
//MIDI_SERIAL.write(0xF0); //Sysex command
|
||||
for(int i=0; i<length; ++i) {
|
||||
MIDI_SERIAL.write(data[i]);
|
||||
//MIDI_SERIAL.write(data[i]);
|
||||
}
|
||||
MIDI_SERIAL.write(0xF7); //Sysex end
|
||||
//MIDI_SERIAL.write(0xF7); //Sysex end
|
||||
}
|
||||
|
||||
void sendWLPower(const uint8_t level) {
|
0
NuEVI/midi.h → NuEVI/src/midi.h
Executable file → Normal file
0
NuEVI/midi.h → NuEVI/src/midi.h
Executable file → Normal file
10
NuEVI/name.c → NuEVI/src/name.c
Executable file → Normal file
10
NuEVI/name.c → NuEVI/src/name.c
Executable file → Normal file
|
@ -4,14 +4,8 @@
|
|||
|
||||
#include "usb_names.h"
|
||||
|
||||
// Edit these lines to create your own name. The length must
|
||||
// match the number of characters in your custom name.
|
||||
#if defined(NURAD)
|
||||
#define MIDI_NAME {'N','u','R','A','D',' ','M','I','D','I'}
|
||||
#else
|
||||
#define MIDI_NAME {'N','u','E','V','I',' ','M','I','D','I'}
|
||||
#endif
|
||||
#define MIDI_NAME_LEN 10
|
||||
#define MIDI_NAME {'x','E','V','I',' ','M','I','D','I'}
|
||||
#define MIDI_NAME_LEN 9
|
||||
|
||||
// Do not change this part. This exact format is required by USB.
|
||||
|
342
NuEVI/src/settings.cpp
Normal file
342
NuEVI/src/settings.cpp
Normal file
|
@ -0,0 +1,342 @@
|
|||
#include <Arduino.h>
|
||||
#include <EEPROM.h>
|
||||
#include <Adafruit_SSD1306.h>
|
||||
|
||||
|
||||
#include "settings.h"
|
||||
#include "globals.h"
|
||||
#include "menu.h"
|
||||
#include "hardware.h"
|
||||
#include "config.h"
|
||||
#include "midi.h"
|
||||
#include "led.h"
|
||||
|
||||
//Read and write EEPROM data
|
||||
void writeInt(const uint16_t address, const uint16_t value) {
|
||||
union {
|
||||
uint8_t v[2];
|
||||
uint16_t val;
|
||||
} data;
|
||||
data.val = value;
|
||||
EEPROM.update(address, data.v[0]);
|
||||
EEPROM.update(address+1, data.v[1]);
|
||||
}
|
||||
|
||||
uint16_t readInt(uint16_t address) {
|
||||
union {
|
||||
uint8_t v[2];
|
||||
uint16_t val;
|
||||
} data;
|
||||
data.v[0] = EEPROM.read(address);
|
||||
data.v[1] = EEPROM.read(address+1);
|
||||
return data.val;
|
||||
}
|
||||
|
||||
void writeCalibration() {
|
||||
EEPROM.put(SETTINGS_OFFSET, calibration);
|
||||
}
|
||||
|
||||
void readCalibration() {
|
||||
EEPROM.get(SETTINGS_OFFSET, calibration);
|
||||
}
|
||||
|
||||
void writePreset(uint8_t preset) {
|
||||
EEPROM.put(SETTINGS_OFFSET + preset * sizeof(preset_t), presets[preset]);
|
||||
}
|
||||
|
||||
void writePresets() {
|
||||
EEPROM.put(SETTINGS_OFFSET + CALIBRATION_MAX_SIZE, presets);
|
||||
}
|
||||
|
||||
void readPresets() {
|
||||
EEPROM.get(SETTINGS_OFFSET + CALIBRATION_MAX_SIZE, presets);
|
||||
}
|
||||
|
||||
//Functions to send and receive config (and other things) via USB MIDI SysEx messages
|
||||
uint32_t crc32(const uint8_t *message, const size_t length) {
|
||||
size_t pos=0;
|
||||
uint32_t crc=0xFFFFFFFF;
|
||||
|
||||
while (pos<length) {
|
||||
crc ^= message[pos++]; //Get next byte and increment position
|
||||
for (uint8_t j=0; j<8; ++j) { //Mask off 8 next bits
|
||||
crc = (crc >> 1) ^ (0xEDB88320 & -(crc & 1));
|
||||
}
|
||||
}
|
||||
return ~crc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
||||
Send EEPROM config dump as sysex message. Message format is structured like this:
|
||||
|
||||
+------------------------------------------------------------------------------------+
|
||||
| vendor(3) | "NuEVIc01" (8) | Payload size (2) | EEPROM data (variable) | crc32 (4) |
|
||||
+------------------------------------------------------------------------------------+
|
||||
|
||||
Payload size is for the EEPROM data chunk (not including anything else before or after
|
||||
CRC32 covers the entire buffer up to and including the eeprom data (but not the checksum itself)
|
||||
|
||||
This currently operates under the assumption that the whole EEPROM chunk only consists of unsigned 16 bit ints, only using the range 0-16383
|
||||
|
||||
*/
|
||||
void sendSysexSettings() {
|
||||
const char *header = "NuEVIc01"; //NuEVI config dump 01
|
||||
|
||||
//Build a send buffer of all the things
|
||||
size_t sysex_size = 3 + strlen(header) + 2 + EEPROM_SIZE + 4;
|
||||
uint8_t *sysex_data = (uint8_t*)malloc(sysex_size);
|
||||
|
||||
//Positions (offsets) of parts in send buffer
|
||||
int header_pos = 3;
|
||||
int size_pos = header_pos + strlen(header);
|
||||
int payload_pos = size_pos + 2;
|
||||
int checksum_pos = payload_pos + EEPROM_SIZE;
|
||||
|
||||
//SysEX manufacturer ID
|
||||
memcpy(sysex_data, sysex_id, 3);
|
||||
|
||||
//Header with command code
|
||||
memcpy(sysex_data+header_pos, header, strlen(header));
|
||||
|
||||
//Payload length
|
||||
*(uint16_t*)(sysex_data+size_pos) = convertToMidiValue(EEPROM_SIZE);
|
||||
|
||||
//Config data
|
||||
uint16_t* config_buffer_start = (uint16_t*)(sysex_data+payload_pos);
|
||||
|
||||
//Read one settings item at a time, change data format, and put in send buffer
|
||||
readPresets();
|
||||
uint16_t *preset_buffer = (uint16_t*)presets;
|
||||
for(uint16_t idx=0; idx<EEPROM_SIZE/2; idx++) {
|
||||
uint16_t eepromval = preset_buffer[idx];
|
||||
config_buffer_start[idx] = convertToMidiValue(eepromval);
|
||||
}
|
||||
|
||||
uint32_t checksum = crc32(sysex_data, checksum_pos);
|
||||
|
||||
*(uint32_t*)(sysex_data+checksum_pos) = convertToMidiCRC(checksum);
|
||||
|
||||
usbMIDI.sendSysEx(sysex_size, sysex_data);
|
||||
|
||||
free(sysex_data);
|
||||
}
|
||||
|
||||
//Send a simple 3-byte message code as sysex
|
||||
void sendSysexMessage(const char* messageCode) {
|
||||
char sysexMessage[] = "vvvNuEVIccc"; //Placeholders for vendor and code
|
||||
|
||||
memcpy(sysexMessage, sysex_id, 3);
|
||||
memcpy(sysexMessage+8, messageCode, 3);
|
||||
|
||||
usbMIDI.sendSysEx(11, (const uint8_t *)sysexMessage);
|
||||
}
|
||||
|
||||
|
||||
bool receiveSysexSettings(const uint8_t* data, const uint16_t length) {
|
||||
|
||||
//Expected size of data (vendor+NuEVIc02+len+payload+crc32)
|
||||
uint16_t expected_size = 3 + 8 + 2 + EEPROM_SIZE + 4;
|
||||
|
||||
|
||||
//Positions (offsets) of parts in buffer
|
||||
int size_pos = 11;
|
||||
int payload_pos = size_pos + 2;
|
||||
int checksum_pos = payload_pos + EEPROM_SIZE;
|
||||
|
||||
//Make sure length of receive buffer is enough to read all we need to. We can accept extra junk at the end though.
|
||||
if(length<expected_size) {
|
||||
configShowMessage("Invalid config format");
|
||||
return false;
|
||||
}
|
||||
|
||||
//No need to verify vendor or header/command, already done before we get here.
|
||||
|
||||
//Calculate checksum of stuff received (everything before checksum), transform to midi format
|
||||
//(being a one-way operation, we can't do the reverse anyway)
|
||||
uint32_t crc=convertToMidiCRC(crc32(data, checksum_pos));
|
||||
uint32_t crc_rcv;
|
||||
memcpy(&crc_rcv, data+checksum_pos, 4);
|
||||
if(crc != crc_rcv && crc_rcv != NO_CHECKSUM) {
|
||||
configShowMessage("Invalid checksum");
|
||||
return false;
|
||||
}
|
||||
|
||||
//Verify that payload size matches the size of our EEPROM config
|
||||
uint16_t payload_size = convertFromMidiValue(data+size_pos);
|
||||
if(payload_size != EEPROM_SIZE) {
|
||||
configShowMessage("Invalid config size");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
uint16_t eeprom_version_rcv = convertFromMidiValue(data+(payload_pos+EEPROM_VERSION_ADDR));
|
||||
if(eeprom_version_rcv != EEPROM_VERSION) {
|
||||
configShowMessage("Invalid config version");
|
||||
return false;
|
||||
}
|
||||
|
||||
//Grab all the items in payload and save to EEPROM
|
||||
uint16_t *preset_buffer = (uint16_t*)presets;
|
||||
for(uint16_t i=0; i<payload_size/2; i++) {
|
||||
uint16_t addr = i*2;
|
||||
uint16_t val;
|
||||
|
||||
preset_buffer[addr] = convertFromMidiValue(data+(payload_pos+addr));
|
||||
}
|
||||
|
||||
writePresets();
|
||||
|
||||
//All went well
|
||||
return true;
|
||||
}
|
||||
|
||||
//Send EEPROM and firmware versions
|
||||
void sendSysexVersion() {
|
||||
char sysexMessage[] = "vvvNuEVIc04eevvvvvvvv"; //Placeholders for vendor and code
|
||||
uint8_t fwStrLen = min(strlen(FIRMWARE_VERSION), 8); //Limit firmware version string to 8 bytes
|
||||
|
||||
memcpy(sysexMessage, sysex_id, 3);
|
||||
memcpy(sysexMessage+13, FIRMWARE_VERSION, fwStrLen);
|
||||
|
||||
*(uint16_t*)(sysexMessage+11) = convertToMidiValue(EEPROM_VERSION);
|
||||
|
||||
uint8_t message_length = 13+fwStrLen;
|
||||
|
||||
usbMIDI.sendSysEx(message_length, (const uint8_t *)sysexMessage);
|
||||
}
|
||||
|
||||
extern Adafruit_SSD1306 display;
|
||||
|
||||
void configShowMessage(const char* message) {
|
||||
display.fillRect(0,32,128,64,BLACK);
|
||||
display.setCursor(0,32);
|
||||
display.setTextColor(WHITE);
|
||||
|
||||
display.print(message);
|
||||
|
||||
display.display();
|
||||
}
|
||||
|
||||
uint8_t* sysex_rcv_buffer = NULL;
|
||||
uint16_t sysex_buf_size = 0;
|
||||
|
||||
|
||||
void handleSysexChunk(const uint8_t *data, uint16_t length, bool last) {
|
||||
uint16_t pos;
|
||||
|
||||
if(!sysex_rcv_buffer) {
|
||||
//Start out with an empty buffer
|
||||
pos = 0;
|
||||
sysex_buf_size = length;
|
||||
sysex_rcv_buffer = (uint8_t *)malloc(sysex_buf_size);
|
||||
} else {
|
||||
//Increase size of current buffer
|
||||
pos = sysex_buf_size;
|
||||
sysex_buf_size += length;
|
||||
sysex_rcv_buffer = (uint8_t *)realloc(sysex_rcv_buffer, sysex_buf_size);
|
||||
}
|
||||
|
||||
//Append this chunk to buffer
|
||||
memcpy(sysex_rcv_buffer + pos, data, length);
|
||||
|
||||
//If it's the last one, call the regular handler to process it
|
||||
if(last) {
|
||||
handleSysex(sysex_rcv_buffer, sysex_buf_size);
|
||||
|
||||
//Discard the buffer
|
||||
free(sysex_rcv_buffer);
|
||||
sysex_rcv_buffer = NULL;
|
||||
sysex_buf_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void handleSysex(uint8_t *data, unsigned int length) {
|
||||
//Note: Sysex data as received here contains sysex start and end markers (0xF0 and 0xF7)
|
||||
|
||||
//Too short to even contain a 3-byte vendor id is not for us.
|
||||
if(length<4) return;
|
||||
|
||||
//Verify vendor
|
||||
if(strncmp((char*)(data+1), sysex_id, 3)) return; //Silently ignore different vendor id
|
||||
|
||||
//Verify header. Min length is 3+5+3 bytes (vendor+header+message code)
|
||||
if(length<12 || strncmp((char*)(data+4), "NuEVI", 5)) {
|
||||
configShowMessage("Invalid message.");
|
||||
sendSysexMessage("e00");
|
||||
return;
|
||||
}
|
||||
|
||||
//Get message code
|
||||
char messageCode[3];
|
||||
strncpy(messageCode, (char*)(data+9), 3);
|
||||
|
||||
if(!strncmp(messageCode, "c00", 3)) { //Config dump request
|
||||
configShowMessage("Sending config...");
|
||||
sendSysexSettings();
|
||||
configShowMessage("Config sent.");
|
||||
} else if(!strncmp(messageCode, "c03", 3)) { //Version info request
|
||||
configShowMessage("Sending version.");
|
||||
sendSysexVersion();
|
||||
} else if(!strncmp(messageCode, "c02", 3)) { //New config incoming
|
||||
configShowMessage("Receiving config...");
|
||||
|
||||
//Tell receiveSysexSettings about what's between sysex start and end markers
|
||||
if(receiveSysexSettings(data+1, length-2)) configShowMessage("New config saved.");
|
||||
} else {
|
||||
configShowMessage("Unknown message.");
|
||||
sendSysexMessage("e01"); //Unimplemented message code
|
||||
}
|
||||
}
|
||||
|
||||
void configModeSetup() {
|
||||
statusLedFlash(500);
|
||||
|
||||
display.clearDisplay();
|
||||
display.setCursor(0,0);
|
||||
display.setTextColor(WHITE);
|
||||
display.setTextSize(0);
|
||||
|
||||
display.println("Config mgmt");
|
||||
display.println("Power off NuEVI");
|
||||
display.println("to exit");
|
||||
display.display();
|
||||
|
||||
usbMIDI.setHandleSystemExclusive(handleSysexChunk);
|
||||
|
||||
statusLedFlash(500);
|
||||
|
||||
sendSysexVersion(); //Friendly hello
|
||||
|
||||
configShowMessage("Ready.");
|
||||
}
|
||||
|
||||
//"Main loop". Just sits and wait for midi messages and lets the sysex handler do all the work.
|
||||
void configModeLoop() {
|
||||
usbMIDI.read();
|
||||
}
|
||||
|
||||
//Read settings from eeprom. Returns wether or not anything was written (due to factory reset or upgrade)
|
||||
void readEEPROM(const bool factoryReset) {
|
||||
|
||||
// if stored settings are not for current version, or Enter+Menu are pressed at startup, they are replaced by factory settings
|
||||
uint16_t settings_version = readInt(EEPROM_VERSION_ADDR);
|
||||
|
||||
// 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;
|
||||
} else {
|
||||
readPresets();
|
||||
readCalibration();
|
||||
}
|
||||
|
||||
if(settings_version != EEPROM_VERSION) {
|
||||
// Add default settings here
|
||||
|
||||
writePresets();
|
||||
writeCalibration();
|
||||
writeInt(EEPROM_VERSION_ADDR, EEPROM_VERSION);
|
||||
}
|
||||
}
|
123
NuEVI/src/settings.h
Normal file
123
NuEVI/src/settings.h
Normal file
|
@ -0,0 +1,123 @@
|
|||
#ifndef __SETTINGS_H
|
||||
#define __SETTINGS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "globals.h"
|
||||
|
||||
|
||||
#define EEPROM_VERSION 1
|
||||
#define EEPROM_VERSION_ADDR 0
|
||||
#define SETTINGS_OFFSET 2
|
||||
#define PRESET_MAX_SIZE 128 // Leave extra space for future settings
|
||||
#define PRESET_COUNT 8
|
||||
#define CALIBRATION_MAX_SIZE 54 // Leave extra space for future settings
|
||||
#define EEPROM_SIZE 1080 // Cannot exceed this amount of EEPROM space
|
||||
|
||||
static_assert(SETTINGS_OFFSET + PRESET_MAX_SIZE * PRESET_COUNT + CALIBRATION_MAX_SIZE <= EEPROM_SIZE,
|
||||
"Not enough EEPROM");
|
||||
|
||||
/**
|
||||
* Sensor calibration is global across presets
|
||||
*/
|
||||
struct calibration_t {
|
||||
int16_t breathThrValOffset = 5;
|
||||
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];
|
||||
};
|
||||
|
||||
static_assert(sizeof(calibration_t) == CALIBRATION_MAX_SIZE, "calibration data wrong size");
|
||||
|
||||
/**
|
||||
* Per-preset config
|
||||
*/
|
||||
struct preset_t {
|
||||
uint8_t MIDIchannel = 1; // Midi channel to send on
|
||||
uint8_t breathCC = 2; // breath CC selection
|
||||
uint8_t altBreathCC = 70; // breath CC selection
|
||||
uint8_t extraCC = 1; // extra CC selection
|
||||
uint8_t leverCC = 7; // "lever" CC selection
|
||||
uint8_t biteCC = 11; // bite CC selection
|
||||
uint8_t fixedVelocity = 0; // Zero = not fixed
|
||||
uint8_t portamentoLimit = 127; // 1-127 - max portamento level
|
||||
uint8_t PBdepth = 1; // OFF:1-12 divider
|
||||
uint8_t deglitch = 20; // debounes time for key/roller inputs
|
||||
uint8_t breathCurve = 4; // breath curve selection
|
||||
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
|
||||
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
|
||||
int8_t cvTune = 0;
|
||||
int8_t cvScale = 0;
|
||||
|
||||
PortamentoMode portamentoMode;
|
||||
BreathMode breathMode = BreathMode::BREATH_LSB_AT;
|
||||
FingeringMode fingering = FingeringMode::EVI;
|
||||
RollerMode rollerMode = RollerMode::HIGHEST;
|
||||
ExtraControl biteControl = ExtraControl::GLIDE;
|
||||
ExtraControl leverControl = ExtraControl::VIBRATO;
|
||||
ExtraControl extraControl = ExtraControl::CC;
|
||||
|
||||
VibratoMode vibratoMode = VSTART_DOWN; // direction of first vibrato wave UPWD or DNWD
|
||||
uint8_t vibratoDepth = 1; // OFF:1-9
|
||||
uint8_t vibSens = 2; // vibrato sensitivity
|
||||
uint8_t vibRetn = 2; // vibrato return speed
|
||||
|
||||
uint8_t knob1CC = 71;
|
||||
uint8_t knob2CC = 72;
|
||||
uint8_t knob3CC = 73;
|
||||
uint8_t knob4CC = 74;
|
||||
|
||||
uint8_t icmAccelMode;
|
||||
uint8_t icmAccelCC;
|
||||
uint8_t icmTiltMode;
|
||||
uint8_t icmTiltCC;
|
||||
uint8_t icmRotationMode;
|
||||
uint8_t icmRotationCC;
|
||||
|
||||
uint8_t _reserved[87];
|
||||
};
|
||||
|
||||
static_assert(sizeof(preset_t) == PRESET_MAX_SIZE, "preset_t must be 128 bytes");
|
||||
extern preset_t presets[PRESET_COUNT];
|
||||
extern calibration_t calibration;
|
||||
extern preset_t *currentPreset;
|
||||
|
||||
#define NO_CHECKSUM 0x7F007F00
|
||||
|
||||
void readEEPROM(const bool factoryReset);
|
||||
void writePreset(uint8_t preset);
|
||||
void writeCalibration();
|
||||
|
||||
//Functions for config management mode
|
||||
void sendSysexSettings();
|
||||
void sendSysexMessage(const char* messageCode);
|
||||
void sendSysexVersion();
|
||||
|
||||
void handleSysex(uint8_t *data, unsigned int length);
|
||||
void handleSysexChunk(const uint8_t *data, uint16_t length, bool last);
|
||||
|
||||
uint32_t crc32(const uint8_t *message, const size_t length);
|
||||
|
||||
void configInitScreen();
|
||||
void configShowMessage(const char* message);
|
||||
|
||||
void configModeSetup();
|
||||
void configModeLoop();
|
||||
|
||||
#endif
|
57
NuEVI/src/test.cpp
Normal file
57
NuEVI/src/test.cpp
Normal file
|
@ -0,0 +1,57 @@
|
|||
#include "hardware.h"
|
||||
|
||||
uint8_t oldButtons = 0;
|
||||
uint16_t oldKeys = 0;
|
||||
uint16_t oldUtil = 0;
|
||||
bool plotCap = false;
|
||||
|
||||
void handleTestMode() {
|
||||
uint8_t buttons = buttonState();
|
||||
if (buttons != oldButtons) {
|
||||
oldButtons = buttons;
|
||||
Serial.print("Buttons:");
|
||||
Serial.println(buttons, HEX);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
int k = readKnob(i);
|
||||
if (k != 0) {
|
||||
Serial.print("Knob");
|
||||
Serial.print(i);
|
||||
Serial.print(":");
|
||||
Serial.println(k);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t keys = keysTouched();
|
||||
if (keys != oldKeys) {
|
||||
Serial.print("Keys:");
|
||||
Serial.println(keys, HEX);
|
||||
}
|
||||
|
||||
uint16_t util = utilTouched();
|
||||
if (util != oldUtil) {
|
||||
Serial.print("Util:");
|
||||
Serial.println(util, HEX);
|
||||
}
|
||||
|
||||
if (buttons == 0x01) {
|
||||
plotCap = !plotCap;
|
||||
}
|
||||
|
||||
if (plotCap) {
|
||||
for (int i = 0; i < 12; i++) {
|
||||
Serial.print(">key");
|
||||
Serial.print(i);
|
||||
Serial.print(":");
|
||||
Serial.println(readTouchKey(i));
|
||||
}
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
Serial.print(">util");
|
||||
Serial.print(i);
|
||||
Serial.print(":");
|
||||
Serial.println(readTouchUtil(i));
|
||||
}
|
||||
}
|
||||
}
|
6
NuEVI/src/test.h
Normal file
6
NuEVI/src/test.h
Normal file
|
@ -0,0 +1,6 @@
|
|||
#ifndef __TEST_H
|
||||
#define __TEST_H
|
||||
|
||||
void handleTestMode();
|
||||
|
||||
#endif
|
823
NuEVI/src/xEVI.cpp
Normal file
823
NuEVI/src/xEVI.cpp
Normal file
|
@ -0,0 +1,823 @@
|
|||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include <SPI.h>
|
||||
#include <EEPROM.h>
|
||||
#include <array>
|
||||
|
||||
#include "globals.h"
|
||||
#include "hardware.h"
|
||||
#include "midi.h"
|
||||
#include "menu.h"
|
||||
#include "config.h"
|
||||
#include "settings.h"
|
||||
#include "led.h"
|
||||
|
||||
/*
|
||||
NAME: xEVI
|
||||
WRITTEN BY: BRIAN HREBEC
|
||||
BASED ON: NuEVI by JOHAN BERGLUND
|
||||
DATE: 2023-8-23
|
||||
FOR: PJRC Teensy 4.0 and 2x MPR121 capactive touch sensor board.
|
||||
Uses an SSD1306 controlled OLED display communicating over I2C and a NeoPixel LED strip for status display
|
||||
ICM20948 for intertial measurement.
|
||||
FUNCTION: EVI Wind Controller using MPRLS pressure sensors and capacitive touch keys. Output to USB MIDI and CV.
|
||||
*/
|
||||
|
||||
#if !defined(USB_MIDI) && !defined(USB_MIDI_SERIAL)
|
||||
#error "USB MIDI not enabled. Please set USB type to 'MIDI' or 'Serial + MIDI'."
|
||||
#endif
|
||||
|
||||
preset_t presets[PRESET_COUNT];
|
||||
instrument_state_t state;
|
||||
preset_t *currentPreset;
|
||||
calibration_t calibration;
|
||||
|
||||
static const int pbDepthList[13] = { 8192, 8192, 4096, 2731, 2048, 1638, 1365, 1170, 1024, 910, 819, 744, 683 };
|
||||
static const float vibDepth[10] = { 0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.40, 0.45 }; // max pitch bend values (+/-) for the vibrato settings
|
||||
static const short vibMaxBiteList[16] = { 1400, 1200, 1000, 900, 800, 700, 600, 500, 400, 300, 250, 200, 150, 100, 50, 25 };
|
||||
static const short vibMaxList[12] = { 300, 275, 250, 225, 200, 175, 150, 125, 100, 75, 50, 25 };
|
||||
static const int timeDividerList[9] = { 0, 222, 200, 181, 167, 152, 143, 130, 125 }; // For CV vibrato - 222 is 4.5Hz, 200 is 5Hz, 181 is 5.5Hz 167 is 6Hz, 152 is 6.5Hz, 143 is 7Hz, 130 is 7.5Hz, 125 is 8Hz
|
||||
|
||||
static const unsigned short curveM4[] = { 0, 4300, 7000, 8700, 9900, 10950, 11900, 12600, 13300, 13900, 14500, 15000, 15450, 15700, 16000, 16250, 16383 };
|
||||
static const unsigned short curveM3[] = { 0, 2900, 5100, 6650, 8200, 9500, 10550, 11500, 12300, 13100, 13800, 14450, 14950, 15350, 15750, 16150, 16383 };
|
||||
static const unsigned short curveM2[] = { 0, 2000, 3600, 5000, 6450, 7850, 9000, 10100, 11100, 12100, 12900, 13700, 14400, 14950, 15500, 16000, 16383 };
|
||||
static const unsigned short curveM1[] = { 0, 1400, 2850, 4100, 5300, 6450, 7600, 8700, 9800, 10750, 11650, 12600, 13350, 14150, 14950, 15650, 16383 };
|
||||
const unsigned short curveIn[] = { 0, 1023, 2047, 3071, 4095, 5119, 6143, 7167, 8191, 9215, 10239, 11263, 12287, 13311, 14335, 15359, 16383 };
|
||||
static const unsigned short curveP1[] = { 0, 600, 1350, 2150, 2900, 3800, 4700, 5600, 6650, 7700, 8800, 9900, 11100, 12300, 13500, 14850, 16383 };
|
||||
static const unsigned short curveP2[] = { 0, 400, 800, 1300, 2000, 2650, 3500, 4300, 5300, 6250, 7400, 8500, 9600, 11050, 12400, 14100, 16383 };
|
||||
static const unsigned short curveP3[] = { 0, 200, 500, 900, 1300, 1800, 2350, 3100, 3800, 4600, 5550, 6550, 8000, 9500, 11250, 13400, 16383 };
|
||||
static const unsigned short curveP4[] = { 0, 100, 200, 400, 700, 1050, 1500, 1950, 2550, 3200, 4000, 4900, 6050, 7500, 9300, 12100, 16383 };
|
||||
static const unsigned short curveS1[] = { 0, 600, 1350, 2150, 2900, 3800, 4700, 6000, 8700, 11000, 12400, 13400, 14300, 14950, 15500, 16000, 16383 };
|
||||
static const unsigned short curveS2[] = { 0, 600, 1350, 2150, 2900, 4000, 6100, 9000, 11000, 12100, 12900, 13700, 14400, 14950, 15500, 16000, 16383 };
|
||||
// static const unsigned short curveS3[] = {0,600,1350,2300,3800,6200,8700,10200,11100,12100,12900,13700,14400,14950,15500,16000,16383};
|
||||
// static const unsigned short curveS4[] = {0,600,1700,4000,6600,8550,9700,10550,11400,12200,12900,13700,14400,14950,15500,16000,16383};
|
||||
|
||||
static const unsigned short curveZ1[] = { 0, 1400, 2100, 2900, 3200, 3900, 4700, 5600, 6650, 7700, 8800, 9900, 11100, 12300, 13500, 14850, 16383 };
|
||||
static const unsigned short curveZ2[] = { 0, 2000, 3200, 3800, 4096, 4800, 5100, 5900, 6650, 7700, 8800, 9900, 11100, 12300, 13500, 14850, 16383 };
|
||||
|
||||
const std::array<const unsigned short*, 13> curves = {
|
||||
curveM4, curveM3, curveM2, curveM1, curveIn, curveP1, curveP2,
|
||||
curveP3, curveP4, curveS1, curveS2, curveZ1, curveZ2 };
|
||||
|
||||
static int waveformsTable[maxSamplesNum] = {
|
||||
// Sine wave
|
||||
0x7ff, 0x86a, 0x8d5, 0x93f, 0x9a9, 0xa11, 0xa78, 0xadd, 0xb40, 0xba1,
|
||||
0xbff, 0xc5a, 0xcb2, 0xd08, 0xd59, 0xda7, 0xdf1, 0xe36, 0xe77, 0xeb4,
|
||||
0xeec, 0xf1f, 0xf4d, 0xf77, 0xf9a, 0xfb9, 0xfd2, 0xfe5, 0xff3, 0xffc,
|
||||
0xfff, 0xffc, 0xff3, 0xfe5, 0xfd2, 0xfb9, 0xf9a, 0xf77, 0xf4d, 0xf1f,
|
||||
0xeec, 0xeb4, 0xe77, 0xe36, 0xdf1, 0xda7, 0xd59, 0xd08, 0xcb2, 0xc5a,
|
||||
0xbff, 0xba1, 0xb40, 0xadd, 0xa78, 0xa11, 0x9a9, 0x93f, 0x8d5, 0x86a,
|
||||
0x7ff, 0x794, 0x729, 0x6bf, 0x655, 0x5ed, 0x586, 0x521, 0x4be, 0x45d,
|
||||
0x3ff, 0x3a4, 0x34c, 0x2f6, 0x2a5, 0x257, 0x20d, 0x1c8, 0x187, 0x14a,
|
||||
0x112, 0xdf, 0xb1, 0x87, 0x64, 0x45, 0x2c, 0x19, 0xb, 0x2,
|
||||
0x0, 0x2, 0xb, 0x19, 0x2c, 0x45, 0x64, 0x87, 0xb1, 0xdf,
|
||||
0x112, 0x14a, 0x187, 0x1c8, 0x20d, 0x257, 0x2a5, 0x2f6, 0x34c, 0x3a4,
|
||||
0x3ff, 0x45d, 0x4be, 0x521, 0x586, 0x5ed, 0x655, 0x6bf, 0x729, 0x794 };
|
||||
|
||||
const int rollerHarmonic[2][7] = { {0, 7, 12, 16, 19, 24, 26}, // F horn 2,3,4,5,6,8,9 hrm
|
||||
{7, 12, 16, 19, 24, 26, 31} }; // Bb horn 3,4,5,6,8,9,12 hrm
|
||||
|
||||
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;
|
||||
|
||||
//_______________________________________________________________________________________________ SETUP
|
||||
|
||||
// MIDI note value check with out of range octave repeat
|
||||
inline int noteValueCheck(int note) {
|
||||
if (note > 127) {
|
||||
note = 115 + (note - 127) % 12;
|
||||
} else if (note < 0) {
|
||||
note = 12 - abs(note) % 12;
|
||||
}
|
||||
return note;
|
||||
}
|
||||
|
||||
//***********************************************************
|
||||
|
||||
void port(int portCC) {
|
||||
if (portCC == state.portamentoVal) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentPreset->portamentoMode == PortamentoMode::PON || currentPreset->portamentoMode == PortamentoMode::PGLIDE_ONLY) {
|
||||
if (state.portamentoVal > 0 && portCC == 0) {
|
||||
midiSendControlChange(CCN_PortOnOff, 0);
|
||||
} else if (state.portamentoVal == 0 && portCC > 0) {
|
||||
midiSendControlChange(CCN_PortOnOff, 127);
|
||||
}
|
||||
}
|
||||
|
||||
midiSendControlChange(CCN_Port, portCC);
|
||||
state.portamentoVal = portCC;
|
||||
}
|
||||
|
||||
// Update CV output pin, run from timer.
|
||||
void cvUpdate() {
|
||||
static byte cvPortaTuneCount = 0;
|
||||
uint32_t currentTime = millis();
|
||||
int cvPressure = readPressure();
|
||||
analogWrite(cvBreathPin, cvPressure);
|
||||
state.targetPitch = (state.activeNote - 24) * 42;
|
||||
state.targetPitch += map(state.pitchBend, 0, 16383, -84, 84);
|
||||
state.targetPitch -= state.quarterToneTrigger * 21;
|
||||
if (state.portamentoVal > 0) {
|
||||
if (state.targetPitch > state.cvPitch) {
|
||||
if (!cvPortaTuneCount) {
|
||||
state.cvPitch += 1 + (127 - state.portamentoVal) / 4;
|
||||
} else {
|
||||
cvPortaTuneCount++;
|
||||
if (cvPortaTuneCount > CVPORTATUNE)
|
||||
cvPortaTuneCount = 0;
|
||||
}
|
||||
if (state.cvPitch > state.targetPitch)
|
||||
state.cvPitch = state.targetPitch;
|
||||
} else if (state.targetPitch < state.cvPitch) {
|
||||
if (!cvPortaTuneCount) {
|
||||
state.cvPitch -= 1 + (127 - state.portamentoVal) / 4;
|
||||
} else {
|
||||
cvPortaTuneCount++;
|
||||
if (cvPortaTuneCount > CVPORTATUNE)
|
||||
cvPortaTuneCount = 0;
|
||||
}
|
||||
if (state.cvPitch < state.targetPitch)
|
||||
state.cvPitch = state.targetPitch;
|
||||
} else {
|
||||
state.cvPitch = state.targetPitch;
|
||||
}
|
||||
} else {
|
||||
state.cvPitch = state.targetPitch;
|
||||
}
|
||||
|
||||
if (currentPreset->cvVibRate) {
|
||||
int timeDivider = timeDividerList[currentPreset->cvVibRate];
|
||||
int cvVib = map(((waveformsTable[map(currentTime % timeDivider, 0, timeDivider, 0, maxSamplesNum - 1)] - 2047)), -259968, 259969, -11, 11);
|
||||
state.cvPitch += cvVib;
|
||||
}
|
||||
int cvPitchTuned = 2 * (currentPreset->cvTune) + map(state.cvPitch, 0, 4032, 0, 4032 + 2 * (currentPreset->cvScale));
|
||||
analogWrite(cvPitchPin, constrain(cvPitchTuned, 0, 4095));
|
||||
}
|
||||
|
||||
//**************************************************************
|
||||
// non linear mapping function (http://playground.arduino.cc/Main/MultiMap)
|
||||
// note: the _in array should have increasing values
|
||||
unsigned int multiMap(unsigned short val, const unsigned short* _in, const unsigned short* _out, uint8_t size) {
|
||||
// take care the value is within range
|
||||
// val = constrain(val, _in[0], _in[size-1]);
|
||||
if (val <= _in[0])
|
||||
return _out[0];
|
||||
if (val >= _in[size - 1])
|
||||
return _out[size - 1];
|
||||
|
||||
// search right interval
|
||||
uint8_t pos = 1; // _in[0] allready tested
|
||||
while (val > _in[pos])
|
||||
pos++;
|
||||
|
||||
// this will handle all exact "points" in the _in array
|
||||
if (val == _in[pos])
|
||||
return _out[pos];
|
||||
|
||||
// interpolate in the right segment for the rest
|
||||
return (val - _in[pos - 1]) * (_out[pos] - _out[pos - 1]) / (_in[pos] - _in[pos - 1]) + _out[pos - 1];
|
||||
}
|
||||
|
||||
|
||||
|
||||
//**************************************************************
|
||||
|
||||
// map breath values to selected curve
|
||||
unsigned int breathCurve(unsigned int inputVal) {
|
||||
if (currentPreset->breathCurve >= curves.size())
|
||||
return inputVal;
|
||||
return multiMap(inputVal, curveIn, curves[currentPreset->breathCurve], 17);
|
||||
}
|
||||
|
||||
//**************************************************************
|
||||
|
||||
int patchLimit(int value) {
|
||||
return constrain(value, 1, 128);
|
||||
}
|
||||
|
||||
//**************************************************************
|
||||
|
||||
int breath() {
|
||||
static int oldbreath = 0;
|
||||
static unsigned int oldbreathhires = 0;
|
||||
|
||||
int breathCCval, breathCCvalFine;
|
||||
unsigned int breathCCvalHires;
|
||||
breathCCvalHires = breathCurve(mapConstrain(state.breathSignal, state.breathThrVal, state.breathMaxVal, 0, 16383));
|
||||
breathCCval = (breathCCvalHires >> 7) & 0x007F;
|
||||
breathCCvalFine = breathCCvalHires & 0x007F;
|
||||
if (breathCCval != oldbreath) { // only send midi data if breath has changed from previous value
|
||||
if (currentPreset->breathCC) {
|
||||
// send midi cc
|
||||
midiSendControlChange(currentPreset->breathCC, breathCCval);
|
||||
}
|
||||
if (currentPreset->breathMode == BreathMode::BREATH_AT || currentPreset->breathMode == BreathMode::BREATH_LSB_AT) {
|
||||
// send aftertouch
|
||||
midiSendAfterTouch(breathCCval);
|
||||
}
|
||||
oldbreath = breathCCval;
|
||||
}
|
||||
|
||||
if (breathCCvalHires != oldbreathhires
|
||||
&& (currentPreset->breathMode == BreathMode::BREATH_LSB || currentPreset->breathMode == BreathMode::BREATH_LSB_AT)) {
|
||||
midiSendControlChange(currentPreset->breathCC + 32, breathCCvalFine);
|
||||
}
|
||||
|
||||
oldbreathhires = breathCCvalHires;
|
||||
|
||||
return breathCCval;
|
||||
}
|
||||
|
||||
//**************************************************************
|
||||
|
||||
void pitch_bend() {
|
||||
// handle input from pitchbend touchpads and
|
||||
// on-pcb variable capacitor for vibrato.
|
||||
static int oldpb = 0;
|
||||
int vibMax;
|
||||
int vibMaxBite;
|
||||
int calculatedPBdepth;
|
||||
byte pbTouched = 0;
|
||||
int vibRead = 0;
|
||||
int vibReadBite = 0;
|
||||
state.pbUpSignal = readTouchUtil(pbUpPin); // PCB PIN "Pu"
|
||||
state.pbDnSignal = readTouchUtil(pbDnPin); // PCB PIN "Pd"
|
||||
bool halfPitchBendKey = (currentPreset->pinkySetting == PBD) && state.pinkyKey; // hold pinky key for 1/2 pitchbend value
|
||||
state.quarterToneTrigger = (currentPreset->pinkySetting == QTN) && state.pinkyKey; // pinky key for a quarter tone down using pitch bend (assuming PB range on synth is set to 2 semitones)
|
||||
|
||||
calculatedPBdepth = pbDepthList[currentPreset->PBdepth];
|
||||
if (halfPitchBendKey)
|
||||
calculatedPBdepth = calculatedPBdepth * 0.5;
|
||||
|
||||
vibMax = vibMaxList[currentPreset->vibSens - 1];
|
||||
|
||||
float calculatedDepth = 0;
|
||||
if (currentPreset->vibratoMode == VibratoMode::VSTART_DOWN) {
|
||||
calculatedDepth = calculatedPBdepth * vibDepth[currentPreset->vibratoDepth];
|
||||
} else {
|
||||
calculatedDepth = (0 - calculatedPBdepth * vibDepth[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 (vibReadBite < state.vibThrBite) {
|
||||
state.vibSignal = (state.vibSignal + mapConstrain(
|
||||
vibReadBite, (state.vibZeroBite - vibMaxBite), state.vibThrBite, calculatedDepth, 0)
|
||||
) / 2;
|
||||
} else if (vibReadBite > state.vibThrBiteLo) {
|
||||
state.vibSignal = (state.vibSignal + mapConstrain(
|
||||
vibReadBite, (state.vibZeroBite + vibMaxBite), state.vibThrBite, calculatedDepth, 0)
|
||||
) / 2;
|
||||
} else {
|
||||
state.vibSignal = state.vibSignal / 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (ExtraControl::VIBRATO == currentPreset->leverControl) { // lever vibrato
|
||||
vibRead = readTouchUtil(vibratoPin);
|
||||
if (vibRead < state.vibThr) {
|
||||
state.vibSignal = (state.vibSignal +
|
||||
mapConstrain(vibRead, (state.vibZero - vibMax), state.vibThr, calculatedDepth, 0)
|
||||
) / 2;
|
||||
} else if (vibRead > state.vibThrLo) {
|
||||
state.vibSignal = (state.vibSignal +
|
||||
mapConstrain(vibRead, (state.vibZero + vibMax), state.vibThr, calculatedDepth, 0)
|
||||
) / 2;
|
||||
} else {
|
||||
state.vibSignal = state.vibSignal / 2;
|
||||
}
|
||||
}
|
||||
|
||||
switch (currentPreset->vibRetn) { // moving baseline
|
||||
case 0:
|
||||
// keep vibZero value
|
||||
break;
|
||||
case 1:
|
||||
state.vibZero = state.vibZero * 0.95 + vibRead * 0.05;
|
||||
state.vibZeroBite = state.vibZeroBite * 0.95 + vibReadBite * 0.05;
|
||||
break;
|
||||
case 2:
|
||||
state.vibZero = state.vibZero * 0.9 + vibRead * 0.1;
|
||||
state.vibZeroBite = state.vibZeroBite * 0.9 + vibReadBite * 0.1;
|
||||
break;
|
||||
case 3:
|
||||
state.vibZero = state.vibZero * 0.8 + vibRead * 0.2;
|
||||
state.vibZeroBite = state.vibZeroBite * 0.8 + vibReadBite * 0.2;
|
||||
break;
|
||||
case 4:
|
||||
state.vibZero = state.vibZero * 0.6 + vibRead * 0.4;
|
||||
state.vibZeroBite = state.vibZeroBite * 0.6 + vibReadBite * 0.4;
|
||||
}
|
||||
state.vibThr = state.vibZero - currentPreset->vibSquelch;
|
||||
state.vibThrLo = state.vibZero + currentPreset->vibSquelch;
|
||||
state.vibThrBite = state.vibZeroBite - currentPreset->vibSquelch;
|
||||
state.vibThrBiteLo = state.vibZeroBite + currentPreset->vibSquelch;
|
||||
int pbPos = mapConstrain(state.pbUpSignal, calibration.pbUpMaxVal, calibration.pbUpThrVal, calculatedPBdepth, 0);
|
||||
int pbNeg = mapConstrain(state.pbDnSignal, calibration.pbDnMaxVal, calibration.pbDnThrVal, calculatedPBdepth, 0);
|
||||
int pbSum = 8193 + pbPos - pbNeg;
|
||||
int pbDif = abs(pbPos - pbNeg);
|
||||
|
||||
if ((state.pbUpSignal < calibration.pbUpThrVal || state.pbDnSignal < calibration.pbDnThrVal) && currentPreset->PBdepth) {
|
||||
if (pbDif < 10) {
|
||||
state.pitchBend = 8192;
|
||||
} else {
|
||||
state.pitchBend = state.pitchBend * 0.6 + 0.4 * pbSum;
|
||||
}
|
||||
pbTouched = 1;
|
||||
}
|
||||
if (!pbTouched) {
|
||||
state.pitchBend = state.pitchBend * 0.6 + 8192 * 0.4; // released, so smooth your way back to zero
|
||||
if ((state.pitchBend > 8187) && (state.pitchBend < 8197))
|
||||
state.pitchBend = 8192; // 8192 is 0 pitch bend, don't miss it bc of smoothing
|
||||
}
|
||||
|
||||
state.pitchBend = state.pitchBend + state.vibSignal;
|
||||
state.pitchBend = constrain(state.pitchBend, 0, 16383);
|
||||
|
||||
state.pbSend = state.pitchBend - state.quarterToneTrigger * calculatedPBdepth * 0.25;
|
||||
state.pbSend = constrain(state.pbSend, 0, 16383);
|
||||
|
||||
if (state.pbSend != oldpb) { // only send midi data if pitch bend has changed from previous value
|
||||
midiSendPitchBend(state.pbSend);
|
||||
oldpb = state.pbSend;
|
||||
}
|
||||
}
|
||||
|
||||
//***********************************************************
|
||||
|
||||
void portamento_() {
|
||||
if (currentPreset->portamentoMode == PortamentoMode::POFF) {
|
||||
port(0); // ensure it's off
|
||||
return;
|
||||
}
|
||||
|
||||
int portSumCC = 0;
|
||||
if (currentPreset->pinkySetting == GLD) {
|
||||
if (state.pinkyKey) {
|
||||
portSumCC += currentPreset->portamentoLimit;
|
||||
}
|
||||
}
|
||||
if (ExtraControl::GLIDE == currentPreset->biteControl) {
|
||||
// Portamento is controlled with the bite sensor in the mouthpiece
|
||||
state.biteSignal = readTouchUtil(bitePin);
|
||||
if (state.biteSignal >= calibration.biteThrVal) { // if we are enabled and over the threshold, send portamento
|
||||
portSumCC += mapConstrain(state.biteSignal, calibration.biteThrVal, calibration.biteMaxVal, 0, currentPreset->portamentoLimit);
|
||||
}
|
||||
}
|
||||
|
||||
if (ExtraControl::GLIDE == currentPreset->leverControl) {
|
||||
// Portamento is controlled with thumb lever
|
||||
state.leverSignal = readTouchUtil(vibratoPin);
|
||||
if (((3000 - state.leverSignal) >= calibration.leverThrVal)) { // if we are enabled and over the threshold, send portamento
|
||||
portSumCC += mapConstrain((3000 - state.leverSignal), calibration.leverThrVal, calibration.leverMaxVal, 0, currentPreset->portamentoLimit);
|
||||
}
|
||||
}
|
||||
|
||||
port(constrain(portSumCC, 0, currentPreset->portamentoLimit)); // Total output glide rate limited to glide max setting
|
||||
}
|
||||
|
||||
//***********************************************************
|
||||
|
||||
void biteCC_() {
|
||||
int biteVal = 0;
|
||||
if (ExtraControl::CC == currentPreset->biteControl) {
|
||||
state.biteSignal = readTouchUtil(bitePin); // get sensor data, do some smoothing - SENSOR PIN 17 - PCB PINS LABELED "BITE" (GND left, sensor pin right)
|
||||
if (state.biteSignal >= calibration.biteThrVal) { // we are over the threshold, calculate CC value
|
||||
biteVal = mapConstrain(state.biteSignal, calibration.biteThrVal, calibration.biteMaxVal, 0, 127);
|
||||
}
|
||||
|
||||
if (biteVal != state.biteVal) {
|
||||
midiSendControlChange(currentPreset->biteCC, biteVal);
|
||||
}
|
||||
state.biteVal = biteVal;
|
||||
}
|
||||
}
|
||||
|
||||
void autoCal() {
|
||||
state.vibZero = state.vibZeroBite = 0;
|
||||
for(int i = 1 ; i <= CALIBRATE_SAMPLE_COUNT; ++i) {
|
||||
state.breathZero += readPressure();
|
||||
state.breathAltZero += readAltPressure();
|
||||
state.vibZero += readTouchUtil(vibratoPin);
|
||||
state.vibZeroBite += readTouchUtil(bitePin);
|
||||
}
|
||||
|
||||
state.breathZero /= CALIBRATE_SAMPLE_COUNT;
|
||||
state.breathAltZero /= CALIBRATE_SAMPLE_COUNT;
|
||||
state.vibZero /= CALIBRATE_SAMPLE_COUNT;
|
||||
state.vibZeroBite /= CALIBRATE_SAMPLE_COUNT;
|
||||
|
||||
state.vibThr = state.vibZero - currentPreset->vibSquelch;
|
||||
state.vibThrLo = state.vibZero + currentPreset->vibSquelch;
|
||||
state.vibThrBite = state.vibZeroBite - currentPreset->vibSquelch;
|
||||
state.vibThrBiteLo = state.vibZeroBite + currentPreset->vibSquelch;
|
||||
|
||||
state.breathThrVal = state.breathZero + calibration.breathThrValOffset;
|
||||
state.breathMaxVal = state.breathThrVal + calibration.breathMaxValOffset;
|
||||
state.breathAltThrVal = state.breathAltZero + calibration.breathAltThrValOffset;
|
||||
state.breathAltMaxVal = state.breathAltThrVal + calibration.breathAltMaxValOffset;
|
||||
|
||||
}
|
||||
|
||||
void fullAutoCal() {
|
||||
int calRead;
|
||||
int calReadNext;
|
||||
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);
|
||||
|
||||
// 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);
|
||||
|
||||
// Touch sensors
|
||||
calRead = CTOUCH_HI_LIMIT;
|
||||
for (byte i = 0; i < 12; i++) {
|
||||
calReadNext = readTouchKey(i);
|
||||
if (calReadNext < calRead)
|
||||
calRead = calReadNext; // use lowest value
|
||||
}
|
||||
|
||||
calibration.ctouchThrVal = calRead - 20;
|
||||
}
|
||||
|
||||
//***********************************************************
|
||||
|
||||
/*
|
||||
* Read octave and return offset
|
||||
*/
|
||||
int readOctave() {
|
||||
static byte lastOctaveR = 0;
|
||||
|
||||
// Roller modes
|
||||
// 1: Highest touched roller, release memory
|
||||
// 2: Highest touched roller, extend range on top/bottom release
|
||||
// 3: Touched roller pair
|
||||
// 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;
|
||||
|
||||
byte 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;
|
||||
|
||||
byte offset = 0;
|
||||
byte octaveR = 0;
|
||||
if (rollerMode == RollerMode::HIGHEST || rollerMode == RollerMode::HIGHEST_EXTEND) {
|
||||
for (int i = 5; i >= 0; i--) {
|
||||
if (rollers[i]) {
|
||||
octaveR = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (rollerMode == RollerMode::HIGHEST_PAIR || rollerMode == RollerMode::HIGHEST_PAIR_EXTEND) {
|
||||
for (int i = 5; i >= 1; i--) {
|
||||
if (rollers[i] && rollers[i - 1]) {
|
||||
octaveR = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Allow top/bottom single rollers in extended mode
|
||||
if (octaveR == 0 && extend) {
|
||||
if (rollers[0]) {
|
||||
octaveR = 1;
|
||||
} else if (rollers[5]) {
|
||||
octaveR = 6;
|
||||
}
|
||||
}
|
||||
} else if (rollerMode == RollerMode::PARTIAL || rollerMode == RollerMode::PARTIAL_EXTEND) {
|
||||
for (int i = 5; i >= 0; i--) {
|
||||
if (rollers[i]) {
|
||||
octaveR = i + 1;
|
||||
if (i > 0 && rollers[i - 1]) {
|
||||
offset = -5;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Go up/down a partial on top/bottom release
|
||||
if (extend && octaveR == 0) {
|
||||
if (lastOctaveR == 1) {
|
||||
offset = -5;
|
||||
} else if (lastOctaveR == 6) {
|
||||
offset = 7;
|
||||
}
|
||||
|
||||
octaveR = lastOctaveR;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle extended release
|
||||
if (extend && octaveR == 0) {
|
||||
if (rollerMode == RollerMode::HIGHEST_EXTEND && lastOctaveR == 6) {
|
||||
octaveR = 7;
|
||||
} else if (rollerMode == RollerMode::PARTIAL_EXTEND && lastOctaveR == 7) {
|
||||
octaveR = 8;
|
||||
} else if (lastOctaveR == 1) {
|
||||
octaveR = 0;
|
||||
} else {
|
||||
octaveR = lastOctaveR;
|
||||
}
|
||||
}
|
||||
|
||||
lastOctaveR = octaveR;
|
||||
|
||||
FingeringMode fingering = currentPreset->fingering;
|
||||
byte K4 = readTouchKey(K4Pin) < calibration.ctouchThrVal;
|
||||
if (FingeringMode::TPT == fingering) { // TPT fingering
|
||||
return 24 + offset + trumpetHarmonic[K4][octaveR]; // roller harmonics
|
||||
} else if (FingeringMode::HRN == fingering) { // HRN fingering
|
||||
return 12 + offset + rollerHarmonic[K4][octaveR]; // roller harmonics
|
||||
} else if (FingeringMode::EVR == fingering) { // HRN fingering
|
||||
return 12 * (6 - octaveR) + offset;
|
||||
} else { // EVI
|
||||
return 12 * octaveR + offset;
|
||||
}
|
||||
}
|
||||
|
||||
int readSwitches() {
|
||||
// Keep the last fingering value for debouncing
|
||||
static int lastFingering = 0;
|
||||
static int fingeredNote = 0;
|
||||
static unsigned long lastDeglitchTime = 0; // The last time the fingering was changed
|
||||
|
||||
// Read touch pads (MPR121), compare against threshold value
|
||||
bool touchKeys[12];
|
||||
for (byte i = 0; i < 12; i++) {
|
||||
touchKeys[i] = readTouchKey(i) < calibration.ctouchThrVal;
|
||||
}
|
||||
|
||||
// Valves and trill keys, TRUE (1) for pressed, FALSE (0) for not pressed
|
||||
byte K1 = touchKeys[K1Pin]; // Valve 1 (pitch change -2)
|
||||
byte K2 = touchKeys[K2Pin]; // Valve 2 (pitch change -1)
|
||||
byte K3 = touchKeys[K3Pin]; // Valve 3 (pitch change -3)
|
||||
byte K4 = touchKeys[K4Pin]; // Left Hand index finger (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)
|
||||
|
||||
state.pinkyKey = touchKeys[K8Pin];
|
||||
|
||||
int qTransp = (state.pinkyKey && (currentPreset->pinkySetting < 25)) ? currentPreset->pinkySetting - 12 : 0;
|
||||
|
||||
// Calculate midi note number from pressed keys
|
||||
int fingeredNoteUntransposed = 0;
|
||||
if (EVI == 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
|
||||
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
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
if (K3 && K7) {
|
||||
if (4 == currentPreset->trill3_interval)
|
||||
fingeredNoteUntransposed += 2;
|
||||
else
|
||||
fingeredNoteUntransposed += 4;
|
||||
}
|
||||
|
||||
int fingeredNoteRead = fingeredNoteUntransposed + state.transpose - 12 + qTransp;
|
||||
|
||||
if (fingeredNoteRead != lastFingering) { //
|
||||
// reset the debouncing timer
|
||||
lastDeglitchTime = millis();
|
||||
}
|
||||
|
||||
if ((millis() - lastDeglitchTime) > currentPreset->deglitch) {
|
||||
// whatever the reading is at, it's been there for longer
|
||||
// than the debounce delay, so take it as the actual current state
|
||||
fingeredNote = fingeredNoteRead;
|
||||
}
|
||||
|
||||
lastFingering = fingeredNoteRead;
|
||||
|
||||
return fingeredNote;
|
||||
}
|
||||
|
||||
void noteOn(int fingeredNote, float pressureSensor, int initial_breath_value) {
|
||||
// Yes, so calculate MIDI note and velocity, then send a note on event
|
||||
// We should be at tonguing peak, so set velocity based on current pressureSensor value unless fixed velocity is set
|
||||
state.breathSignal = constrain(max(pressureSensor, initial_breath_value), state.breathThrVal, state.breathMaxVal);
|
||||
byte velocitySend;
|
||||
if (!currentPreset->fixedVelocity) {
|
||||
unsigned int breathValHires = breathCurve(mapConstrain(state.breathSignal, state.breathThrVal, state.breathMaxVal, 0, 16383));
|
||||
velocitySend = (breathValHires >> 7) & 0x007F;
|
||||
velocitySend = constrain(velocitySend + velocitySend * .1 * currentPreset->velBias, 1, 127);
|
||||
} else {
|
||||
velocitySend = currentPreset->fixedVelocity;
|
||||
}
|
||||
|
||||
breath(); // send breath data
|
||||
midiSendNoteOn(fingeredNote, velocitySend); // send Note On message for new note
|
||||
state.activeNote = fingeredNote;
|
||||
}
|
||||
|
||||
void handleOffStateActions() {
|
||||
if (state.activeMIDIchannel != currentPreset->MIDIchannel) {
|
||||
state.activeMIDIchannel = currentPreset->MIDIchannel; // only switch channel if no active note
|
||||
midiSetChannel(state.activeMIDIchannel);
|
||||
}
|
||||
if ((state.activePatch != state.patch) && state.doPatchUpdate) {
|
||||
state.activePatch = state.patch;
|
||||
midiSendProgramChange(state.activePatch);
|
||||
state.doPatchUpdate = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Initialize the main instrument state
|
||||
*/
|
||||
void initState() {
|
||||
state.activePatch = 0;
|
||||
state.mainState = NOTE_OFF; // initialize main state machine
|
||||
|
||||
state.activeMIDIchannel = currentPreset->MIDIchannel;
|
||||
midiInitialize(currentPreset->MIDIchannel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send CC data when needed
|
||||
*/
|
||||
void sendCCs() {
|
||||
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)
|
||||
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)) {
|
||||
breath();
|
||||
ccBreathSendTime = currentTime;
|
||||
}
|
||||
if (currentTime - ccSendTime > CC_INTERVAL_PRIMARY) {
|
||||
// deal with Pitch Bend, Modulation, etc.
|
||||
pitch_bend();
|
||||
biteCC_();
|
||||
ccSendTime = currentTime;
|
||||
}
|
||||
if (currentTime - ccSendTime2 > CC_INTERVAL_PORT) {
|
||||
portamento_();
|
||||
ccSendTime2 = currentTime;
|
||||
}
|
||||
if (currentTime - ccSendTime3 > CC_INTERVAL_OTHER) {
|
||||
updateSensorLEDs();
|
||||
ccSendTime3 = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main instrument state machine
|
||||
*/
|
||||
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 ((state.breathSignal > state.breathThrVal)) {
|
||||
// Value has risen above threshold. Move to the RISE_WAIT
|
||||
// state. Record time and initial breath value.
|
||||
breath_on_time = millis();
|
||||
initial_breath_value = state.breathSignal;
|
||||
state.mainState = RISE_WAIT; // Go to next state
|
||||
}
|
||||
} else if (state.mainState == RISE_WAIT) {
|
||||
if ((state.breathSignal > state.breathThrVal)) {
|
||||
// Has enough time passed for us to collect our second sample?
|
||||
if ((millis() - breath_on_time > currentPreset->velSmpDl) || (0 == currentPreset->velSmpDl) || currentPreset->fixedVelocity) {
|
||||
noteOn(fingeredNote, state.breathSignal, initial_breath_value);
|
||||
state.mainState = NOTE_ON;
|
||||
}
|
||||
} 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 ((state.breathSignal < state.breathThrVal)) {
|
||||
// Value has fallen below threshold - turn the note off
|
||||
midiSendNoteOff(state.activeNote); // send Note Off message
|
||||
state.breathSignal = 0;
|
||||
state.mainState = NOTE_OFF;
|
||||
} else {
|
||||
if (fingeredNote != state.activeNote) {
|
||||
// Player has moved to a new fingering while still blowing.
|
||||
// Send a note off for the current note and a note on for
|
||||
// the new note.
|
||||
noteOn(fingeredNote, state.breathSignal, 0);
|
||||
delayMicroseconds(2000); // delay for midi recording fix
|
||||
midiSendNoteOff(state.activeNote); // send Note Off message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
if (checkButtonState(DEBUG_CONFIG)) {
|
||||
Serial.begin(9600); // debug
|
||||
Serial.println("Debug Startup");
|
||||
}
|
||||
|
||||
bool factoryReset = checkButtonState(STARTUP_FACTORY_RESET);
|
||||
configManagementMode = checkButtonState(STARTUP_CONFIG);
|
||||
testMode = checkButtonState(TEST_CONFIG);
|
||||
|
||||
initDisplay(); // Start up display and show logo
|
||||
initHardware();
|
||||
|
||||
// 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);
|
||||
|
||||
statusLedFlash(500);
|
||||
statusLedOff();
|
||||
|
||||
if (factoryReset) {
|
||||
// Full calibration
|
||||
fullAutoCal();
|
||||
} else {
|
||||
// Minimal startup calibration (atmo pressure)
|
||||
autoCal();
|
||||
}
|
||||
|
||||
showVersion();
|
||||
delay(1000);
|
||||
|
||||
initState(); // Set up midi/etc
|
||||
statusLedOn(); // Switch on the onboard LED to indicate power on/ready
|
||||
}
|
||||
|
||||
//_______________________________________________________________________________________________ MAIN LOOP
|
||||
|
||||
void loop() {
|
||||
static unsigned long pixelUpdateTime = 0;
|
||||
static const unsigned long pixelUpdateInterval = 80;
|
||||
|
||||
// If in config mgmt loop, do that and nothing else
|
||||
if (configManagementMode) {
|
||||
configModeLoop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (testMode) {
|
||||
|
||||
}
|
||||
|
||||
state.breathSignal = constrain(readPressure(), BREATH_LO_LIMIT, BREATH_HI_LIMIT); // Get the filtered pressure sensor reading from analog pin A0, input from sensor MP3V5004GP
|
||||
|
||||
runStateMachine();
|
||||
sendCCs();
|
||||
|
||||
// cvUpdate();
|
||||
midiDiscardInput();
|
||||
|
||||
if (millis() - pixelUpdateTime > pixelUpdateInterval) {
|
||||
// even if we just alter a pixel, the whole display is redrawn (35ms of MPU lockup) and we can't do that all the time
|
||||
// this is one of the big reasons the display is for setup use only
|
||||
// TODO: is this still true on teensy 4?
|
||||
pixelUpdateTime = millis();
|
||||
handleMenu(true);
|
||||
} else {
|
||||
handleMenu(false);
|
||||
}
|
||||
|
||||
}
|
0
README.md
Executable file → Normal file
0
README.md
Executable file → Normal file
Loading…
Add table
Add a link
Reference in a new issue