1372 lines
43 KiB
C++
1372 lines
43 KiB
C++
#include "menu.h"
|
|
#include "hardware.h"
|
|
#include "config.h"
|
|
#include "globals.h"
|
|
#include "midi.h"
|
|
|
|
#include <EEPROM.h>
|
|
#include <Adafruit_GFX.h>
|
|
#include <Adafruit_SSD1306.h>
|
|
#include <Adafruit_MPR121.h>
|
|
#include "settings.h"
|
|
#include "numenu.h"
|
|
|
|
// TODO: Ask Johan the reason for using this..
|
|
// static const uint16_t minOffset = 50;
|
|
|
|
static uint8_t lastDeumButtons = 0;
|
|
static uint8_t deumButtonState = 0;
|
|
static byte buttonPressedAndNotUsed = 0;
|
|
|
|
// Allocate some space for cursors
|
|
|
|
enum CursorIdx {
|
|
EMain,
|
|
EBreath,
|
|
EControl,
|
|
ERotator,
|
|
EVibrato,
|
|
|
|
// NEVER ADD
|
|
NUM_CURSORS
|
|
};
|
|
|
|
static byte cursors[CursorIdx::NUM_CURSORS];
|
|
static byte offsets[CursorIdx::NUM_CURSORS];
|
|
static byte activeSub[CursorIdx::NUM_CURSORS];
|
|
|
|
byte cursorNow;
|
|
|
|
static byte forceRedraw = 0;
|
|
static byte FPD = 0;
|
|
|
|
|
|
// 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 lastDebounceTime = 0; // the last time the output pin was toggled
|
|
static unsigned long buttonRepeatTime = 0;
|
|
static unsigned long buttonPressedTime = 0;
|
|
static unsigned long menuTime = 0;
|
|
static unsigned long patchViewTime = 0;
|
|
unsigned long cursorBlinkTime = 0; // the last time the cursor was toggled
|
|
|
|
//Display state
|
|
static byte state = DISPLAYOFF_IDL;
|
|
static byte stateFirstRun = 1;
|
|
|
|
static byte subParallel = 0;
|
|
static byte subRotator = 0;
|
|
static byte subPriority = 0;
|
|
|
|
// The external function of subSquelch has been broken,
|
|
// need to come up with a smart way to make it work again.
|
|
// The status led was update when the Squelch menu was open.
|
|
byte subVibSquelch = 0; //extern
|
|
|
|
// 'NuEVI' 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);
|
|
extern Adafruit_MPR121 touchSensor;
|
|
|
|
extern const MenuPage mainMenuPage; // Forward declaration.
|
|
|
|
|
|
#define OLED_RESET 4
|
|
Adafruit_SSD1306 display(128, 64, &Wire, OLED_RESET);
|
|
|
|
void initDisplay() {
|
|
|
|
// by default, we'll generate the high voltage from the 3.3v line internally! (neat!)
|
|
display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // 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();
|
|
display.drawBitmap(0,0,nuevi_logo_bmp,LOGO16_GLCD_WIDTH,LOGO16_GLCD_HEIGHT,1);
|
|
display.display();
|
|
|
|
memset(cursors, 0, sizeof(cursors));
|
|
memset(offsets, 0, sizeof(offsets));
|
|
memset(activeSub, 0, sizeof(activeSub));
|
|
}
|
|
|
|
void showVersion() {
|
|
display.setTextColor(WHITE);
|
|
display.setTextSize(1);
|
|
#if defined(CASSIDY)
|
|
display.setCursor(0,0);
|
|
display.print("BC");
|
|
#endif
|
|
#if defined(CVSCALEBOARD)
|
|
display.setCursor(15,0);
|
|
display.print("CV");
|
|
#endif
|
|
display.setCursor(85,52);
|
|
display.print("v.");
|
|
display.println(FIRMWARE_VERSION);
|
|
display.display();
|
|
}
|
|
|
|
// Assumes dest points to a buffer of atleast 7 bytes.
|
|
static const char* numToString(int16_t value, char* dest, bool plusSign = false) {
|
|
char* ptr = dest;
|
|
uint16_t absVal = abs(value);
|
|
int c = 1;
|
|
|
|
if(value < 0) {
|
|
*ptr++ = '-';
|
|
} else if(plusSign && value) {
|
|
*ptr++ = '+';
|
|
}
|
|
while((c*10) < absVal+1) c *= 10;
|
|
while(c > 0) {
|
|
int tmp = absVal / c;
|
|
*ptr++ = tmp + '0';
|
|
absVal %= c;
|
|
c /= 10;
|
|
}
|
|
*ptr = 0;
|
|
return dest;
|
|
}
|
|
|
|
static void plotSubOption(const char* label, int color) {
|
|
display.setTextColor(color);
|
|
display.setTextSize(2);
|
|
int x_pos = 96-strlen(label)*6;
|
|
display.setCursor(x_pos,33);
|
|
display.println(label);
|
|
}
|
|
|
|
static bool drawSubMenu(const MenuPage &page, int color) {
|
|
int index = cursors[page.cursor];
|
|
// TODO: Handle MenuEntrySubRotator case
|
|
// TODO: Null check subMenuFunc
|
|
const MenuEntry* subEntry = page.entries[index];
|
|
switch(subEntry->type) {
|
|
case MenuType::ESub:
|
|
((const MenuEntrySub*)subEntry)->subMenuFunc(color);
|
|
break;
|
|
|
|
case MenuType::ESubNew:
|
|
{
|
|
char buffer[12];
|
|
const char* labelPtr = nullptr;
|
|
const MenuEntrySubNew* sub = (const MenuEntrySubNew*)subEntry;
|
|
sub->getSubTextFunc(buffer, &labelPtr);
|
|
|
|
// If ECustom flag is set, we assume that the getSubTextFunc
|
|
// rendered by it self.
|
|
if( !(sub->flags & MenuEntryFlags::ECustom)) {
|
|
plotSubOption(buffer, color);
|
|
if(labelPtr != nullptr) {
|
|
// TODO: handle this better, we should center text + label
|
|
display.setCursor(105,40);
|
|
display.setTextSize(1);
|
|
display.println(labelPtr);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void clearSub(){
|
|
display.fillRect(63,11,64,52,BLACK);
|
|
}
|
|
|
|
static void clearSubValue() {
|
|
display.fillRect(65, 24, 60, 37, BLACK);
|
|
}
|
|
|
|
static bool updateSubMenuCursor(const MenuPage &page, uint32_t timeNow)
|
|
{
|
|
if ((timeNow - cursorBlinkTime) > cursorBlinkInterval) {
|
|
cursorBlinkTime = timeNow;
|
|
if (cursorNow == WHITE) {
|
|
cursorNow = BLACK;
|
|
clearSubValue();
|
|
} else {
|
|
cursorNow = WHITE;
|
|
return drawSubMenu( page, cursorNow );
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void plotMenuEntries(const MenuPage &page, bool clear = false) {
|
|
int row = 0;
|
|
if(clear) {
|
|
display.fillRect( 0, MENU_HEADER_OFFSET, 56, 64-MENU_HEADER_OFFSET, BLACK );
|
|
}
|
|
for(int item = offsets[page.cursor]; (item < page.numEntries) && (row < MENU_NUM_ROWS); item++, row++) {
|
|
int rowPixel = (row)*MENU_ROW_HEIGHT + MENU_HEADER_OFFSET;
|
|
const char* lineText = page.entries[item]->title;
|
|
display.setCursor(0,rowPixel);
|
|
display.println(lineText);
|
|
}
|
|
}
|
|
|
|
static void drawMenu(const MenuPage &page, const char* customTitle = nullptr) {
|
|
//Initialize display and draw menu header + line
|
|
display.clearDisplay();
|
|
display.setTextSize(1);
|
|
display.setTextColor(WHITE);
|
|
display.setCursor(0,0);
|
|
if(customTitle)
|
|
display.println(customTitle);
|
|
else
|
|
display.println(page.title);
|
|
display.drawLine(0, MENU_ROW_HEIGHT, 127, MENU_ROW_HEIGHT, WHITE);
|
|
|
|
plotMenuEntries(page);
|
|
}
|
|
|
|
static void drawMenuScreen() {
|
|
//Construct the title including voltage reading.
|
|
//Involves intricate splicing of the title string with battery voltage
|
|
char menuTitle[] = "MENU XXX Y.YV"; //Allocate string buffer of appropriate size with some placeholders
|
|
char* splice1 = menuTitle + 13;
|
|
char* splice2 = menuTitle + 17;
|
|
|
|
int vMeterReading = analogRead(vMeterPin);
|
|
memcpy(splice1, (vMeterReading > 3000) ? "USB" : "BAT", 3);
|
|
if (vMeterReading < 2294) {
|
|
memcpy(splice2, "LOW ", 4);
|
|
} else {
|
|
int voltage = map(vMeterReading,0,3030,0,50);
|
|
splice2[0] = (voltage/10)+'0';
|
|
splice2[2] = (voltage%10)+'0';
|
|
}
|
|
drawMenu(mainMenuPage, menuTitle);
|
|
}
|
|
|
|
static void drawTrills(){
|
|
if (K5) display.fillRect(0,0,5,5,WHITE); else display.drawRect(0,0,5,5,WHITE);
|
|
if (K6) display.fillRect(10,0,5,5,WHITE); else display.drawRect(10,0,5,5,WHITE);
|
|
if (K7) display.fillRect(20,0,5,5,WHITE); else display.drawRect(20,0,5,5,WHITE);
|
|
}
|
|
|
|
static void drawPatchView(){
|
|
display.clearDisplay();
|
|
if (FPD){
|
|
drawTrills();
|
|
}
|
|
display.setTextColor(WHITE);
|
|
display.setTextSize(6);
|
|
if (FPD < 2){
|
|
int align;
|
|
if (patch < 10) { // 1-9
|
|
align = 48;
|
|
} else if (patch < 100) { // 10-99
|
|
align = 31;
|
|
} else { // 100-128 use default
|
|
align = 10;
|
|
}
|
|
display.setCursor(align,10);
|
|
display.println(patch);
|
|
} else if (FPD == 2){
|
|
display.setCursor(10,10);
|
|
display.println("SET");
|
|
} else {
|
|
display.setCursor(10,10);
|
|
display.println("CLR");
|
|
}
|
|
}
|
|
|
|
|
|
static void drawSubBox(const char* label)
|
|
{
|
|
display.fillRect(63,11,64,52,BLACK);
|
|
display.drawRect(63,11,64,52,WHITE);
|
|
display.setTextColor(WHITE);
|
|
display.setTextSize(1);
|
|
int len = strlen(label);
|
|
|
|
display.setCursor(95-len*3,15);
|
|
display.println(label);
|
|
}
|
|
|
|
void drawMenuCursor(byte itemNo, byte color){
|
|
byte ymid = 15 + 9 * itemNo;
|
|
display.drawTriangle(57, ymid,61, ymid+2,61, ymid-2, color);
|
|
}
|
|
|
|
static void plotRotator(int color,int value){
|
|
char buff[12];
|
|
numToString(value, buff, true);
|
|
plotSubOption(buff, color);
|
|
}
|
|
|
|
static void plotPriority(int color){
|
|
if (priority){
|
|
plotSubOption("ROT", color);
|
|
} else {
|
|
plotSubOption("MEL", color);
|
|
}
|
|
}
|
|
|
|
static void plotMIDI(int color) {
|
|
char buff[7];
|
|
numToString(MIDIchannel, buff);
|
|
plotSubOption(buff, color);
|
|
if (slowMidi && color) {
|
|
display.setTextColor(WHITE);
|
|
} else {
|
|
display.setTextColor(BLACK);
|
|
}
|
|
display.setTextSize(1);
|
|
display.setCursor(116,51);
|
|
display.print("S");
|
|
}
|
|
|
|
static void drawSubRotator(int __unused color){
|
|
// HACKY HACK ROTATOR MENU
|
|
// drawSubBox("SEMITONES");
|
|
//plotRotator(WHITE,value);
|
|
forceRedraw = 1;
|
|
}
|
|
|
|
//***********************************************************
|
|
|
|
// TODO: Move these to a settings.cpp maybe?
|
|
void writeSetting(byte address, unsigned short value){
|
|
union {
|
|
byte v[2];
|
|
unsigned short val;
|
|
} data;
|
|
data.val = value;
|
|
EEPROM.update(address, data.v[0]);
|
|
EEPROM.update(address+1, data.v[1]);
|
|
}
|
|
|
|
unsigned short readSetting(byte address){
|
|
union {
|
|
byte v[2];
|
|
unsigned short val;
|
|
} data;
|
|
data.v[0] = EEPROM.read(address);
|
|
data.v[1] = EEPROM.read(address+1);
|
|
return data.val;
|
|
}
|
|
|
|
//***********************************************************
|
|
|
|
static int readTrills() {
|
|
readSwitches();
|
|
return K5+2*K6+4*K7;
|
|
}
|
|
|
|
//***********************************************************
|
|
|
|
static void setFPS(int trills, uint16_t patchNum) {
|
|
fastPatch[trills-1] = patchNum;
|
|
writeSetting(FP1_ADDR+2*(trills-1), patchNum);
|
|
FPD = 2;
|
|
}
|
|
|
|
//***********************************************************
|
|
|
|
static void clearFPS(int trills) {
|
|
fastPatch[trills-1] = 0;
|
|
writeSetting(FP1_ADDR+2*(trills-1),0);
|
|
FPD = 3;
|
|
}
|
|
|
|
//***********************************************************
|
|
// Main menu
|
|
const MenuEntrySubNew transposeMenu = {
|
|
MenuType::ESubNew, "TRANSPOSE", "TRANSPOSE", &transpose, 0, 24, MenuEntryFlags::ENone,
|
|
[](char* out, const char** __unused unit) {
|
|
numToString(transpose - 12, out, true);
|
|
},
|
|
[]() { writeSetting(TRANSP_ADDR,transpose); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySubNew octaveMenu = {
|
|
MenuType::ESubNew, "OCTAVE", "OCTAVE", &octave, 0, 6, MenuEntryFlags::ENone,
|
|
[](char* out, const char** __unused unit) {
|
|
numToString(octave-3, out, true);
|
|
},
|
|
[]() { writeSetting(OCTAVE_ADDR,octave); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySubNew midiMenu = {
|
|
MenuType::ESubNew, "MIDI CH", "MIDI CHNL", &MIDIchannel, 1, 16, MenuEntryFlags::ECustom | MenuEntryFlags::EEnterHandler,
|
|
[](char* __unused out, const char** __unused unit) {
|
|
plotMIDI(WHITE);
|
|
},
|
|
[]() { writeSetting(MIDI_ADDR, MIDIchannel); },
|
|
[]() -> bool { // On enter handler
|
|
readSwitches();
|
|
if (pinkyKey){
|
|
slowMidi = !slowMidi;
|
|
dipSwBits = dipSwBits ^ (1<<3);
|
|
writeSetting(DIPSW_BITS_ADDR,dipSwBits);
|
|
return false;
|
|
} else {
|
|
writeSetting(MIDI_ADDR, MIDIchannel);
|
|
return true;
|
|
}
|
|
}
|
|
};
|
|
|
|
const MenuEntryStateCh adjustMenu = { MenuType::EStateChange, "ADJUST", ADJUST_MENU };
|
|
const MenuEntryStateCh breathMenu = { MenuType::EStateChange, "SETUP BR", SETUP_BR_MENU };
|
|
const MenuEntryStateCh controlMenu = { MenuType::EStateChange, "SETUP CTL", SETUP_CT_MENU };
|
|
|
|
const MenuEntry* mainMenuEntries[] = {
|
|
(MenuEntry*)&transposeMenu,
|
|
(MenuEntry*)&octaveMenu,
|
|
(MenuEntry*)&midiMenu,
|
|
(MenuEntry*)&adjustMenu,
|
|
(MenuEntry*)&breathMenu,
|
|
(MenuEntry*)&controlMenu
|
|
};
|
|
|
|
const MenuPage mainMenuPage = {
|
|
nullptr,
|
|
CursorIdx::EMain,
|
|
DISPLAYOFF_IDL,
|
|
ARR_LEN(mainMenuEntries), mainMenuEntries
|
|
};
|
|
|
|
// Rotator menu
|
|
const MenuEntrySub rotatorParaMenu = { MenuType::ESub, "PARALLEL", "SEMITONES", &subParallel, drawSubRotator };
|
|
const MenuEntrySubRotator rotator1Menu = { MenuType::ESubRotator, "ROTATE 1", "SEMITONES", 1, &subRotator, drawSubRotator };
|
|
const MenuEntrySubRotator rotator2Menu = { MenuType::ESubRotator, "ROTATE 2", "SEMITONES", 2, &subRotator, drawSubRotator };
|
|
const MenuEntrySubRotator rotator3Menu = { MenuType::ESubRotator, "ROTATE 3", "SEMITONES", 3, &subRotator, drawSubRotator };
|
|
const MenuEntrySubRotator rotator4Menu = { MenuType::ESubRotator, "ROTATE 4", "SEMITONES", 4, &subRotator, drawSubRotator };
|
|
const MenuEntrySub rotatorPrioMenu = { MenuType::ESub, "PRIORITY", "MONO PRIO", &subPriority, plotPriority };
|
|
|
|
const MenuEntry* rotatorMenuEntries[] = {
|
|
(MenuEntry*)&rotatorParaMenu,
|
|
(MenuEntry*)&rotator1Menu,
|
|
(MenuEntry*)&rotator2Menu,
|
|
(MenuEntry*)&rotator3Menu,
|
|
(MenuEntry*)&rotator4Menu,
|
|
(MenuEntry*)&rotatorPrioMenu
|
|
};
|
|
|
|
const MenuPage rotatorMenuPage = {
|
|
"ROTATOR SETUP",
|
|
CursorIdx::ERotator,
|
|
DISPLAYOFF_IDL,
|
|
ARR_LEN(rotatorMenuEntries), rotatorMenuEntries
|
|
};
|
|
|
|
//***********************************************************
|
|
// Breath menu
|
|
const MenuEntrySubNew breathCCMenu = {
|
|
MenuType::ESubNew, "BREATH CC", "BREATH CC", &breathCC, 0, 10, MenuEntryFlags::EWrap,
|
|
[](char* out, const char** __unused unit) {
|
|
const char* breathCCMenuLabels[] = { "OFF", "MW", "BR", "VL", "EX", "MW+",
|
|
"BR+", "VL+", "EX+", "CF", "20" };
|
|
strncpy(out, breathCCMenuLabels[breathCC], 4);
|
|
},
|
|
[](){
|
|
if (readSetting(BREATH_CC_ADDR) != breathCC) {
|
|
writeSetting(BREATH_CC_ADDR,breathCC);
|
|
midiReset();
|
|
}
|
|
}
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySubNew breathATMenu = {
|
|
MenuType::ESubNew, "BREATH AT", "BREATH AT", &breathAT, 0, 1, MenuEntryFlags::EWrap,
|
|
[](char* out, const char ** __unused unit) {
|
|
strncpy(out, breathAT?"ON":"OFF", 4);
|
|
}, []() {
|
|
if (readSetting(BREATH_AT_ADDR) != breathAT) {
|
|
writeSetting(BREATH_AT_ADDR, breathAT);
|
|
midiReset();
|
|
}
|
|
}
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySubNew velocityMenu = {
|
|
MenuType::ESubNew, "VELOCITY", "VELOCITY", &velocity, 0, 127, MenuEntryFlags::EWrap,
|
|
[](char* out, const char** __unused unit) {
|
|
if(velocity) numToString(velocity, out);
|
|
else strncpy(out, "DYN", 4);
|
|
},
|
|
[]() { writeSetting(VELOCITY_ADDR,velocity); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySubNew curveMenu = {
|
|
MenuType::ESubNew, "CURVE", "CURVE", &curve, 0, 12, MenuEntryFlags::EWrap,
|
|
[](char* out, const char** __unused unit) {
|
|
const char* curveMenuLabels[] = {"-4", "-3", "-2", "-1", "LIN", "+1", "+2",
|
|
"+3", "+4", "S1", "S2", "Z1", "Z2" };
|
|
strncpy(out, curveMenuLabels[curve], 4);
|
|
},
|
|
[](){ writeSetting(BREATHCURVE_ADDR,curve); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySubNew velSmpDlMenu = {
|
|
MenuType::ESubNew, "VEL DELAY", "VEL DELAY", &velSmpDl, 0, 30, MenuEntryFlags::EWrap,
|
|
[](char *out, const char** label) {
|
|
if (velSmpDl) {
|
|
numToString(velSmpDl, out);
|
|
*label = "ms";
|
|
} else strncpy(out, "OFF", 4);
|
|
},
|
|
[]() { writeSetting(VEL_SMP_DL_ADDR,velSmpDl); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySubNew velBiasMenu = {
|
|
MenuType::ESubNew, "VEL BIAS", "VEL BIAS", &velBias, 0, 9, MenuEntryFlags::EWrap,
|
|
[](char* out, const char** __unused unit) {
|
|
if (velBias) numToString(velBias, out);
|
|
else strncpy(out, "OFF", 4);
|
|
},
|
|
[](){ writeSetting(VEL_BIAS_ADDR,velBias); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntry* breathMenuEntries[] = {
|
|
(MenuEntry*)&breathCCMenu,
|
|
(MenuEntry*)&breathATMenu,
|
|
(MenuEntry*)&velocityMenu,
|
|
(MenuEntry*)&curveMenu,
|
|
(MenuEntry*)&velSmpDlMenu,
|
|
(MenuEntry*)&velBiasMenu
|
|
};
|
|
|
|
const MenuPage breathMenuPage = {
|
|
"SETUP BREATH",
|
|
CursorIdx::EBreath,
|
|
MAIN_MENU,
|
|
ARR_LEN(breathMenuEntries), breathMenuEntries
|
|
};
|
|
|
|
//***********************************************************
|
|
// Control menu
|
|
const MenuEntrySubNew portMenu = {
|
|
MenuType::ESubNew, "PORT/GLD", "PORT/GLD", &portamento, 0, 2, MenuEntryFlags::EWrap,
|
|
[](char* out, const char ** __unused unit) {
|
|
const char* labs[] = { "OFF", "ON", "SW" };
|
|
strncpy(out, labs[portamento], 4);
|
|
},
|
|
[]() { writeSetting(PORTAM_ADDR,portamento); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySubNew pitchBendMenu = {
|
|
MenuType::ESubNew, "PITCHBEND", "PITCHBEND", &PBdepth, 0, 12, MenuEntryFlags::ENone,
|
|
[](char* out, const char** __unused unit) {
|
|
if(PBdepth) {
|
|
memcpy(out, "1/", 2);
|
|
numToString(PBdepth, &out[2]);
|
|
}
|
|
else strncpy(out, "OFF", 4);
|
|
},
|
|
[](){ writeSetting(PB_ADDR,PBdepth); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySubNew extraMenu = {
|
|
MenuType::ESubNew, "EXTRA CTR", "EXTRA CTR", &extraCT, 0,4, MenuEntryFlags::EWrap,
|
|
[](char* out, const char** __unused unit) {
|
|
const char* extraMenuLabels[] = { "OFF", "MW", "FP", "CF", "SP" };
|
|
strncpy(out, extraMenuLabels[extraCT], 12);
|
|
},
|
|
[]() { writeSetting(EXTRA_ADDR,extraCT); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntryStateCh vibratoSubMenu = { MenuType::EStateChange, "VIBRATO", VIBRATO_MENU };
|
|
|
|
const MenuEntrySubNew deglitchMenu = {
|
|
MenuType::ESubNew, "DEGLITCH", "DEGLITCH", °litch, 0, 70, MenuEntryFlags::ENone,
|
|
[](char* textBuffer, const char** label) {
|
|
if(deglitch) {
|
|
numToString(deglitch, textBuffer);
|
|
*label = "ms";
|
|
} else
|
|
strncpy(textBuffer, "OFF", 4);
|
|
},
|
|
[]() { writeSetting(DEGLITCH_ADDR,deglitch); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySubNew pinkyMenu = {
|
|
MenuType::ESubNew, "PINKY KEY", "PINKY KEY", &pinkySetting, 0, 24, MenuEntryFlags::ENone,
|
|
[](char* textBuffer, const char** __unused unit) {
|
|
if (pinkySetting == PBD)
|
|
strncpy(textBuffer, "PBD", 4);
|
|
else
|
|
numToString(pinkySetting-12, textBuffer, true);
|
|
},
|
|
[]() { writeSetting(PINKY_KEY_ADDR,pinkySetting); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntry* controlMenuEntries[] = {
|
|
(MenuEntry*)&portMenu,
|
|
(MenuEntry*)&pitchBendMenu,
|
|
(MenuEntry*)&extraMenu,
|
|
(MenuEntry*)&vibratoSubMenu,
|
|
(MenuEntry*)°litchMenu,
|
|
(MenuEntry*)&pinkyMenu
|
|
};
|
|
|
|
const MenuPage controlMenuPage = {
|
|
"SETUP CTRLS",
|
|
CursorIdx::EControl,
|
|
MAIN_MENU,
|
|
ARR_LEN(controlMenuEntries), controlMenuEntries
|
|
};
|
|
|
|
|
|
//***********************************************************
|
|
// Vibrato menu
|
|
|
|
static void vibGetStr(char* textBuffer, const char** __unused unit) {
|
|
if(vibrato)
|
|
numToString(vibrato, textBuffer);
|
|
else
|
|
strncpy(textBuffer, "OFF", 4);
|
|
}
|
|
|
|
static void vibStore() {
|
|
writeSetting(VIBRATO_ADDR,vibrato);
|
|
}
|
|
|
|
const MenuEntrySubNew vibDepthMenu = {
|
|
MenuType::ESubNew, "DEPTH", "LEVEL", &vibrato, 0, 9, MenuEntryFlags::ENone,
|
|
vibGetStr,
|
|
vibStore,
|
|
nullptr
|
|
};
|
|
|
|
const MenuEntrySubNew vibSenseMenu = {
|
|
MenuType::ESubNew, "SENSE", "LEVEL", &vibSens, 1, 12, MenuEntryFlags::ENone,
|
|
[](char* textBuffer, const char** __unused unit) {
|
|
numToString(vibSens, textBuffer);
|
|
},
|
|
[]() { writeSetting(VIB_SENS_ADDR,vibSens); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySubNew vibRetnMenu = {
|
|
MenuType::ESubNew, "RETURN", "LEVEL", &vibRetn, 0, 4, MenuEntryFlags::ENone,
|
|
[](char* textBuffer, const char** __unused unit) {
|
|
numToString(vibRetn, textBuffer);
|
|
},
|
|
[]() { writeSetting(VIB_RETN_ADDR,vibRetn); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySubNew vibSquelchMenu = {
|
|
MenuType::ESubNew, "SQUELCH", "LEVEL", &vibSquelch, 1, 30, MenuEntryFlags::ENone,
|
|
[](char* textBuffer, const char** __unused unit) {
|
|
numToString(vibSquelch, textBuffer);
|
|
},
|
|
[]() { writeSetting(VIB_SQUELCH_ADDR,vibSquelch); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySubNew vibDirMenu = {
|
|
MenuType::ESubNew, "DIRECTION", "DIRECTION", &vibDirection , 0, 1, MenuEntryFlags::EWrap,
|
|
[](char* out, const char** __unused unit) {
|
|
if (DNWD == vibDirection)
|
|
strncpy(out, "NRM", 4);
|
|
else
|
|
strncpy(out, "REV", 4);
|
|
},
|
|
[]() { writeSetting(VIB_DIRECTION_ADDR,vibDirection); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntry* vibratorMenuEntries[] = {
|
|
(MenuEntry*)&vibDepthMenu,
|
|
(MenuEntry*)&vibSenseMenu,
|
|
(MenuEntry*)&vibRetnMenu,
|
|
(MenuEntry*)&vibSquelchMenu,
|
|
(MenuEntry*)&vibDirMenu
|
|
};
|
|
|
|
const MenuPage vibratoMenuPage = {
|
|
"VIBRATO",
|
|
CursorIdx::EVibrato,
|
|
SETUP_CT_MENU,
|
|
ARR_LEN(vibratorMenuEntries), vibratorMenuEntries
|
|
};
|
|
|
|
|
|
//***********************************************************
|
|
|
|
static bool ExecuteMenuSelection(const MenuPage &page) //int cursorPosition, const struct MenuEntry *menuEntry)
|
|
{
|
|
int cursorPosition = cursors[page.cursor];
|
|
const MenuEntry* menuEntry = page.entries[cursorPosition];
|
|
cursorBlinkTime = millis();
|
|
|
|
switch(menuEntry->type) {
|
|
case MenuType::ESub:
|
|
*((const MenuEntrySub*)menuEntry)->flag = 1;
|
|
activeSub[page.cursor] = cursorPosition+1;
|
|
drawMenuCursor(cursorPosition, WHITE);
|
|
drawSubBox( ((const MenuEntrySub*)menuEntry)->subTitle);
|
|
((const MenuEntrySub*)menuEntry)->subMenuFunc(WHITE);
|
|
return true;
|
|
|
|
case MenuType::ESubNew:
|
|
activeSub[page.cursor] = cursorPosition+1;
|
|
drawMenuCursor(cursorPosition, WHITE);
|
|
drawSubBox( ((const MenuEntrySubNew*)menuEntry)->subTitle);
|
|
drawSubMenu(page, WHITE);
|
|
return true;
|
|
|
|
case MenuType::EStateChange:
|
|
state = ((const MenuEntryStateCh*)menuEntry)->state;
|
|
stateFirstRun = 1;
|
|
break;
|
|
|
|
case MenuType::ESubRotator:
|
|
activeSub[page.cursor] = cursorPosition+1;
|
|
*((const MenuEntrySubRotator*)menuEntry)->flag = ((const MenuEntrySubRotator*)menuEntry)->flagValue;
|
|
drawMenuCursor(cursorPosition, WHITE);
|
|
((const MenuEntrySubRotator*)menuEntry)->subMenuFunc(WHITE);
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//***********************************************************
|
|
|
|
static bool selectMenuOption(const MenuPage &page){
|
|
// const MenuEntry* entry = menuEntries[cursorPosition];
|
|
return ExecuteMenuSelection( page );
|
|
}
|
|
|
|
//***********************************************************
|
|
|
|
static bool updateSubMenu(const MenuPage &page, uint32_t timeNow) {
|
|
|
|
bool redraw = false;
|
|
bool redrawSubValue = false;
|
|
if (buttonPressedAndNotUsed){
|
|
buttonPressedAndNotUsed = 0;
|
|
|
|
int current_sub = activeSub[page.cursor] -1;
|
|
|
|
if( current_sub < 0)
|
|
return false;
|
|
|
|
auto sub = (const MenuEntrySubNew*)page.entries[current_sub];
|
|
uint16_t currentVal = *sub->valuePtr;
|
|
|
|
switch (deumButtonState){
|
|
case BTN_DOWN:
|
|
if(currentVal > sub->min) {
|
|
currentVal -= 1;
|
|
} else if(sub->flags & MenuEntryFlags::EWrap) {
|
|
currentVal = sub->max;
|
|
}
|
|
break;
|
|
|
|
case BTN_UP:
|
|
if(currentVal < sub->max) {
|
|
currentVal += 1;
|
|
} else if(sub->flags & MenuEntryFlags::EWrap) {
|
|
currentVal = sub->min;
|
|
}
|
|
break;
|
|
|
|
case BTN_ENTER:
|
|
if(sub->flags & MenuEntryFlags::EEnterHandler) {
|
|
bool result = sub->onEnterFunc();
|
|
if(result) {
|
|
activeSub[page.cursor] = 0;
|
|
}
|
|
} else {
|
|
activeSub[page.cursor] = 0;
|
|
sub->applyFunc();
|
|
}
|
|
break;
|
|
|
|
case BTN_MENU:
|
|
activeSub[page.cursor] = 0;
|
|
sub->applyFunc();
|
|
break;
|
|
}
|
|
*sub->valuePtr = currentVal;
|
|
redrawSubValue = true;
|
|
} else {
|
|
redraw = updateSubMenuCursor( page, timeNow );
|
|
}
|
|
|
|
if(redrawSubValue) {
|
|
clearSubValue();
|
|
redraw |= drawSubMenu(page, WHITE);
|
|
cursorNow = BLACK;
|
|
cursorBlinkTime = timeNow;
|
|
}
|
|
|
|
return redraw;
|
|
}
|
|
|
|
static bool updateMenuPage( const MenuPage &page, uint32_t timeNow ) {
|
|
byte cursorPos = cursors[page.cursor];
|
|
byte newPos = cursorPos;
|
|
bool redraw = false;
|
|
|
|
if (buttonPressedAndNotUsed) {
|
|
|
|
int lastEntry = page.numEntries-1;
|
|
|
|
buttonPressedAndNotUsed = 0;
|
|
switch (deumButtonState) {
|
|
case BTN_DOWN:
|
|
if (cursorPos < lastEntry)
|
|
newPos = cursorPos+1;
|
|
break;
|
|
|
|
case BTN_ENTER:
|
|
// redraw |= selectMenuOption(cursorPos, page.entries);
|
|
redraw |= selectMenuOption(page);
|
|
break;
|
|
|
|
case BTN_UP:
|
|
if (cursorPos > 0)
|
|
newPos = cursorPos-1;
|
|
break;
|
|
|
|
case BTN_MENU:
|
|
state = page.parentPage;
|
|
stateFirstRun = 1;
|
|
break;
|
|
}
|
|
|
|
if(newPos != cursorPos) {
|
|
drawMenuCursor(cursorPos, BLACK);
|
|
drawMenuCursor(newPos, WHITE);
|
|
cursorNow = BLACK;
|
|
clearSub();
|
|
redraw = true;
|
|
cursors[page.cursor] = newPos;
|
|
}
|
|
} else if ((timeNow - cursorBlinkTime) > cursorBlinkInterval) {
|
|
// Only need to update cursor blink if no buttons were pressed
|
|
if (cursorNow == WHITE) cursorNow = BLACK; else cursorNow = WHITE;
|
|
drawMenuCursor(cursorPos, cursorNow);
|
|
redraw = true;
|
|
cursorBlinkTime = timeNow;
|
|
}
|
|
|
|
return redraw;
|
|
}
|
|
|
|
static bool updatePage(const MenuPage &page, uint32_t timeNow) {
|
|
if (stateFirstRun) {
|
|
drawMenu(page);
|
|
stateFirstRun = 0;
|
|
}
|
|
if (activeSub[page.cursor]) {
|
|
return updateSubMenu(page, timeNow);
|
|
} else {
|
|
return updateMenuPage(page, timeNow);
|
|
}
|
|
}
|
|
|
|
//***********************************************************
|
|
static void checkForPatchView(int buttons) {
|
|
int trills = readTrills();
|
|
|
|
switch (buttons){
|
|
case BTN_MENU+BTN_DOWN:
|
|
break;
|
|
|
|
case BTN_MENU+BTN_ENTER:
|
|
if (trills){
|
|
state = PATCH_VIEW;
|
|
stateFirstRun = 1;
|
|
setFPS(trills, patch);
|
|
}
|
|
break;
|
|
|
|
case BTN_MENU+BTN_UP:
|
|
if (trills){
|
|
state = PATCH_VIEW;
|
|
stateFirstRun = 1;
|
|
clearFPS(trills);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// This should be moved to a separate file/process that handles only led
|
|
static void statusBlink() {
|
|
digitalWrite(statusLedPin,LOW);
|
|
delay(150);
|
|
digitalWrite(statusLedPin,HIGH);
|
|
delay(150);
|
|
digitalWrite(statusLedPin,LOW);
|
|
delay(150);
|
|
digitalWrite(statusLedPin,HIGH);
|
|
}
|
|
|
|
static bool updateSensorPixelsFlag = false;
|
|
|
|
void drawSensorPixels() {
|
|
updateSensorPixelsFlag = true;
|
|
}
|
|
|
|
|
|
//***********************************************************
|
|
|
|
void menu() {
|
|
unsigned long timeNow = millis();
|
|
const MenuPage *currentPage = nullptr;
|
|
|
|
bool redrawSubValue = false;
|
|
bool redraw = stateFirstRun;
|
|
// read the state of the switches
|
|
uint8_t deumButtons = 0x0f ^(digitalRead(dPin) | (digitalRead(ePin) << 1) | (digitalRead(uPin) << 2) | (digitalRead(mPin)<<3));
|
|
|
|
// 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) {
|
|
deumButtonState = deumButtons;
|
|
menuTime = timeNow;
|
|
buttonPressedAndNotUsed = 1;
|
|
buttonPressedTime = timeNow;
|
|
}
|
|
|
|
if (((deumButtons == 1) || (deumButtons == 4)) && (timeNow - buttonPressedTime > buttonRepeatDelay) && (timeNow - buttonRepeatTime > buttonRepeatInterval)){
|
|
buttonPressedAndNotUsed = 1;
|
|
buttonRepeatTime = timeNow;
|
|
}
|
|
}
|
|
|
|
|
|
// save the reading. Next time through the loop, it'll be the lastButtonState:
|
|
lastDeumButtons = deumButtons;
|
|
|
|
// 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 (state && ((timeNow - menuTime) > menuTimeUp)) {
|
|
state = DISPLAYOFF_IDL;
|
|
stateFirstRun = 1;
|
|
|
|
subParallel = 0;
|
|
subRotator = 0;
|
|
subPriority = 0;
|
|
|
|
subVibSquelch = 0;
|
|
memset(activeSub, 0, sizeof(activeSub));
|
|
}
|
|
|
|
if (state == DISPLAYOFF_IDL) {
|
|
if (stateFirstRun) {
|
|
display.ssd1306_command(SSD1306_DISPLAYOFF);
|
|
stateFirstRun = 0;
|
|
}
|
|
if (buttonPressedAndNotUsed) {
|
|
buttonPressedAndNotUsed = 0;
|
|
int trills = readTrills();
|
|
switch (deumButtonState){
|
|
case BTN_UP: // fallthrough
|
|
case BTN_DOWN:
|
|
if (trills && (fastPatch[trills-1] > 0)){
|
|
patch = fastPatch[trills-1];
|
|
activePatch = 0;
|
|
doPatchUpdate = 1;
|
|
FPD = 1;
|
|
} else if (!trills) buttonPressedAndNotUsed = 1;
|
|
state = PATCH_VIEW;
|
|
stateFirstRun = 1;
|
|
break;
|
|
|
|
case BTN_ENTER:
|
|
if (trills && (fastPatch[trills-1] > 0)){
|
|
patch = fastPatch[trills-1];
|
|
activePatch = 0;
|
|
doPatchUpdate = 1;
|
|
FPD = 1;
|
|
}
|
|
state = PATCH_VIEW;
|
|
stateFirstRun = 1;
|
|
break;
|
|
|
|
case BTN_MENU:
|
|
if (pinkyKey && (exSensor >= ((extracThrVal+extracMaxVal)/2))) { // switch breath activated legacy settings on/off
|
|
legacyBrAct = !legacyBrAct;
|
|
dipSwBits = dipSwBits ^ (1<<2);
|
|
writeSetting(DIPSW_BITS_ADDR,dipSwBits);
|
|
statusBlink();
|
|
} else if ((exSensor >= ((extracThrVal+extracMaxVal)/2))) { // switch pb pad activated legacy settings control on/off
|
|
legacy = !legacy;
|
|
dipSwBits = dipSwBits ^ (1<<1);
|
|
writeSetting(DIPSW_BITS_ADDR,dipSwBits);
|
|
statusBlink();
|
|
} else if (pinkyKey && !specialKey){ //hold pinky key for rotator menu, and if too high touch sensing blocks regular menu, touching special key helps
|
|
display.ssd1306_command(SSD1306_DISPLAYON);
|
|
state = ROTATOR_MENU;
|
|
stateFirstRun = 1;
|
|
} else {
|
|
display.ssd1306_command(SSD1306_DISPLAYON);
|
|
state = MAIN_MENU;
|
|
stateFirstRun = 1;
|
|
}
|
|
break;
|
|
|
|
case 15:
|
|
//all keys depressed, reboot to programming mode
|
|
_reboot_Teensyduino_();
|
|
}
|
|
}
|
|
} else if (state == PATCH_VIEW) {
|
|
if (stateFirstRun) {
|
|
display.ssd1306_command(SSD1306_DISPLAYON);
|
|
drawPatchView();
|
|
patchViewTime = timeNow;
|
|
stateFirstRun = 0;
|
|
}
|
|
if ((timeNow - patchViewTime) > patchViewTimeUp) {
|
|
state = DISPLAYOFF_IDL;
|
|
stateFirstRun = 1;
|
|
doPatchUpdate = 1;
|
|
FPD = 0;
|
|
writeSetting(PATCH_ADDR,patch);
|
|
}
|
|
if (buttonPressedAndNotUsed){
|
|
buttonPressedAndNotUsed = 0;
|
|
patchViewTime = timeNow;
|
|
int trills = readTrills();
|
|
switch (deumButtonState){
|
|
case BTN_DOWN:
|
|
// down
|
|
if (trills && (fastPatch[trills-1] > 0)){
|
|
patch = fastPatch[trills-1];
|
|
activePatch = 0;
|
|
doPatchUpdate = 1;
|
|
FPD = 1;
|
|
writeSetting(PATCH_ADDR,patch);
|
|
} else if (!trills){
|
|
if (patch > 1){
|
|
patch--;
|
|
} else patch = 128;
|
|
activePatch = 0;
|
|
doPatchUpdate = 1;
|
|
FPD = 0;
|
|
}
|
|
drawPatchView();
|
|
redraw = true;
|
|
break;
|
|
case BTN_ENTER:
|
|
// enter
|
|
if (trills && (fastPatch[trills-1] > 0)){
|
|
patch = fastPatch[trills-1];
|
|
activePatch = 0;
|
|
doPatchUpdate = 1;
|
|
FPD = 1;
|
|
drawPatchView();
|
|
redraw = true;
|
|
}
|
|
break;
|
|
case BTN_UP:
|
|
// up
|
|
if (trills && (fastPatch[trills-1] > 0)){
|
|
patch = fastPatch[trills-1];
|
|
activePatch = 0;
|
|
doPatchUpdate = 1;
|
|
FPD = 1;
|
|
writeSetting(PATCH_ADDR,patch);
|
|
} else if (!trills){
|
|
if (patch < 128){
|
|
patch++;
|
|
} else patch = 1;
|
|
activePatch = 0;
|
|
doPatchUpdate = 1;
|
|
FPD = 0;
|
|
}
|
|
drawPatchView();
|
|
redraw = true;
|
|
break;
|
|
|
|
case BTN_MENU:
|
|
if (FPD < 2){
|
|
state = DISPLAYOFF_IDL;
|
|
stateFirstRun = 1;
|
|
doPatchUpdate = 1;
|
|
}
|
|
writeSetting(PATCH_ADDR,patch);
|
|
FPD = 0;
|
|
break;
|
|
|
|
case BTN_MENU+BTN_ENTER:
|
|
midiPanic();
|
|
display.clearDisplay();
|
|
display.setTextColor(WHITE);
|
|
display.setTextSize(2);
|
|
display.setCursor(35,15);
|
|
display.println("DON'T");
|
|
display.setCursor(35,30);
|
|
display.println("PANIC");
|
|
redraw = true;
|
|
break;
|
|
|
|
case BTN_MENU+BTN_ENTER+BTN_UP+BTN_DOWN:
|
|
//all keys depressed, reboot to programming mode
|
|
_reboot_Teensyduino_();
|
|
}
|
|
}
|
|
} else if (state == MAIN_MENU) { // MAIN MENU HERE <<<<<<<<<<<<<<<
|
|
currentPage = &mainMenuPage;
|
|
if (stateFirstRun) {
|
|
drawMenuScreen();
|
|
stateFirstRun = 0;
|
|
}
|
|
|
|
if(activeSub[currentPage->cursor]) {
|
|
redraw |= updateSubMenu(*currentPage, timeNow);
|
|
} else {
|
|
bool hadButtons = buttonPressedAndNotUsed;
|
|
redraw |= updateMenuPage( *currentPage, timeNow );
|
|
if (hadButtons)
|
|
checkForPatchView(deumButtonState);
|
|
}
|
|
} else if (state == ROTATOR_MENU) { // ROTATOR MENU HERE <<<<<<<<<<<<<<<
|
|
if (stateFirstRun) {
|
|
drawMenu(rotatorMenuPage);
|
|
stateFirstRun = 0;
|
|
}
|
|
currentPage = &rotatorMenuPage;
|
|
if (subParallel){
|
|
if (((timeNow - cursorBlinkTime) > cursorBlinkInterval) || forceRedraw) {
|
|
if (cursorNow == WHITE) cursorNow = BLACK; else cursorNow = WHITE;
|
|
if (forceRedraw){
|
|
forceRedraw = 0;
|
|
cursorNow = WHITE;
|
|
}
|
|
plotRotator(cursorNow,parallel);
|
|
redraw = true;
|
|
cursorBlinkTime = timeNow;
|
|
}
|
|
if (buttonPressedAndNotUsed){
|
|
buttonPressedAndNotUsed = 0;
|
|
switch (deumButtonState){
|
|
case BTN_DOWN:
|
|
if (parallel > -24)
|
|
parallel--;
|
|
break;
|
|
|
|
case BTN_UP:
|
|
if (parallel < 24)
|
|
parallel++;
|
|
break;
|
|
|
|
case BTN_ENTER: // fallthrough
|
|
case BTN_MENU:
|
|
subParallel = 0;
|
|
writeSetting(PARAL_ADDR,(parallel + 24));
|
|
break;
|
|
}
|
|
clearSubValue();
|
|
plotRotator(WHITE,parallel);
|
|
cursorNow = BLACK;
|
|
redraw = true;
|
|
cursorBlinkTime = timeNow;
|
|
}
|
|
} else if (subRotator){
|
|
if (((timeNow - cursorBlinkTime) > cursorBlinkInterval) || forceRedraw) {
|
|
if (cursorNow == WHITE) cursorNow = BLACK; else cursorNow = WHITE;
|
|
if (forceRedraw){
|
|
forceRedraw = 0;
|
|
cursorNow = WHITE;
|
|
}
|
|
plotRotator(cursorNow,rotations[subRotator-1]);
|
|
redraw = true;
|
|
cursorBlinkTime = timeNow;
|
|
}
|
|
if (buttonPressedAndNotUsed){
|
|
buttonPressedAndNotUsed = 0;
|
|
switch (deumButtonState){
|
|
case BTN_DOWN:
|
|
if (rotations[subRotator-1] > -24)
|
|
rotations[subRotator-1]--;
|
|
break;
|
|
|
|
case BTN_UP:
|
|
if (rotations[subRotator-1] < 24)
|
|
rotations[subRotator-1]++;
|
|
break;
|
|
|
|
case BTN_ENTER: // fallthrough
|
|
case BTN_MENU:
|
|
writeSetting(ROTN1_ADDR+2*(subRotator-1),(rotations[subRotator-1]+24));
|
|
subRotator = 0;
|
|
break;
|
|
}
|
|
clearSubValue();
|
|
plotRotator(WHITE,rotations[subRotator-1]);
|
|
cursorNow = BLACK;
|
|
redraw = true;
|
|
cursorBlinkTime = timeNow;
|
|
}
|
|
} else if (subPriority){
|
|
updateSubMenuCursor( *currentPage, timeNow );
|
|
if (buttonPressedAndNotUsed) {
|
|
buttonPressedAndNotUsed = 0;
|
|
switch (deumButtonState){
|
|
case BTN_DOWN: // fallthrough
|
|
case BTN_UP:
|
|
priority = !priority;
|
|
break;
|
|
|
|
case BTN_ENTER: // fallthrough
|
|
case BTN_MENU:
|
|
subPriority = 0;
|
|
writeSetting(PRIO_ADDR,priority);
|
|
break;
|
|
}
|
|
redrawSubValue = true;
|
|
}
|
|
} else {
|
|
bool hadButtons = buttonPressedAndNotUsed;
|
|
redraw |= updateMenuPage(*currentPage, timeNow );
|
|
if (hadButtons)
|
|
checkForPatchView(deumButtonState);
|
|
}
|
|
// end rotator menu
|
|
|
|
} else if (state == ADJUST_MENU) {
|
|
// This is a hack to update touch_Thr is it was changed..
|
|
int old_thr = ctouchThrVal;
|
|
int result = updateAdjustMenu(timeNow, buttonPressedAndNotUsed ? deumButtonState : 0, stateFirstRun, updateSensorPixelsFlag);
|
|
|
|
updateSensorPixelsFlag = false;
|
|
stateFirstRun = 0;
|
|
buttonPressedAndNotUsed = 0;
|
|
|
|
if( result < 0) {
|
|
// Go back to main menu
|
|
state = MAIN_MENU;
|
|
stateFirstRun = true;
|
|
}
|
|
|
|
if( old_thr != ctouchThrVal) {
|
|
touch_Thr = map(ctouchThrVal,ctouchHiLimit,ctouchLoLimit,ttouchLoLimit,ttouchHiLimit);
|
|
}
|
|
} else if (state == SETUP_BR_MENU) { // SETUP BREATH MENU HERE <<<<<<<<<<<<<<
|
|
redraw |= updatePage(breathMenuPage, timeNow);
|
|
} else if (state == SETUP_CT_MENU) { // SETUP CONTROLLERS MENU HERE <<<<<<<<<<<<<
|
|
redraw |= updatePage(controlMenuPage, timeNow);
|
|
} else if (state == VIBRATO_MENU) { // VIBRATO MENU HERE <<<<<<<<<<<<<
|
|
redraw |= updatePage(vibratoMenuPage, timeNow);
|
|
}
|
|
|
|
if(redrawSubValue && currentPage) {
|
|
clearSubValue();
|
|
redraw |= drawSubMenu(*currentPage, WHITE);
|
|
cursorNow = BLACK;
|
|
cursorBlinkTime = timeNow;
|
|
}
|
|
|
|
if(redraw) {
|
|
display.display();
|
|
}
|
|
}
|