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:
Brian Hrebec 2023-08-27 11:52:08 -05:00
parent c58c3f9e46
commit 01d193c9b3
92 changed files with 69119 additions and 73272 deletions

5
NuEVI/.gitignore vendored Normal file
View 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
View 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
View file

@ -0,0 +1,9 @@
{
"files.associations": {
"type_traits": "cpp",
"*.tcc": "cpp",
"ostream": "cpp",
"streambuf": "cpp",
"array": "cpp"
}
}

File diff suppressed because it is too large Load diff

View file

@ -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, &currentMenu->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;
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
}

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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.
*/

View file

@ -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

View file

@ -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

View file

@ -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();
}

View file

@ -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

View 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
View file

8
NuEVI/src/TODO Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View file

755
NuEVI/src/menu.cpp Normal file
View 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
View 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

View file

@ -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
View file

10
NuEVI/name.c → NuEVI/src/name.c Executable file → Normal file
View 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
View 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
View 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
View 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
View file

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

823
NuEVI/src/xEVI.cpp Normal file
View 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
View file