
BITE CTL and LEVER CTL can both be set to any of these destinations: OFF - no destination active for this controller VIB - vibrato GLD - glide/portamento (defined by GLIDE MOD and GLIDE LMT settings) CC - custom CC output (defined in GLIDE CC and LEVER CC settings) Settings in GLIDE CC and LEVER CC are only used when corresponding control is set to CC in its CTL setting as described above.
2259 lines
74 KiB
C++
2259 lines
74 KiB
C++
#include <Adafruit_GFX.h>
|
|
#include <Adafruit_SSD1306.h>
|
|
#include <Adafruit_MPR121.h>
|
|
#include <Arduino.h>
|
|
|
|
#include "menu.h"
|
|
#include "settings.h"
|
|
#include "hardware.h"
|
|
#include "config.h"
|
|
#include "globals.h"
|
|
#include "midi.h"
|
|
#include "numenu.h"
|
|
#include "led.h"
|
|
|
|
enum CursorIdx {
|
|
EMain,
|
|
EBreath,
|
|
EControl,
|
|
ERotator,
|
|
EVibrato,
|
|
EExtras,
|
|
// NEVER ADD ANYTHING AFTER THIS, ONLY ABOVE
|
|
NUM_CURSORS
|
|
};
|
|
|
|
// Allocate some space for cursors
|
|
static byte cursors[CursorIdx::NUM_CURSORS];
|
|
static byte offsets[CursorIdx::NUM_CURSORS];
|
|
static byte activeSub[CursorIdx::NUM_CURSORS];
|
|
|
|
byte cursorNow;
|
|
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 menuTime = 0;
|
|
static unsigned long patchViewTime = 0;
|
|
unsigned long cursorBlinkTime = 0; // the last time the cursor was toggled
|
|
|
|
//Display state
|
|
static byte menuState= DISPLAYOFF_IDL;
|
|
static byte stateFirstRun = 1;
|
|
|
|
// 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' or 'NuRAD' logo
|
|
#define LOGO16_GLCD_WIDTH 128
|
|
#define LOGO16_GLCD_HEIGHT 64
|
|
#if defined(NURAD)
|
|
static const unsigned char PROGMEM nurad_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, 0x7f, 0xff, 0xff, 0x00, 0x00, 0x03, 0xf9, 0xff, 0xff, 0xfc, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0x80, 0x00, 0x07, 0xf9, 0xff, 0xff, 0xfe, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x01, 0xc0, 0x00, 0x0e, 0x19, 0x80, 0x00, 0x07, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x6f, 0xff, 0xfc, 0xc0, 0x00, 0x1d, 0xd9, 0xbf, 0xff, 0xf3, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x6f, 0xff, 0xfe, 0xc0, 0x00, 0x3b, 0xd9, 0xbf, 0xff, 0xfb, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x06, 0xc0, 0x00, 0x77, 0xd9, 0xb0, 0x00, 0x1b, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x06, 0xc0, 0x00, 0xee, 0xd9, 0xb0, 0x00, 0x1b, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x06, 0xc0, 0x01, 0xdc, 0xd9, 0xb0, 0x00, 0x1b, 0x00,
|
|
0x00, 0x00, 0x08, 0x00, 0x00, 0x6c, 0x00, 0x06, 0xc0, 0x03, 0xb8, 0xd9, 0xb0, 0x00, 0x1b, 0x00,
|
|
0x00, 0x00, 0x18, 0x00, 0x00, 0x6f, 0xff, 0xfe, 0xc0, 0x07, 0x7f, 0xd9, 0xb0, 0x00, 0x1b, 0x00,
|
|
0x00, 0x00, 0x18, 0x00, 0x00, 0x6f, 0xff, 0xfc, 0xc0, 0x0e, 0xff, 0xd9, 0xb0, 0x00, 0x1b, 0x00,
|
|
0x00, 0xc0, 0x18, 0x00, 0x00, 0x60, 0x00, 0x01, 0xc0, 0x1c, 0x00, 0x19, 0xb0, 0x00, 0x1b, 0x00,
|
|
0x00, 0xc0, 0x38, 0x00, 0x00, 0x6f, 0xfb, 0xff, 0x80, 0x3b, 0xff, 0xd9, 0xb0, 0x00, 0x1b, 0x00,
|
|
0x00, 0xc0, 0x30, 0x00, 0x00, 0x6f, 0xfd, 0xff, 0x00, 0x77, 0xff, 0xd9, 0xb0, 0x00, 0x1b, 0x00,
|
|
0x01, 0xe0, 0x30, 0x00, 0x00, 0x6c, 0x0e, 0xe0, 0x00, 0xee, 0x00, 0xd9, 0xb0, 0x00, 0x1b, 0x00,
|
|
0x01, 0xe0, 0x30, 0x00, 0x00, 0x6c, 0x07, 0x70, 0x01, 0xdc, 0x00, 0xd9, 0xb0, 0x00, 0x1b, 0x00,
|
|
0x01, 0xb0, 0x30, 0x00, 0x00, 0x6c, 0x03, 0xb8, 0x03, 0xb8, 0x00, 0xd9, 0xb0, 0x00, 0x1b, 0x00,
|
|
0x01, 0xb0, 0x30, 0x00, 0x00, 0x6c, 0x01, 0xdc, 0x07, 0x70, 0x00, 0xd9, 0xb0, 0x00, 0x1b, 0x00,
|
|
0x01, 0x98, 0x20, 0xc0, 0x40, 0x6c, 0x00, 0xee, 0x0e, 0xe0, 0x00, 0xd9, 0xb0, 0x00, 0x1b, 0x00,
|
|
0x03, 0x0c, 0x20, 0xc0, 0xc0, 0x6c, 0x00, 0x77, 0x1d, 0xc0, 0x00, 0xd9, 0xbf, 0xff, 0xfb, 0x00,
|
|
0x03, 0x0c, 0x20, 0xc0, 0xc0, 0x6c, 0x00, 0x3b, 0xbb, 0x80, 0x00, 0xd9, 0xbf, 0xff, 0xf3, 0x00,
|
|
0x03, 0x06, 0x20, 0xc1, 0xc0, 0x6c, 0x00, 0x1d, 0xf7, 0x00, 0x00, 0xd9, 0x80, 0x00, 0x07, 0x00,
|
|
0x03, 0x03, 0x20, 0x83, 0x80, 0x6c, 0x00, 0x0e, 0xee, 0x00, 0x00, 0xd9, 0xff, 0xff, 0xfe, 0x00,
|
|
0x03, 0x03, 0xb1, 0x83, 0x80, 0x6c, 0x00, 0x07, 0x5c, 0x00, 0x00, 0xd9, 0xff, 0xff, 0xfc, 0x00,
|
|
0x03, 0x01, 0xf1, 0x87, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x03, 0x00, 0xf1, 0x8d, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x03, 0x00, 0x71, 0xb9, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x03, 0x00, 0x31, 0xf1, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x03, 0x00, 0x00, 0xc1, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
};
|
|
#else
|
|
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
|
|
};
|
|
#endif
|
|
|
|
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);
|
|
|
|
int drawBatt(int x,int y){
|
|
display.drawRect(x+1,y,3,3,WHITE);
|
|
display.drawRect(x,y+2,5,12,WHITE);
|
|
int bar=0;
|
|
int vMeterReading = battAvg;
|
|
switch (batteryType){
|
|
case 0:
|
|
bar = constrain((10 *(vMeterReading - ALK_BAT_LOW)) / (ALK_BAT_FULL - ALK_BAT_LOW),0,10);
|
|
break;
|
|
case 1:
|
|
bar = constrain((10 * (vMeterReading - NMH_BAT_LOW)) / (NMH_BAT_FULL - NMH_BAT_LOW),0,10);
|
|
break;
|
|
case 2:
|
|
bar = constrain((10 *(vMeterReading - LIP_BAT_LOW)) / (LIP_BAT_FULL - LIP_BAT_LOW),0,10);
|
|
}
|
|
display.fillRect(x+1,y+13-bar,3,constrain(bar,0,10),WHITE);
|
|
return bar;
|
|
}
|
|
|
|
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, 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();
|
|
#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();
|
|
|
|
memset(cursors, 0, sizeof(cursors));
|
|
memset(offsets, 0, sizeof(offsets));
|
|
memset(activeSub, 0, sizeof(activeSub));
|
|
}
|
|
|
|
void showVersion() {
|
|
display.setTextColor(WHITE);
|
|
display.setTextSize(1);
|
|
display.setCursor(85,52);
|
|
display.print("v.");
|
|
display.println(FIRMWARE_VERSION);
|
|
display.display();
|
|
}
|
|
|
|
void i2cScanDisplay(){
|
|
uint8_t target; // slave addr
|
|
byte error;
|
|
while(1){
|
|
display.clearDisplay();
|
|
display.setTextColor(WHITE);
|
|
display.setTextSize(1);
|
|
display.setCursor(0,0);
|
|
display.println("MPR121 board scan");
|
|
display.display();
|
|
for(target = 0x5A; target <= 0x5D; target++) // sweep addr
|
|
{
|
|
Wire.beginTransmission(target); // slave addr
|
|
error = Wire.endTransmission();
|
|
delay(500);
|
|
display.print("Addr 0x");
|
|
display.print(target,HEX);
|
|
if (error)
|
|
display.print(" N\n");
|
|
else
|
|
display.print(" Y\n");
|
|
display.display();
|
|
}
|
|
delay(1000);
|
|
display.println();
|
|
display.println("MENU to rescan");
|
|
display.println("Power off to exit");
|
|
display.display();
|
|
while (digitalRead(mPin)){
|
|
delay(100);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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, 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);
|
|
}
|
|
|
|
static bool drawSubMenu(const MenuPage *page) {
|
|
int index = cursors[page->cursor];
|
|
// TODO: Null check subMenuFunc
|
|
const MenuEntry* subEntry = page->entries[index];
|
|
switch(subEntry->type) {
|
|
case MenuType::ESub:
|
|
{
|
|
char buffer[12];
|
|
const char* labelPtr = nullptr;
|
|
const MenuEntrySub* sub = (const MenuEntrySub*)subEntry;
|
|
sub->getSubTextFunc(*sub, buffer, &labelPtr);
|
|
|
|
// If EMenuEntryCustom flag is set, we assume that the getSubTextFunc
|
|
// rendered by it self.
|
|
if( !(sub->flags & EMenuEntryCustom)) {
|
|
plotSubOption(buffer, 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();
|
|
return true;
|
|
} else {
|
|
cursorNow = WHITE;
|
|
return drawSubMenu(page);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void plotMenuEntries(const MenuPage *page, bool clear = false) {
|
|
int row = 0;
|
|
if(clear) {
|
|
display.fillRect( 0, MENU_HEADER_OFFSET, 63, 64-MENU_HEADER_OFFSET, BLACK);
|
|
}
|
|
display.setTextSize(1);
|
|
int offset = offsets[page->cursor];
|
|
for(int item = offset; (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);
|
|
}
|
|
|
|
if(offset)
|
|
display.drawTriangle(58, MENU_HEADER_OFFSET, 57, MENU_HEADER_OFFSET+3, 59, MENU_HEADER_OFFSET+3, WHITE);
|
|
|
|
if((offset+MENU_NUM_ROWS) < page->numEntries)
|
|
display.drawTriangle(58, 63, 57, 60, 59, 60, WHITE);
|
|
}
|
|
|
|
typedef void (*MenuTitleGetFunc)(char*out);
|
|
|
|
static void drawMenu(const MenuPage *page) {
|
|
//Initialize display and draw menu header + line
|
|
display.clearDisplay();
|
|
display.setTextSize(1);
|
|
display.setCursor(0,0);
|
|
|
|
if(page->flags & EMenuCustomTitle) {
|
|
// This is a bit hacky, but we are reusing the title pointer as a function pointer
|
|
MenuTitleGetFunc func = (MenuTitleGetFunc)page->title;
|
|
char buffer[23];
|
|
func(buffer);
|
|
display.println(buffer);
|
|
} else
|
|
display.println(page->title);
|
|
display.drawLine(0, MENU_ROW_HEIGHT, 127, MENU_ROW_HEIGHT, WHITE);
|
|
|
|
plotMenuEntries(page);
|
|
}
|
|
|
|
static void mainTitleGetStr(char* out) {
|
|
//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 = battAvg;
|
|
memcpy(splice1, (vMeterReading > 3020) ? "USB" : "BAT", 3);
|
|
int vLowLimit;
|
|
switch (batteryType){
|
|
case 0:
|
|
vLowLimit = ALK_BAT_LOW;
|
|
break;
|
|
case 1:
|
|
vLowLimit = NMH_BAT_LOW;
|
|
break;
|
|
case 2:
|
|
vLowLimit = LIP_BAT_LOW;
|
|
}
|
|
if (vMeterReading <= vLowLimit) { //2300 alkaline, 2250 lipo, 2200 nimh
|
|
memcpy(splice2, "LOW ", 4);
|
|
} else {
|
|
int voltage = map(vMeterReading,2200,3060,36,50);
|
|
splice2[0] = (voltage/10)+'0';
|
|
splice2[2] = (voltage%10)+'0';
|
|
}
|
|
strncpy(out, menuTitle, 22);
|
|
}
|
|
|
|
#if defined(NURAD)
|
|
static void drawTrills(){
|
|
if (LH1) display.fillRect(0,0,5,5,WHITE); else display.drawRect(0,0,5,5,WHITE);
|
|
if (LH2) display.fillRect(10,0,5,5,WHITE); else display.drawRect(10,0,5,5,WHITE);
|
|
if (LH3) display.fillRect(20,0,5,5,WHITE); else display.drawRect(20,0,5,5,WHITE);
|
|
}
|
|
#else
|
|
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);
|
|
}
|
|
#endif
|
|
|
|
static void drawPatchView(){
|
|
display.clearDisplay();
|
|
if (FPD){
|
|
drawTrills();
|
|
}
|
|
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.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);
|
|
}
|
|
|
|
//***********************************************************
|
|
|
|
#if defined(NURAD)
|
|
static int readTrills() {
|
|
readSwitches();
|
|
return LH1+2*LH2+4*LH3;
|
|
}
|
|
#else
|
|
static int readTrills() {
|
|
readSwitches();
|
|
return K5+2*K6+4*K7;
|
|
}
|
|
#endif
|
|
//***********************************************************
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
//***********************************************************
|
|
// Poly Play menu
|
|
|
|
static void rotatorSave(const MenuEntrySub& __unused sub) {
|
|
int16_t stored;
|
|
for(int i = 0; i < 4; ++i) {
|
|
stored = readSetting(ROTN1_ADDR+2*i);
|
|
if(stored != rotations[i])
|
|
writeSetting(ROTN1_ADDR+2*i,(rotations[i]));
|
|
}
|
|
}
|
|
|
|
static void rotatorBSave(const MenuEntrySub& __unused sub) {
|
|
int16_t stored;
|
|
for(int i = 0; i < 4; ++i) {
|
|
stored = readSetting(ROTB1_ADDR+2*i);
|
|
if(stored != rotationsb[i])
|
|
writeSetting(ROTB1_ADDR+2*i,(rotationsb[i]));
|
|
}
|
|
}
|
|
|
|
static void rotatorCSave(const MenuEntrySub& __unused sub) {
|
|
int16_t stored;
|
|
for(int i = 0; i < 4; ++i) {
|
|
stored = readSetting(ROTC1_ADDR+2*i);
|
|
if(stored != rotationsc[i])
|
|
writeSetting(ROTC1_ADDR+2*i,(rotationsc[i]));
|
|
}
|
|
}
|
|
|
|
|
|
static void rotatorOptionGet(SubMenuRef sub, char *out, const char** __unused unit) {
|
|
numToString((*sub.valuePtr) - 24, out, true);
|
|
}
|
|
|
|
static void parallelOptionGet(SubMenuRef __unused, char *out, const char** __unused unit) {
|
|
numToString(parallel-24, out, true);
|
|
}
|
|
|
|
static void parallelBOptionGet(SubMenuRef __unused, char *out, const char** __unused unit) {
|
|
numToString(parallelb-24, out, true);
|
|
}
|
|
|
|
static void parallelCOptionGet(SubMenuRef __unused, char *out, const char** __unused unit) {
|
|
numToString(parallelc-24, out, true);
|
|
}
|
|
|
|
static void parallelSave(SubMenuRef __unused) {
|
|
writeSetting(PARAL_ADDR, parallel);
|
|
}
|
|
|
|
static void parallelBSave(SubMenuRef __unused) {
|
|
writeSetting(PARAB_ADDR, parallelb);
|
|
}
|
|
|
|
static void parallelCSave(SubMenuRef __unused) {
|
|
writeSetting(PARAC_ADDR, parallelc);
|
|
}
|
|
|
|
const MenuEntrySub rotatorParaMenu = {
|
|
MenuType::ESub, "RTA PARAL", "SEMITONES", ¶llel, 0, 48, MenuEntryFlags::ENone,
|
|
parallelOptionGet, parallelSave, nullptr
|
|
};
|
|
|
|
const MenuEntrySub rotator1Menu = {
|
|
MenuType::ESub, "RTA ROT 1", "SEMITONES", &rotations[0], 0, 48, MenuEntryFlags::ENone,
|
|
rotatorOptionGet, rotatorSave, nullptr
|
|
};
|
|
|
|
const MenuEntrySub rotator2Menu = {
|
|
MenuType::ESub, "RTA ROT 2", "SEMITONES", &rotations[1], 0, 48, MenuEntryFlags::ENone,
|
|
rotatorOptionGet, rotatorSave, nullptr
|
|
};
|
|
|
|
const MenuEntrySub rotator3Menu = {
|
|
MenuType::ESub, "RTA ROT 3", "SEMITONES", &rotations[2], 0, 48, MenuEntryFlags::ENone,
|
|
rotatorOptionGet, rotatorSave, nullptr
|
|
};
|
|
|
|
const MenuEntrySub rotator4Menu = {
|
|
MenuType::ESub, "RTA ROT 4", "SEMITONES", &rotations[3], 0, 48, MenuEntryFlags::ENone,
|
|
rotatorOptionGet, rotatorSave, nullptr
|
|
};
|
|
|
|
const MenuEntrySub rotatorParaBMenu = {
|
|
MenuType::ESub, "RTB PARAL", "SEMITONES", ¶llelb, 0, 48, MenuEntryFlags::ENone,
|
|
parallelBOptionGet, parallelBSave, nullptr
|
|
};
|
|
|
|
const MenuEntrySub rotatorB1Menu = {
|
|
MenuType::ESub, "RTB ROT 1", "SEMITONES", &rotationsb[0], 0, 48, MenuEntryFlags::ENone,
|
|
rotatorOptionGet, rotatorBSave, nullptr
|
|
};
|
|
|
|
const MenuEntrySub rotatorB2Menu = {
|
|
MenuType::ESub, "RTB ROT 2", "SEMITONES", &rotationsb[1], 0, 48, MenuEntryFlags::ENone,
|
|
rotatorOptionGet, rotatorBSave, nullptr
|
|
};
|
|
|
|
const MenuEntrySub rotatorB3Menu = {
|
|
MenuType::ESub, "RTB ROT 3", "SEMITONES", &rotationsb[2], 0, 48, MenuEntryFlags::ENone,
|
|
rotatorOptionGet, rotatorBSave, nullptr
|
|
};
|
|
|
|
const MenuEntrySub rotatorB4Menu = {
|
|
MenuType::ESub, "RTB ROT 4", "SEMITONES", &rotationsb[3], 0, 48, MenuEntryFlags::ENone,
|
|
rotatorOptionGet, rotatorBSave, nullptr
|
|
};
|
|
const MenuEntrySub rotatorParaCMenu = {
|
|
MenuType::ESub, "RTC PARAL", "SEMITONES", ¶llelc, 0, 48, MenuEntryFlags::ENone,
|
|
parallelCOptionGet, parallelCSave, nullptr
|
|
};
|
|
|
|
const MenuEntrySub rotatorC1Menu = {
|
|
MenuType::ESub, "RTC ROT 1", "SEMITONES", &rotationsc[0], 0, 48, MenuEntryFlags::ENone,
|
|
rotatorOptionGet, rotatorCSave, nullptr
|
|
};
|
|
|
|
const MenuEntrySub rotatorC2Menu = {
|
|
MenuType::ESub, "RTC ROT 2", "SEMITONES", &rotationsc[1], 0, 48, MenuEntryFlags::ENone,
|
|
rotatorOptionGet, rotatorCSave, nullptr
|
|
};
|
|
|
|
const MenuEntrySub rotatorC3Menu = {
|
|
MenuType::ESub, "RTC ROT 3", "SEMITONES", &rotationsc[2], 0, 48, MenuEntryFlags::ENone,
|
|
rotatorOptionGet, rotatorCSave, nullptr
|
|
};
|
|
|
|
const MenuEntrySub rotatorC4Menu = {
|
|
MenuType::ESub, "RTC ROT 4", "SEMITONES", &rotationsc[3], 0, 48, MenuEntryFlags::ENone,
|
|
rotatorOptionGet, rotatorCSave, nullptr
|
|
};
|
|
|
|
|
|
static void rotatorPrioOptionGet(SubMenuRef __unused, char* out, const char** __unused) {
|
|
if (priority) strncpy(out, "LO", 4);
|
|
else strncpy(out, "HI", 4);
|
|
}
|
|
|
|
static void rotatorPrioSave(SubMenuRef __unused) {
|
|
writeSetting(PRIO_ADDR,priority);
|
|
}
|
|
|
|
const MenuEntrySub rotatorPrioMenu = {
|
|
MenuType::ESub, "PRIORITY", "MONO PRIO", &priority, 0,1, MenuEntryFlags::EMenuEntryWrap,
|
|
rotatorPrioOptionGet, rotatorPrioSave, nullptr,
|
|
};
|
|
|
|
|
|
static void fwcTypeOptionGet(SubMenuRef __unused, char* out, const char** __unused) {
|
|
switch (fwcType){
|
|
case 0:
|
|
strncpy(out, "6", 4);
|
|
break;
|
|
case 1:
|
|
strncpy(out, "m6", 4);
|
|
break;
|
|
case 2:
|
|
strncpy(out, "7", 4);
|
|
break;
|
|
case 3:
|
|
strncpy(out, "m7", 4);
|
|
}
|
|
}
|
|
|
|
static void fwcTypeSave(SubMenuRef __unused) {
|
|
writeSetting(FWCTYPE_ADDR,fwcType);
|
|
}
|
|
|
|
const MenuEntrySub fwcTypeMenu = {
|
|
MenuType::ESub, "FWC TYPE", "CHORD", &fwcType, 0,3, MenuEntryFlags::EMenuEntryWrap,
|
|
fwcTypeOptionGet, fwcTypeSave, nullptr,
|
|
};
|
|
|
|
static void fwcLockHOptionGet(SubMenuRef __unused, char* out, const char** __unused) {
|
|
if (fwcLockH) strncpy(out, "ON", 4);
|
|
else strncpy(out, "OFF", 4);
|
|
}
|
|
|
|
static void fwcLockHSave(SubMenuRef __unused) {
|
|
writeSetting(FWCLCH_ADDR,fwcLockH);
|
|
}
|
|
|
|
const MenuEntrySub fwcLockHMenu = {
|
|
MenuType::ESub, "FWC LOCKH", "LH MELODY", &fwcLockH, 0,1, MenuEntryFlags::EMenuEntryWrap,
|
|
fwcLockHOptionGet, fwcLockHSave, nullptr,
|
|
};
|
|
|
|
static void fwcDrop2OptionGet(SubMenuRef __unused, char* out, const char** __unused) {
|
|
if (fwcDrop2) strncpy(out, "ON", 4);
|
|
else strncpy(out, "OFF", 4);
|
|
}
|
|
|
|
static void fwcDrop2Save(SubMenuRef __unused) {
|
|
writeSetting(FWCDP2_ADDR,fwcDrop2);
|
|
}
|
|
|
|
const MenuEntrySub fwcDrop2Menu = {
|
|
MenuType::ESub, "FWC DROP2", "DROP 2", &fwcDrop2, 0,1, MenuEntryFlags::EMenuEntryWrap,
|
|
fwcDrop2OptionGet, fwcDrop2Save, nullptr,
|
|
};
|
|
|
|
static void hmzKeyOptionGet(SubMenuRef __unused, char* out, const char** __unused) {
|
|
switch (hmzKey){
|
|
case 0:
|
|
strncpy(out, "C", 4);
|
|
break;
|
|
case 1:
|
|
strncpy(out, "C#", 4);
|
|
break;
|
|
case 2:
|
|
strncpy(out, "D", 4);
|
|
break;
|
|
case 3:
|
|
strncpy(out, "D#", 4);
|
|
break;
|
|
case 4:
|
|
strncpy(out, "E", 4);
|
|
break;
|
|
case 5:
|
|
strncpy(out, "F", 4);
|
|
break;
|
|
case 6:
|
|
strncpy(out, "F#", 4);
|
|
break;
|
|
case 7:
|
|
strncpy(out, "G", 4);
|
|
break;
|
|
case 8:
|
|
strncpy(out, "G#", 4);
|
|
break;
|
|
case 9:
|
|
strncpy(out, "A", 4);
|
|
break;
|
|
case 10:
|
|
strncpy(out, "Bb", 4);
|
|
break;
|
|
case 11:
|
|
strncpy(out, "B", 4);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void hmzKeySave(SubMenuRef __unused) {
|
|
writeSetting(HMZKEY_ADDR,hmzKey);
|
|
}
|
|
|
|
const MenuEntrySub hmzKeyMenu = {
|
|
MenuType::ESub, "HMZ KEY", "KEY PLAYED", &hmzKey, 0,11, MenuEntryFlags::EMenuEntryWrap,
|
|
hmzKeyOptionGet, hmzKeySave, nullptr,
|
|
};
|
|
|
|
|
|
static void polySelectOptionGet(SubMenuRef __unused, char* out, const char** __unused) {
|
|
switch (polySelect){
|
|
case 0:
|
|
strncpy(out, "OFF", 4);
|
|
break;
|
|
case 1:
|
|
strncpy(out, "MGR", 4);
|
|
break;
|
|
case 2:
|
|
strncpy(out, "MGD", 4);
|
|
break;
|
|
case 3:
|
|
strncpy(out, "MA9", 4);
|
|
break;
|
|
case 4:
|
|
strncpy(out, "MND", 4);
|
|
break;
|
|
case 5:
|
|
strncpy(out, "MNA", 4);
|
|
break;
|
|
case 6:
|
|
strncpy(out, "MNH", 4);
|
|
break;
|
|
case 7:
|
|
strncpy(out, "FWC", 4);
|
|
break;
|
|
case 8:
|
|
strncpy(out, "RTA", 4);
|
|
break;
|
|
case 9:
|
|
strncpy(out, "RTB", 4);
|
|
break;
|
|
case 10:
|
|
strncpy(out, "RTC", 4);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void polySelectSave(SubMenuRef __unused) {
|
|
writeSetting(POLYSEL_ADDR,polySelect);
|
|
}
|
|
|
|
const MenuEntrySub polySelectMenu = {
|
|
MenuType::ESub, "POLY MODE", "SELECT", &polySelect, 0,10, MenuEntryFlags::EMenuEntryWrap,
|
|
polySelectOptionGet, polySelectSave, nullptr,
|
|
};
|
|
|
|
static void hmzLimitOptionGet(SubMenuRef sub, char *out, const char** __unused unit) {
|
|
numToString((*sub.valuePtr), out, false);
|
|
}
|
|
|
|
static void hmzLimitSave(SubMenuRef __unused) {
|
|
writeSetting(HMZLIMIT_ADDR,hmzLimit);
|
|
}
|
|
|
|
const MenuEntrySub hmzLimitMenu = {
|
|
MenuType::ESub, "HMZ LIMIT", "VOICES", &hmzLimit, 2,5, MenuEntryFlags::EMenuEntryWrap,
|
|
hmzLimitOptionGet, hmzLimitSave, nullptr,
|
|
};
|
|
|
|
static void otfKeyOptionGet(SubMenuRef __unused, char* out, const char** __unused) {
|
|
if (otfKey) strncpy(out, "ON", 4);
|
|
else strncpy(out, "OFF", 4);
|
|
}
|
|
|
|
static void otfKeySave(SubMenuRef __unused) {
|
|
writeSetting(OTFKEY_ADDR,otfKey);
|
|
}
|
|
|
|
const MenuEntrySub otfKeyMenu = {
|
|
MenuType::ESub, "OTF KEY", "OTF KEYSW", &otfKey, 0,1, MenuEntryFlags::EMenuEntryWrap,
|
|
otfKeyOptionGet, otfKeySave, nullptr,
|
|
};
|
|
|
|
|
|
|
|
const MenuEntry* rotatorMenuEntries[] = {
|
|
(MenuEntry*)&polySelectMenu,
|
|
(MenuEntry*)&hmzKeyMenu,
|
|
(MenuEntry*)&otfKeyMenu,
|
|
(MenuEntry*)&hmzLimitMenu,
|
|
(MenuEntry*)&fwcTypeMenu,
|
|
(MenuEntry*)&fwcLockHMenu,
|
|
(MenuEntry*)&fwcDrop2Menu,
|
|
(MenuEntry*)&rotatorPrioMenu,
|
|
(MenuEntry*)&rotatorParaMenu,
|
|
(MenuEntry*)&rotator1Menu,
|
|
(MenuEntry*)&rotator2Menu,
|
|
(MenuEntry*)&rotator3Menu,
|
|
(MenuEntry*)&rotator4Menu,
|
|
(MenuEntry*)&rotatorParaBMenu,
|
|
(MenuEntry*)&rotatorB1Menu,
|
|
(MenuEntry*)&rotatorB2Menu,
|
|
(MenuEntry*)&rotatorB3Menu,
|
|
(MenuEntry*)&rotatorB4Menu,
|
|
(MenuEntry*)&rotatorParaCMenu,
|
|
(MenuEntry*)&rotatorC1Menu,
|
|
(MenuEntry*)&rotatorC2Menu,
|
|
(MenuEntry*)&rotatorC3Menu,
|
|
(MenuEntry*)&rotatorC4Menu
|
|
};
|
|
/*
|
|
const MenuPage rotatorMenuPage = {
|
|
"ROTATOR SETUP",
|
|
EMenuPageRoot,
|
|
CursorIdx::ERotator,
|
|
DISPLAYOFF_IDL,
|
|
ARR_LEN(rotatorMenuEntries), rotatorMenuEntries
|
|
};
|
|
*/
|
|
const MenuPage rotatorMenuPage = {
|
|
"POLY PLAY SETUP",
|
|
0,
|
|
CursorIdx::ERotator,
|
|
MAIN_MENU,
|
|
ARR_LEN(rotatorMenuEntries), rotatorMenuEntries
|
|
};
|
|
|
|
//***********************************************************
|
|
|
|
// Main menu
|
|
const MenuEntrySub transposeMenu = {
|
|
MenuType::ESub, "TRANSPOSE", "TRANSPOSE", &transpose, 0, 24, MenuEntryFlags::ENone,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
numToString(transpose - 12, out, true);
|
|
},
|
|
[](const MenuEntrySub &sub) { writeSetting(TRANSP_ADDR,*sub.valuePtr); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub octaveMenu = {
|
|
MenuType::ESub, "OCTAVE", "OCTAVE", &octave, 0, 6, MenuEntryFlags::ENone,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
numToString(octave-3, out, true);
|
|
},
|
|
[](SubMenuRef __unused) { writeSetting(OCTAVE_ADDR,octave); }
|
|
, nullptr
|
|
};
|
|
|
|
static void midiSaveFunc(const MenuEntrySub & __unused sub) { writeSetting(MIDI_ADDR, MIDIchannel); }
|
|
|
|
static void midiCustomDrawFunc(SubMenuRef __unused, char* __unused, const char** __unused) {
|
|
char buff[7];
|
|
numToString(MIDIchannel, buff);
|
|
plotSubOption(buff);
|
|
if (slowMidi) {
|
|
//replaced with breathInterval setting and not used anymore.. do cleanup later removing all slowMidi related stuff
|
|
//display.setTextSize(1);
|
|
//display.setCursor(116,51);
|
|
//display.print("S");
|
|
}
|
|
}
|
|
|
|
//***********************************************************
|
|
|
|
const MenuEntrySub legacyPBMenu = {
|
|
MenuType::ESub, "LEGACY PB", "LEGACY PB", &legacy, 0, 1, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused, char* out, const char ** __unused unit) {
|
|
strncpy(out, legacy?"ON":"OFF", 4);
|
|
}, [](const MenuEntrySub & __unused sub) {
|
|
setBit(dipSwBits, DIPSW_LEGACY, legacy);
|
|
writeSetting(DIPSW_BITS_ADDR,dipSwBits);
|
|
}
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub legacyBRMenu = {
|
|
MenuType::ESub, "LEGACY BR", "LEGACY BR", &legacyBrAct, 0, 1, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused, char* out, const char ** __unused unit) {
|
|
strncpy(out, legacyBrAct?"ON":"OFF", 4);
|
|
}, [](const MenuEntrySub & __unused sub) {
|
|
setBit(dipSwBits, DIPSW_LEGACYBRACT, legacyBrAct);
|
|
writeSetting(DIPSW_BITS_ADDR, dipSwBits);
|
|
}
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub gateOpenMenu = {
|
|
MenuType::ESub, "GATE HOLD", "GATE HOLD", &gateOpenEnable, 0, 1, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused, char* out, const char ** __unused unit) {
|
|
strncpy(out, gateOpenEnable?"ON":"OFF", 4);
|
|
}, [](const MenuEntrySub & __unused sub) {
|
|
setBit(dipSwBits, DIPSW_GATEOPEN, gateOpenEnable);
|
|
writeSetting(DIPSW_BITS_ADDR, dipSwBits);
|
|
}
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub specialKeyMenu = {
|
|
MenuType::ESub, "SPEC KEY", "SPEC KEY", &specialKeyEnable, 0, 1, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused, char* out, const char ** __unused unit) {
|
|
strncpy(out, specialKeyEnable?"ON":"OFF", 4);
|
|
}, [](const MenuEntrySub & __unused sub) {
|
|
setBit(dipSwBits, DIPSW_SPKEYENABLE, specialKeyEnable);
|
|
writeSetting(DIPSW_BITS_ADDR, dipSwBits);
|
|
}
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub trill3Menu = {
|
|
MenuType::ESub, "3RD TRILL", "3RD TRILL", &trill3_interval, 3, 4, MenuEntryFlags::ENone,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
numToString(trill3_interval, out, true);
|
|
},
|
|
[](SubMenuRef __unused) { writeSetting(TRILL3_INTERVAL_ADDR, trill3_interval); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub bcasModeMenu = {
|
|
MenuType::ESub, "BCAS MODE", "BCAS MODE", &bcasMode, 0, 1, MenuEntryFlags::ENone,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
strncpy(out, bcasMode?"ON":"OFF", 4);
|
|
},
|
|
[](SubMenuRef __unused) {
|
|
setBit(dipSwBits, DIPSW_BCASMODE, bcasMode);
|
|
writeSetting(DIPSW_BITS_ADDR, dipSwBits);
|
|
}
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub dacModeMenu = {
|
|
MenuType::ESub, "DAC OUT", "DAC OUT", &dacMode, 0, 1, MenuEntryFlags::ENone,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
const char* dacModeLabels[] = { "BRTH", "PTCH"};
|
|
strncpy(out, dacModeLabels[dacMode], 5);
|
|
},
|
|
[](SubMenuRef __unused) { writeSetting(DAC_MODE_ADDR, dacMode); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub fastBootMenu = {
|
|
MenuType::ESub, "FAST BOOT", "FAST BOOT", &fastBoot, 0, 1, MenuEntryFlags::ENone,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
strncpy(out, fastBoot?"ON":"OFF", 4);
|
|
},
|
|
[](SubMenuRef __unused) {
|
|
setBit(dipSwBits, DIPSW_FASTBOOT, fastBoot);
|
|
writeSetting(DIPSW_BITS_ADDR, dipSwBits);
|
|
}
|
|
, nullptr
|
|
};
|
|
|
|
|
|
static uint16_t wireless_power=0;
|
|
static uint16_t wireless_channel=4;
|
|
|
|
const MenuEntrySub wlPowerMenu = {
|
|
MenuType::ESub, "WL POWER", "WL POWER", &wireless_power, 0, 3, MenuEntryFlags::ENone,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
numToString(-6*wireless_power, out, true);
|
|
},
|
|
[](SubMenuRef __unused) { sendWLPower(wireless_power); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub wlChannelMenu = {
|
|
MenuType::ESub, "WL CHAN", "WL CHAN", &wireless_channel, 4, 80, MenuEntryFlags::ENone,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
numToString(wireless_channel, out, false);
|
|
},
|
|
[](SubMenuRef __unused) { sendWLChannel(wireless_channel); }
|
|
, nullptr
|
|
};
|
|
|
|
// Battery type menu
|
|
const MenuEntrySub batteryTypeMenu = {
|
|
MenuType::ESub, "BAT TYPE", "BAT TYPE", &batteryType, 0, 2, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
const char* breathCCMenuLabels[] = { "ALK", "NMH", "LIP" };
|
|
strncpy(out, breathCCMenuLabels[batteryType], 4);
|
|
},
|
|
[](const MenuEntrySub & __unused sub){
|
|
if (readSetting(BATTYPE_ADDR) != batteryType) {
|
|
writeSetting(BATTYPE_ADDR,batteryType);
|
|
}
|
|
}
|
|
, nullptr
|
|
};
|
|
|
|
#if defined(NURAD)
|
|
const MenuEntry* extrasMenuEntries[] = {
|
|
(MenuEntry*)&legacyPBMenu,
|
|
(MenuEntry*)&legacyBRMenu,
|
|
(MenuEntry*)&gateOpenMenu,
|
|
(MenuEntry*)&dacModeMenu,
|
|
(MenuEntry*)&batteryTypeMenu,
|
|
(MenuEntry*)&fastBootMenu,
|
|
(MenuEntry*)&wlPowerMenu,
|
|
(MenuEntry*)&wlChannelMenu,
|
|
};
|
|
#else
|
|
const MenuEntry* extrasMenuEntries[] = {
|
|
(MenuEntry*)&legacyPBMenu,
|
|
(MenuEntry*)&legacyBRMenu,
|
|
(MenuEntry*)&gateOpenMenu,
|
|
(MenuEntry*)&trill3Menu,
|
|
(MenuEntry*)&bcasModeMenu,
|
|
(MenuEntry*)&dacModeMenu,
|
|
(MenuEntry*)&batteryTypeMenu,
|
|
(MenuEntry*)&fastBootMenu,
|
|
(MenuEntry*)&wlPowerMenu,
|
|
(MenuEntry*)&wlChannelMenu,
|
|
};
|
|
#endif
|
|
|
|
const MenuPage extrasMenuPage = {
|
|
"EXTRAS",
|
|
0,
|
|
CursorIdx::EExtras,
|
|
MAIN_MENU,
|
|
ARR_LEN(extrasMenuEntries), extrasMenuEntries
|
|
};
|
|
|
|
static bool midiEnterHandlerFunc() {
|
|
/*
|
|
//this switching is removed due to new breathInterval setting
|
|
readSwitches();
|
|
if (pinkyKey){
|
|
slowMidi = !slowMidi;
|
|
dipSwBits = dipSwBits ^ (1<<3);
|
|
writeSetting(DIPSW_BITS_ADDR,dipSwBits);
|
|
return false;
|
|
} else {
|
|
writeSetting(MIDI_ADDR, MIDIchannel);
|
|
return true;
|
|
}
|
|
*/
|
|
writeSetting(MIDI_ADDR, MIDIchannel);
|
|
return true;
|
|
}
|
|
|
|
const MenuEntrySub midiMenu = {
|
|
MenuType::ESub, "MIDI CH", "MIDI CHNL", &MIDIchannel, 1, 16, EMenuEntryCustom | EMenuEntryEnterHandler,
|
|
midiCustomDrawFunc, midiSaveFunc, midiEnterHandlerFunc
|
|
};
|
|
|
|
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 MenuEntryStateCh rotatorMenu = { MenuType::EStateChange, "POLY PLAY", ROTATOR_MENU };
|
|
const MenuEntryStateCh extrasMenu = { MenuType::EStateChange, "EXTRAS", EXTRAS_MENU };
|
|
const MenuEntryStateCh aboutMenu = { MenuType::EStateChange, "ABOUT", ABOUT_MENU };
|
|
|
|
const MenuEntry* mainMenuEntries[] = {
|
|
(MenuEntry*)&transposeMenu,
|
|
(MenuEntry*)&octaveMenu,
|
|
(MenuEntry*)&midiMenu,
|
|
(MenuEntry*)&rotatorMenu,
|
|
(MenuEntry*)&breathMenu,
|
|
(MenuEntry*)&controlMenu,
|
|
(MenuEntry*)&adjustMenu,
|
|
(MenuEntry*)&extrasMenu,
|
|
(MenuEntry*)&aboutMenu,
|
|
};
|
|
|
|
const MenuPage mainMenuPage = {
|
|
(const char*)mainTitleGetStr,
|
|
EMenuPageRoot | EMenuCustomTitle,
|
|
CursorIdx::EMain,
|
|
DISPLAYOFF_IDL,
|
|
ARR_LEN(mainMenuEntries), mainMenuEntries
|
|
};
|
|
|
|
|
|
// Breath menu
|
|
const MenuEntrySub breathCCMenu = {
|
|
MenuType::ESub, "BRTH CC A", "BRTH CC A", &breathCC, 0, 10, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
const char* breathCCMenuLabels[] = { "OFF", "MW", "BR", "VL", "EX", "MW+",
|
|
"BR+", "VL+", "EX+", "CF", "UNO" };
|
|
strncpy(out, breathCCMenuLabels[breathCC], 4);
|
|
},
|
|
[](const MenuEntrySub & __unused sub){
|
|
if (readSetting(BREATH_CC_ADDR) != breathCC) {
|
|
writeSetting(BREATH_CC_ADDR,breathCC);
|
|
midiReset();
|
|
}
|
|
}
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub breathCC2Menu = {
|
|
MenuType::ESub, "BRTH CC B", "BRTH CC B", &breathCC2, 0, 127, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
if(breathCC2) numToString(breathCC2, out);
|
|
else strncpy(out, "OFF", 4);
|
|
},
|
|
[](const MenuEntrySub & __unused sub){
|
|
if (readSetting(BREATH_CC2_ADDR) != breathCC2) {
|
|
writeSetting(BREATH_CC2_ADDR,breathCC2);
|
|
midiReset();
|
|
}
|
|
}
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub breathCC2RiseMenu = {
|
|
MenuType::ESub, "CC B RISE", "CC B RISE", &breathCC2Rise, 1, 10, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused, char *out, const char** label) {
|
|
numToString(breathCC2Rise, out);
|
|
*label = "X";
|
|
},
|
|
[](const MenuEntrySub & __unused sub) { writeSetting(BREATH_CC2_RISE_ADDR,breathCC2Rise); }
|
|
, nullptr
|
|
};
|
|
|
|
|
|
const MenuEntrySub breathATMenu = {
|
|
MenuType::ESub, "BREATH AT", "BREATH AT", &breathAT, 0, 1, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused, char* out, const char ** __unused unit) {
|
|
strncpy(out, breathAT?"ON":"OFF", 4);
|
|
}, [](const MenuEntrySub & __unused sub) {
|
|
if (readSetting(BREATH_AT_ADDR) != breathAT) {
|
|
writeSetting(BREATH_AT_ADDR, breathAT);
|
|
midiReset();
|
|
}
|
|
}
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub brHarmonicsMenu = {
|
|
MenuType::ESub, "BRTH HARM", "HARM RANGE", &brHarmSetting, 0, 6, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
if(brHarmSetting) numToString(brHarmSetting, out);
|
|
else strncpy(out, "OFF", 4);
|
|
},
|
|
[](const MenuEntrySub & __unused sub) { writeSetting(BRHARMSET_ADDR,brHarmSetting); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub brHarmSelectMenu = {
|
|
MenuType::ESub, "BR HM SEL", "SERIES", &brHarmSelect, 0, 3, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
const char* brHarmSelectMenuLabels[] = { "HM1", "HM2", "5TH", "OCT" };
|
|
strncpy(out, brHarmSelectMenuLabels[brHarmSelect], 4);
|
|
},
|
|
[](const MenuEntrySub & __unused sub){
|
|
if (readSetting(BRHARMSEL_ADDR) != brHarmSelect) {
|
|
writeSetting(BRHARMSEL_ADDR,brHarmSelect);
|
|
}
|
|
}
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub velocityMenu = {
|
|
MenuType::ESub, "VELOCITY", "VELOCITY", &velocity, 0, 127, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
if(velocity) numToString(velocity, out);
|
|
else strncpy(out, "DYN", 4);
|
|
},
|
|
[](const MenuEntrySub & __unused sub) { writeSetting(VELOCITY_ADDR,velocity); }
|
|
, nullptr
|
|
};
|
|
|
|
static void curveCustomDraw(SubMenuRef __unused, char* __unused out, const char** __unused unit) {
|
|
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[curve], 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[curve]);
|
|
}
|
|
|
|
static void curveWriteSettings(const MenuEntrySub & __unused sub){ writeSetting(BREATHCURVE_ADDR,curve); }
|
|
|
|
const MenuEntrySub curveMenu = {
|
|
MenuType::ESub, "CURVE", "CURVE", &curve, 0, 12, MenuEntryFlags::EMenuEntryWrap | MenuEntryFlags::EMenuEntryCustom,
|
|
curveCustomDraw, curveWriteSettings, nullptr
|
|
};
|
|
|
|
const MenuEntrySub velSmpDlMenu = {
|
|
MenuType::ESub, "VEL DELAY", "VEL DELAY", &velSmpDl, 0, 30, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused, char *out, const char** label) {
|
|
if (velSmpDl) {
|
|
numToString(velSmpDl, out);
|
|
*label = "ms";
|
|
} else strncpy(out, "OFF", 4);
|
|
},
|
|
[](const MenuEntrySub & __unused sub) { writeSetting(VEL_SMP_DL_ADDR,velSmpDl); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub velBiasMenu = {
|
|
MenuType::ESub, "VEL BOOST", "VEL BOOST", &velBias, 0, 9, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
if (velBias) numToString(velBias, out);
|
|
else strncpy(out, "OFF", 4);
|
|
},
|
|
[](SubMenuRef __unused){ writeSetting(VEL_BIAS_ADDR,velBias); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub breathIntervalMenu = {
|
|
MenuType::ESub, "BR INTERV", "CC INTERV", &breathInterval, 3, 15, MenuEntryFlags::ENone,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
numToString(breathInterval, out, false);
|
|
},
|
|
[](SubMenuRef __unused) { writeSetting(BRINTERV_ADDR, breathInterval); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntry* breathMenuEntries[] = {
|
|
(MenuEntry*)&breathCCMenu,
|
|
(MenuEntry*)&breathCC2Menu,
|
|
(MenuEntry*)&breathCC2RiseMenu,
|
|
(MenuEntry*)&breathATMenu,
|
|
(MenuEntry*)&velocityMenu,
|
|
(MenuEntry*)&curveMenu,
|
|
(MenuEntry*)&velSmpDlMenu,
|
|
(MenuEntry*)&velBiasMenu,
|
|
//(MenuEntry*)&brHarmonicsMenu,
|
|
//(MenuEntry*)&brHarmSelectMenu,
|
|
(MenuEntry*)&breathIntervalMenu
|
|
};
|
|
|
|
const MenuPage breathMenuPage = {
|
|
"SETUP BREATH",
|
|
0,
|
|
CursorIdx::EBreath,
|
|
MAIN_MENU,
|
|
ARR_LEN(breathMenuEntries), breathMenuEntries
|
|
};
|
|
|
|
//***********************************************************
|
|
// Control menu
|
|
|
|
const MenuEntrySub biteCtlMenu = {
|
|
MenuType::ESub, "BITE CTL", "BITE DEST", &biteControl, 0, 3, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused,char* out, const char ** __unused unit) {
|
|
const char* labs[] = { "OFF", "VIB", "GLD", "CC" };
|
|
strncpy(out, labs[biteControl], 4);
|
|
},
|
|
[](SubMenuRef __unused sub) { writeSetting(BITECTL_ADDR,biteControl); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub biteCCMenu = {
|
|
MenuType::ESub, "BITE CC", "CC NUMBER", &biteCC, 0, 127, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
numToString(biteCC, out);
|
|
},
|
|
[](const MenuEntrySub & __unused sub) { writeSetting(BITECC_ADDR,biteCC); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub leverCtlMenu = {
|
|
MenuType::ESub, "LEVER CTL", "LEVER DEST", &leverControl, 0, 3, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused,char* out, const char ** __unused unit) {
|
|
const char* labs[] = { "OFF", "VIB", "GLD", "CC" };
|
|
strncpy(out, labs[leverControl], 4);
|
|
},
|
|
[](SubMenuRef __unused sub) { writeSetting(LEVERCTL_ADDR,leverControl); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub leverCCMenu = {
|
|
MenuType::ESub, "LEVER CC", "CC NUMBER", &leverCC, 0, 127, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
numToString(leverCC, out);
|
|
},
|
|
[](const MenuEntrySub & __unused sub) { writeSetting(LEVERCC_ADDR,leverCC); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub portMenu = {
|
|
MenuType::ESub, "GLIDE MOD", "PORT/GLD", &portamento, 0, 5, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused,char* out, const char ** __unused unit) {
|
|
const char* labs[] = { "OFF", "ON", "SW", "SEL", "SEE", "SWO" };
|
|
strncpy(out, labs[portamento], 4);
|
|
},
|
|
[](SubMenuRef __unused sub) { writeSetting(PORTAM_ADDR,portamento); }
|
|
, nullptr
|
|
};
|
|
|
|
|
|
const MenuEntrySub portLimitMenu = {
|
|
MenuType::ESub, "GLIDE LMT", "MAX LEVEL", &portLimit, 1, 127, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
numToString(portLimit, out);
|
|
},
|
|
[](const SubMenuRef & __unused sub) { writeSetting(PORTLIMIT_ADDR,portLimit); }
|
|
, nullptr
|
|
};
|
|
|
|
|
|
|
|
const MenuEntrySub pitchBendMenu = {
|
|
MenuType::ESub, "PITCHBEND", "PITCHBEND", &PBdepth, 0, 12, MenuEntryFlags::ENone,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
if(PBdepth) {
|
|
memcpy(out, "1/", 2);
|
|
numToString(PBdepth, &out[2]);
|
|
}
|
|
else strncpy(out, "OFF", 4);
|
|
},
|
|
[](SubMenuRef __unused){ writeSetting(PB_ADDR,PBdepth); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub extraMenu = {
|
|
MenuType::ESub, "EXCT CC A", "EXCT CC A", &extraCT, 0,4, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused,char* out, const char** __unused unit) {
|
|
const char* extraMenuLabels[] = { "OFF", "MW", "FP", "CF", "SP" };
|
|
strncpy(out, extraMenuLabels[extraCT], 12);
|
|
},
|
|
[](const MenuEntrySub & __unused sub) { writeSetting(EXTRA_ADDR,extraCT); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub extraCC2Menu = {
|
|
MenuType::ESub, "EXCT CC B", "EXCT CC B", &extraCT2, 0, 127, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
if(extraCT2) numToString(extraCT2, out);
|
|
else strncpy(out, "OFF", 4);
|
|
},
|
|
[](const MenuEntrySub & __unused sub) { writeSetting(EXTRA2_ADDR,extraCT2); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub harmonicsMenu = {
|
|
MenuType::ESub, "EXCT HARM", "HARM RANGE", &harmSetting, 0, 6, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
if(harmSetting) numToString(harmSetting, out);
|
|
else strncpy(out, "OFF", 4);
|
|
},
|
|
[](const MenuEntrySub & __unused sub) { writeSetting(HARMSET_ADDR,harmSetting); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub harmSelectMenu = {
|
|
MenuType::ESub, "HARM SEL", "SERIES", &harmSelect, 0, 5, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
const char* harmSelectMenuLabels[] = { "HM1", "HM2", "5TH", "OCT", "5DN", "ODN" };
|
|
strncpy(out, harmSelectMenuLabels[harmSelect], 4);
|
|
},
|
|
[](const MenuEntrySub & __unused sub){
|
|
if (readSetting(HARMSEL_ADDR) != harmSelect) {
|
|
writeSetting(HARMSEL_ADDR,harmSelect);
|
|
}
|
|
}
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntryStateCh vibratoSubMenu = { MenuType::EStateChange, "VIBRATO", VIBRATO_MENU };
|
|
|
|
const MenuEntrySub deglitchMenu = {
|
|
MenuType::ESub, "DEGLITCH", "DEGLITCH", °litch, 0, 70, MenuEntryFlags::ENone,
|
|
[](SubMenuRef __unused,char* textBuffer, const char** label) {
|
|
if(deglitch) {
|
|
numToString(deglitch, textBuffer);
|
|
*label = "ms";
|
|
} else
|
|
strncpy(textBuffer, "OFF", 4);
|
|
},
|
|
[](const MenuEntrySub & __unused sub) { writeSetting(DEGLITCH_ADDR,deglitch); }
|
|
, nullptr
|
|
};
|
|
|
|
#if defined(NURAD)
|
|
const MenuEntrySub pinkyMenu = {
|
|
MenuType::ESub, "MOD KEY", "MOD KEY", &pinkySetting, 0, 30, MenuEntryFlags::ENone,
|
|
[](SubMenuRef __unused,char* textBuffer, const char** __unused unit) {
|
|
#else
|
|
const MenuEntrySub pinkyMenu = {
|
|
MenuType::ESub, "PINKY KEY", "PINKY KEY", &pinkySetting, 0, 30, MenuEntryFlags::ENone,
|
|
[](SubMenuRef __unused,char* textBuffer, const char** __unused unit) {
|
|
#endif
|
|
if (pinkySetting == PBD)
|
|
strncpy(textBuffer, "PBD", 4);
|
|
else if (pinkySetting == EC2)
|
|
strncpy(textBuffer, "ECB", 4);
|
|
else if (pinkySetting == ECSW)
|
|
strncpy(textBuffer, "ECS", 4);
|
|
else if (pinkySetting == LVL)
|
|
strncpy(textBuffer, "LVL", 4);
|
|
else if (pinkySetting == LVLP)
|
|
strncpy(textBuffer, "LVP", 4);
|
|
else if (pinkySetting == GLD)
|
|
strncpy(textBuffer, "GLD", 4);
|
|
else if (pinkySetting == ECH)
|
|
strncpy(textBuffer, "ECH", 4);
|
|
else
|
|
numToString(pinkySetting-12, textBuffer, true);
|
|
},
|
|
[](const MenuEntrySub & __unused sub) { writeSetting(PINKY_KEY_ADDR,pinkySetting); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub lvlCtrlCCMenu = {
|
|
MenuType::ESub, "LEVEL CC", "LEVEL CC", &levelCC, 0, 127, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
if(levelCC) numToString(levelCC, out);
|
|
else strncpy(out, "AT", 4);
|
|
},
|
|
[](const MenuEntrySub & __unused sub) { writeSetting(LEVEL_CC_ADDR,levelCC); }
|
|
, nullptr
|
|
};
|
|
|
|
#if defined(NURAD)
|
|
const MenuEntrySub fingeringMenu = {
|
|
MenuType::ESub, "FINGERING", "FINGERING", &fingering, 0, 4, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused,char* out, const char ** __unused unit) {
|
|
const char* labs[] = { "EWI", "EWX", "SAX", "EVI", "EVR" };
|
|
strncpy(out, labs[fingering], 4);
|
|
},
|
|
[](SubMenuRef __unused sub) { writeSetting(FINGER_ADDR,fingering); }
|
|
};
|
|
#else
|
|
const MenuEntrySub fingeringMenu = {
|
|
MenuType::ESub, "FINGERING", "FINGERING", &fingering, 0, 2, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused,char* out, const char ** __unused unit) {
|
|
const char* labs[] = { "EVI", "TPT", "HRN" };
|
|
strncpy(out, labs[fingering], 4);
|
|
},
|
|
[](SubMenuRef __unused sub) { writeSetting(FINGER_ADDR,fingering); }
|
|
};
|
|
#endif
|
|
|
|
|
|
|
|
const MenuEntrySub lpinky3Menu = {
|
|
MenuType::ESub, "EXTRA KEY", "EXTRA PKEY", &lpinky3, 0, 25, MenuEntryFlags::ENone,
|
|
[](SubMenuRef __unused,char* textBuffer, const char** __unused unit) {
|
|
if (lpinky3 == 0)
|
|
strncpy(textBuffer, "OFF", 4);
|
|
else if (lpinky3 == MOD)
|
|
strncpy(textBuffer, "MOD", 4);
|
|
else
|
|
numToString(lpinky3-13, textBuffer, true);
|
|
},
|
|
[](const MenuEntrySub & __unused sub) { writeSetting(LPINKY3_ADDR,lpinky3); }
|
|
, nullptr
|
|
};
|
|
|
|
#if defined(NURAD)
|
|
const MenuEntry* controlMenuEntries[] = {
|
|
(MenuEntry*)&biteCtlMenu,
|
|
(MenuEntry*)&biteCCMenu,
|
|
(MenuEntry*)&leverCtlMenu,
|
|
(MenuEntry*)&leverCCMenu,
|
|
(MenuEntry*)&portMenu,
|
|
(MenuEntry*)&portLimitMenu,
|
|
(MenuEntry*)&vibratoSubMenu,
|
|
(MenuEntry*)&extraMenu,
|
|
(MenuEntry*)&extraCC2Menu,
|
|
(MenuEntry*)&harmonicsMenu,
|
|
(MenuEntry*)&harmSelectMenu,
|
|
(MenuEntry*)°litchMenu,
|
|
(MenuEntry*)&pinkyMenu,
|
|
(MenuEntry*)&lvlCtrlCCMenu,
|
|
(MenuEntry*)&lpinky3Menu,
|
|
(MenuEntry*)&fingeringMenu,
|
|
(MenuEntry*)&pitchBendMenu
|
|
};
|
|
#else
|
|
const MenuEntry* controlMenuEntries[] = {
|
|
(MenuEntry*)&biteCtlMenu,
|
|
(MenuEntry*)&biteCCMenu,
|
|
(MenuEntry*)&leverCtlMenu,
|
|
(MenuEntry*)&leverCCMenu,
|
|
(MenuEntry*)&portMenu,
|
|
(MenuEntry*)&portLimitMenu,
|
|
(MenuEntry*)&vibratoSubMenu,
|
|
(MenuEntry*)&extraMenu,
|
|
(MenuEntry*)&extraCC2Menu,
|
|
(MenuEntry*)&harmonicsMenu,
|
|
(MenuEntry*)&harmSelectMenu,
|
|
(MenuEntry*)°litchMenu,
|
|
(MenuEntry*)&pinkyMenu,
|
|
(MenuEntry*)&lvlCtrlCCMenu,
|
|
(MenuEntry*)&fingeringMenu,
|
|
(MenuEntry*)&pitchBendMenu
|
|
};
|
|
#endif
|
|
|
|
|
|
const MenuPage controlMenuPage = {
|
|
"SETUP CTRLS",
|
|
0,
|
|
CursorIdx::EControl,
|
|
MAIN_MENU,
|
|
ARR_LEN(controlMenuEntries), controlMenuEntries
|
|
};
|
|
|
|
|
|
//***********************************************************
|
|
// Vibrato menu
|
|
|
|
static void vibGetStr(SubMenuRef __unused, char* textBuffer, const char** __unused unit) {
|
|
if(vibrato)
|
|
numToString(vibrato, textBuffer);
|
|
else
|
|
strncpy(textBuffer, "OFF", 4);
|
|
}
|
|
|
|
static void vibStore(const MenuEntrySub & __unused sub) {
|
|
writeSetting(VIBRATO_ADDR,vibrato);
|
|
}
|
|
|
|
const MenuEntrySub vibDepthMenu = {
|
|
MenuType::ESub, "DEPTH", "LEVEL", &vibrato, 0, 9, MenuEntryFlags::ENone,
|
|
vibGetStr,
|
|
vibStore,
|
|
nullptr
|
|
};
|
|
|
|
const MenuEntrySub vibRetnMenu = {
|
|
MenuType::ESub, "RETURN", "LEVEL", &vibRetn, 0, 4, MenuEntryFlags::ENone,
|
|
[](SubMenuRef __unused, char* textBuffer, const char** __unused unit) {
|
|
numToString(vibRetn, textBuffer);
|
|
},
|
|
[](const MenuEntrySub & __unused sub) { writeSetting(VIB_RETN_ADDR,vibRetn); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub vibSenseMenu = {
|
|
MenuType::ESub, "SENSE LVR", "LEVEL", &vibSens, 1, 12, MenuEntryFlags::ENone,
|
|
[](SubMenuRef __unused,char* textBuffer, const char** __unused unit) {
|
|
numToString(vibSens, textBuffer);
|
|
},
|
|
[](const MenuEntrySub & __unused sub) { writeSetting(VIB_SENS_ADDR,vibSens); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub vibSquelchMenu = {
|
|
MenuType::ESub, "SQUELCH L", "LEVEL", &vibSquelch, 1, 30, MenuEntryFlags::ENone,
|
|
[](SubMenuRef __unused, char* textBuffer, const char** __unused unit) {
|
|
numToString(vibSquelch, textBuffer);
|
|
},
|
|
[](const MenuEntrySub & __unused sub) { writeSetting(VIB_SQUELCH_ADDR,vibSquelch); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub vibSenseBiteMenu = {
|
|
MenuType::ESub, "SENSE BTE", "LEVEL", &vibSensBite, 1, 17, MenuEntryFlags::ENone,
|
|
[](SubMenuRef __unused,char* textBuffer, const char** __unused unit) {
|
|
numToString(vibSensBite, textBuffer);
|
|
},
|
|
[](const MenuEntrySub & __unused sub) { writeSetting(VIB_SENS_BITE_ADDR,vibSensBite); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub vibSquelchBiteMenu = {
|
|
MenuType::ESub, "SQUELCH B", "LEVEL", &vibSquelchBite, 1, 30, MenuEntryFlags::ENone,
|
|
[](SubMenuRef __unused, char* textBuffer, const char** __unused unit) {
|
|
numToString(vibSquelchBite, textBuffer);
|
|
},
|
|
[](const MenuEntrySub & __unused sub) { writeSetting(VIB_SQUELCH_BITE_ADDR,vibSquelchBite); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub vibControlMenu = {
|
|
MenuType::ESub, "CONTROL", "CONTROL", &vibControl , 0, 2, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
if (2 == vibControl)
|
|
strncpy(out, "BTH", 4);
|
|
else if (1 == vibControl)
|
|
strncpy(out, "BIT", 4);
|
|
else
|
|
strncpy(out, "LVR", 4);
|
|
},
|
|
[](const MenuEntrySub & __unused sub) { writeSetting(VIB_CONTROL_ADDR,vibControl); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntrySub vibDirMenu = {
|
|
MenuType::ESub, "DIRECTION", "DIRECTION", &vibDirection , 0, 1, MenuEntryFlags::EMenuEntryWrap,
|
|
[](SubMenuRef __unused, char* out, const char** __unused unit) {
|
|
if (DNWD == vibDirection)
|
|
strncpy(out, "NRM", 4);
|
|
else
|
|
strncpy(out, "REV", 4);
|
|
},
|
|
[](const MenuEntrySub & __unused sub) { writeSetting(VIB_DIRECTION_ADDR,vibDirection); }
|
|
, nullptr
|
|
};
|
|
|
|
const MenuEntry* vibratorMenuEntries[] = {
|
|
//(MenuEntry*)&vibControlMenu,
|
|
(MenuEntry*)&vibDepthMenu,
|
|
(MenuEntry*)&vibRetnMenu,
|
|
(MenuEntry*)&vibDirMenu,
|
|
(MenuEntry*)&vibSenseMenu,
|
|
(MenuEntry*)&vibSquelchMenu,
|
|
(MenuEntry*)&vibSenseBiteMenu,
|
|
(MenuEntry*)&vibSquelchBiteMenu,
|
|
};
|
|
|
|
const MenuPage vibratoMenuPage = {
|
|
"VIBRATO", 0, CursorIdx::EVibrato, SETUP_CT_MENU, ARR_LEN(vibratorMenuEntries), vibratorMenuEntries
|
|
};
|
|
|
|
|
|
//***********************************************************
|
|
|
|
static bool patchPageUpdate(KeyState& __unused input, uint32_t __unused timeNow);
|
|
static bool idlePageUpdate(KeyState& __unused input, uint32_t __unused timeNow);
|
|
|
|
const MenuPageCustom adjustMenuPage = { nullptr, EMenuPageCustom, adjustPageUpdate };
|
|
const MenuPageCustom patchMenuPage = { nullptr, EMenuPageCustom, patchPageUpdate };
|
|
const MenuPageCustom idleMenuPage = { nullptr, EMenuPageCustom, idlePageUpdate };
|
|
|
|
const MenuPageCustom aboutMenuPage = { nullptr, EMenuPageCustom,
|
|
[](KeyState& __unused input, uint32_t __unused timeNow) -> bool {
|
|
static uint32_t timer = 0;
|
|
if(stateFirstRun) {
|
|
display.clearDisplay();
|
|
timer = timeNow + 3500;
|
|
stateFirstRun = 0;
|
|
display.setCursor(49,0);
|
|
display.setTextColor(WHITE);
|
|
display.setTextSize(0);
|
|
#if defined(NURAD)
|
|
display.println("NuRAD");
|
|
#else
|
|
display.println("NuEVI");
|
|
#endif
|
|
display.setCursor(16,12);
|
|
display.print("firmware v.");
|
|
display.println(FIRMWARE_VERSION);
|
|
int vMeterReading = battAvg;
|
|
int voltage = map(vMeterReading,2200,3060,36,50);
|
|
display.setCursor(16,32);
|
|
if (vMeterReading > 3020){
|
|
drawFlash(4,34);
|
|
display.print("USB power");
|
|
display.setCursor(16,42);
|
|
display.print(voltage/10);
|
|
display.print(".");
|
|
display.print(voltage%10);
|
|
display.print("v");
|
|
} else {
|
|
int bar = drawBatt(4,34);
|
|
if (0 == batteryType) {
|
|
display.println("Alkaline battery");
|
|
display.setCursor(16,42);
|
|
display.print(bar);
|
|
display.print("0 % at ");
|
|
display.print(voltage/10);
|
|
display.print(".");
|
|
display.print(voltage%10);
|
|
display.print("v");
|
|
} else if (1 == batteryType) {
|
|
display.println("NiMH battery");
|
|
display.setCursor(16,42);
|
|
display.print(bar);
|
|
display.print("0 % at ");
|
|
display.print(voltage/10);
|
|
display.print(".");
|
|
display.print(voltage%10);
|
|
display.print("v");
|
|
} else if (2 == batteryType) {
|
|
display.println("LiPo battery");
|
|
display.setCursor(16,42);
|
|
display.print(bar);
|
|
display.print("0 % at ");
|
|
display.print(voltage/10);
|
|
display.print(".");
|
|
display.print(voltage%10);
|
|
display.print("v");
|
|
}
|
|
}
|
|
|
|
|
|
return true;
|
|
} else {
|
|
if(timeNow >= timer) {
|
|
menuState = MAIN_MENU;
|
|
stateFirstRun = 1;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
//***********************************************************
|
|
|
|
static bool selectMenuOption(const MenuPage *page) {
|
|
int cursorPosition = cursors[page->cursor];
|
|
const MenuEntry* menuEntry = page->entries[cursorPosition];
|
|
cursorBlinkTime = millis();
|
|
|
|
switch(menuEntry->type) {
|
|
case MenuType::ESub:
|
|
activeSub[page->cursor] = cursorPosition+1;
|
|
drawMenuCursor(cursorPosition-offsets[page->cursor], WHITE);
|
|
drawSubBox( ((const MenuEntrySub*)menuEntry)->subTitle);
|
|
drawSubMenu(page);
|
|
return true;
|
|
|
|
case MenuType::EStateChange:
|
|
menuState= ((const MenuEntryStateCh*)menuEntry)->state;
|
|
stateFirstRun = 1;
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//***********************************************************
|
|
|
|
static bool updateSubMenu(const MenuPage *page, KeyState &input, uint32_t timeNow) {
|
|
|
|
bool redraw = false;
|
|
bool redrawSubValue = false;
|
|
if (input.changed) {
|
|
int current_sub = activeSub[page->cursor] -1;
|
|
|
|
if( current_sub < 0)
|
|
return false;
|
|
|
|
auto sub = (const MenuEntrySub*)page->entries[current_sub];
|
|
uint16_t currentVal = *sub->valuePtr;
|
|
|
|
switch (input.current){
|
|
case BTN_DOWN:
|
|
if(currentVal > sub->min) {
|
|
currentVal -= 1;
|
|
} else if(sub->flags & MenuEntryFlags::EMenuEntryWrap) {
|
|
currentVal = sub->max;
|
|
}
|
|
break;
|
|
|
|
case BTN_UP:
|
|
if(currentVal < sub->max) {
|
|
currentVal += 1;
|
|
} else if(sub->flags & MenuEntryFlags::EMenuEntryWrap) {
|
|
currentVal = sub->min;
|
|
}
|
|
break;
|
|
|
|
case BTN_ENTER:
|
|
if(sub->flags & EMenuEntryEnterHandler) {
|
|
bool result = sub->onEnterFunc();
|
|
if(result) {
|
|
activeSub[page->cursor] = 0;
|
|
}
|
|
} else {
|
|
activeSub[page->cursor] = 0;
|
|
sub->applyFunc(*sub);
|
|
}
|
|
break;
|
|
|
|
case BTN_MENU:
|
|
activeSub[page->cursor] = 0;
|
|
sub->applyFunc(*sub);
|
|
break;
|
|
}
|
|
*sub->valuePtr = currentVal;
|
|
redrawSubValue = true;
|
|
} else {
|
|
redraw = updateSubMenuCursor( page, timeNow );
|
|
}
|
|
|
|
if(redrawSubValue) {
|
|
clearSubValue();
|
|
redraw |= drawSubMenu(page);
|
|
cursorNow = BLACK;
|
|
cursorBlinkTime = timeNow;
|
|
}
|
|
|
|
return redraw;
|
|
}
|
|
|
|
static bool updateMenuPage(const MenuPage *page, KeyState &input, uint32_t timeNow) {
|
|
byte cursorPos = cursors[page->cursor];
|
|
byte newPos = cursorPos;
|
|
bool redraw = false;
|
|
|
|
if (input.changed) {
|
|
int lastEntry = page->numEntries-1;
|
|
switch (input.current) {
|
|
case BTN_DOWN:
|
|
if (cursorPos < lastEntry)
|
|
newPos = cursorPos+1;
|
|
break;
|
|
|
|
case BTN_ENTER:
|
|
redraw |= selectMenuOption(page);
|
|
break;
|
|
|
|
case BTN_UP:
|
|
if (cursorPos > 0)
|
|
newPos = cursorPos-1;
|
|
break;
|
|
|
|
case BTN_MENU:
|
|
menuState= page->parentPage;
|
|
stateFirstRun = 1;
|
|
break;
|
|
}
|
|
|
|
if(newPos != cursorPos) {
|
|
|
|
int offset = offsets[page->cursor];
|
|
drawMenuCursor(cursorPos-offset, BLACK); // Clear old cursor
|
|
|
|
if(page->numEntries >= MENU_NUM_ROWS) {
|
|
// Handle scrolling..
|
|
if((newPos - offset) > (MENU_NUM_ROWS-2) ) {
|
|
offset = newPos - (MENU_NUM_ROWS-2);
|
|
} else if( (newPos - offset) < 1) {
|
|
offset = newPos - 1;
|
|
}
|
|
|
|
offset = constrain(offset, 0, page->numEntries - MENU_NUM_ROWS);
|
|
|
|
if( offset != offsets[page->cursor]) {
|
|
offsets[page->cursor] = offset;
|
|
plotMenuEntries(page, true);
|
|
}
|
|
}
|
|
|
|
drawMenuCursor(newPos-offset, 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-offsets[page->cursor], cursorNow);
|
|
redraw = true;
|
|
cursorBlinkTime = timeNow;
|
|
}
|
|
|
|
return redraw;
|
|
}
|
|
|
|
static bool updatePage(const MenuPage *page, KeyState &input, uint32_t timeNow) {
|
|
|
|
display.setTextColor(WHITE);
|
|
|
|
if(page->flags & EMenuPageCustom) {
|
|
const MenuPageCustom* custom = (const MenuPageCustom*)page;
|
|
return custom->menuUpdateFunc(input, timeNow);
|
|
}
|
|
|
|
bool redraw = stateFirstRun;
|
|
|
|
if (stateFirstRun) {
|
|
drawMenu(page);
|
|
stateFirstRun = 0;
|
|
}
|
|
if (activeSub[page->cursor]) {
|
|
redraw = updateSubMenu(page, input, timeNow);
|
|
} else {
|
|
redraw = updateMenuPage(page, input, timeNow);
|
|
|
|
if((page->flags & EMenuPageRoot) && input.changed) {
|
|
int trills = readTrills();
|
|
switch (input.current) {
|
|
case BTN_MENU+BTN_ENTER:
|
|
if (trills) {
|
|
menuState = PATCH_VIEW;
|
|
stateFirstRun = 1;
|
|
setFPS(trills, patch);
|
|
}
|
|
break;
|
|
|
|
case BTN_MENU+BTN_UP:
|
|
if (trills) {
|
|
menuState = PATCH_VIEW;
|
|
stateFirstRun = 1;
|
|
clearFPS(trills);
|
|
}
|
|
break;
|
|
default: break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return redraw;
|
|
}
|
|
|
|
|
|
//***********************************************************
|
|
|
|
|
|
static bool updateSensorPixelsFlag = false;
|
|
void drawSensorPixels() {
|
|
updateSensorPixelsFlag = true;
|
|
}
|
|
|
|
//***********************************************************
|
|
|
|
|
|
bool adjustPageUpdate(KeyState &input, uint32_t timeNow) {
|
|
// This is a hack to update touch_Thr is it was changed..
|
|
int old_thr = ctouchThrVal;
|
|
int result = updateAdjustMenu(timeNow, input, stateFirstRun, updateSensorPixelsFlag);
|
|
bool redraw = false;
|
|
|
|
updateSensorPixelsFlag = false;
|
|
stateFirstRun = 0;
|
|
|
|
if(result < 0) {
|
|
// Go back to main menu
|
|
menuState= MAIN_MENU;
|
|
stateFirstRun = true;
|
|
} else {
|
|
redraw = result;
|
|
}
|
|
|
|
if( old_thr != ctouchThrVal) {
|
|
touch_Thr = map(ctouchThrVal,ctouchHiLimit,ctouchLoLimit,ttouchLoLimit,ttouchHiLimit);
|
|
}
|
|
return redraw;
|
|
}
|
|
|
|
|
|
static bool patchPageUpdate(KeyState& input, uint32_t timeNow) {
|
|
bool redraw = stateFirstRun;
|
|
|
|
if (stateFirstRun) {
|
|
display.ssd1306_command(SSD1306_DISPLAYON);
|
|
drawPatchView();
|
|
patchViewTime = timeNow;
|
|
stateFirstRun = 0;
|
|
}
|
|
if ((timeNow - patchViewTime) > patchViewTimeUp) {
|
|
menuState= DISPLAYOFF_IDL;
|
|
stateFirstRun = 1;
|
|
doPatchUpdate = 1;
|
|
FPD = 0;
|
|
writeSetting(PATCH_ADDR,patch);
|
|
}
|
|
if (input.changed) {
|
|
patchViewTime = timeNow;
|
|
int trills = readTrills();
|
|
switch (input.current){
|
|
case BTN_DOWN:
|
|
// fallthrough
|
|
case BTN_UP:
|
|
if (trills && (fastPatch[trills-1] > 0)){
|
|
patch = fastPatch[trills-1];
|
|
activePatch = 0;
|
|
doPatchUpdate = 1;
|
|
FPD = 1;
|
|
writeSetting(PATCH_ADDR,patch);
|
|
} else if (!trills){
|
|
patch = (((patch-1u) + ((input.current == BTN_UP)?1u:-1u))&127u) + 1u;
|
|
activePatch = 0;
|
|
doPatchUpdate = 1;
|
|
FPD = 0;
|
|
}
|
|
drawPatchView();
|
|
redraw = true;
|
|
break;
|
|
|
|
case BTN_ENTER:
|
|
if (trills && (fastPatch[trills-1] > 0)){
|
|
patch = fastPatch[trills-1];
|
|
activePatch = 0;
|
|
doPatchUpdate = 1;
|
|
FPD = 1;
|
|
drawPatchView();
|
|
redraw = true;
|
|
}
|
|
break;
|
|
|
|
|
|
case BTN_MENU:
|
|
if (FPD < 2){
|
|
menuState= DISPLAYOFF_IDL;
|
|
stateFirstRun = 1;
|
|
doPatchUpdate = 1;
|
|
}
|
|
writeSetting(PATCH_ADDR,patch);
|
|
FPD = 0;
|
|
break;
|
|
|
|
case BTN_MENU+BTN_ENTER:
|
|
display.clearDisplay();
|
|
display.setTextSize(2);
|
|
display.setCursor(35,15);
|
|
display.println("DON'T");
|
|
display.setCursor(35,30);
|
|
display.println("PANIC");
|
|
display.display(); // call display explicitly _before_ we call midiPanic
|
|
midiPanic();
|
|
break;
|
|
|
|
case BTN_MENU+BTN_ENTER+BTN_UP+BTN_DOWN:
|
|
//all keys depressed, reboot to programming mode
|
|
_reboot_Teensyduino_();
|
|
}
|
|
}
|
|
|
|
return redraw;
|
|
}
|
|
|
|
|
|
static bool idlePageUpdate(KeyState& __unused input, uint32_t __unused timeNow) {
|
|
bool redraw = false;
|
|
if (stateFirstRun) {
|
|
display.ssd1306_command(SSD1306_DISPLAYOFF);
|
|
display.clearDisplay();
|
|
redraw = true;
|
|
stateFirstRun = 0;
|
|
}
|
|
if (input.changed) {
|
|
int trills = readTrills();
|
|
switch (input.current){
|
|
case BTN_UP:
|
|
// fallthrough
|
|
case BTN_DOWN:
|
|
if (!trills) {
|
|
patch = (((patch-1u) + ((input.current == BTN_UP)?1u:-1u))&127u) + 1u;
|
|
}
|
|
// fallthrough
|
|
case BTN_ENTER:
|
|
if (trills && (fastPatch[trills-1] > 0)){
|
|
patch = fastPatch[trills-1];
|
|
activePatch = 0;
|
|
doPatchUpdate = 1;
|
|
FPD = 1;
|
|
}
|
|
menuState= PATCH_VIEW;
|
|
stateFirstRun = 1;
|
|
break;
|
|
|
|
case BTN_MENU:
|
|
/* REMOVE ALL MODIFIER ENTRIES
|
|
if (pinkyKey && (exSensor >= ((extracThrVal+extracMaxVal)/2)) && !specialKey) { // switch breath activated legacy settings on/off
|
|
legacyBrAct = !legacyBrAct;
|
|
dipSwBits = dipSwBits ^ (1<<2);
|
|
writeSetting(DIPSW_BITS_ADDR,dipSwBits);
|
|
statusLedBlink();
|
|
} else if ((exSensor >= ((extracThrVal+extracMaxVal)/2)) && !specialKey) { // switch pb pad activated legacy settings control on/off
|
|
legacy = !legacy;
|
|
dipSwBits = dipSwBits ^ (1<<1);
|
|
writeSetting(DIPSW_BITS_ADDR,dipSwBits);
|
|
statusLedBlink();
|
|
} 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);
|
|
menuState= ROTATOR_MENU;
|
|
stateFirstRun = 1;
|
|
} else {
|
|
display.ssd1306_command(SSD1306_DISPLAYON);
|
|
menuState = MAIN_MENU;
|
|
stateFirstRun = 1;
|
|
}*/
|
|
display.ssd1306_command(SSD1306_DISPLAYON);
|
|
menuState = MAIN_MENU;
|
|
stateFirstRun = 1;
|
|
break;
|
|
|
|
case BTN_UP | BTN_DOWN | BTN_ENTER | BTN_MENU:
|
|
//all keys depressed, reboot to programming mode
|
|
_reboot_Teensyduino_();
|
|
}
|
|
}
|
|
return redraw;
|
|
}
|
|
|
|
|
|
//***********************************************************
|
|
|
|
static KeyState 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;
|
|
|
|
KeyState keys = { deumButtonState, 0 };
|
|
|
|
// read the state of the switches (note that they are active low, so we invert the values)
|
|
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) {
|
|
keys.current = deumButtons;
|
|
keys.changed = deumButtonState ^ deumButtons;
|
|
|
|
deumButtonState = deumButtons;
|
|
menuTime = timeNow;
|
|
buttonPressedTime = timeNow;
|
|
}
|
|
|
|
if (((deumButtons == BTN_DOWN) || (deumButtons == BTN_UP)) && (timeNow - buttonPressedTime > buttonRepeatDelay) && (timeNow - buttonRepeatTime > buttonRepeatInterval)){
|
|
buttonRepeatTime = timeNow;
|
|
keys.changed = deumButtons; // Key repeat
|
|
}
|
|
}
|
|
|
|
// save the reading. Next time through the loop, it'll be the lastButtonState:
|
|
lastDeumButtons = deumButtons;
|
|
|
|
return keys;
|
|
}
|
|
|
|
|
|
|
|
void menu() {
|
|
unsigned long timeNow = millis();
|
|
|
|
bool redraw = stateFirstRun;
|
|
|
|
KeyState 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 (menuState&& ((timeNow - menuTime) > menuTimeUp)) {
|
|
menuState= DISPLAYOFF_IDL;
|
|
stateFirstRun = 1;
|
|
subVibSquelch = 0;
|
|
memset(activeSub, 0, sizeof(activeSub));
|
|
}
|
|
|
|
if (menuState== DISPLAYOFF_IDL) {
|
|
redraw |= updatePage((const MenuPage*)&idleMenuPage, input, timeNow);
|
|
} else if (menuState== PATCH_VIEW) {
|
|
redraw |= updatePage((const MenuPage*)&patchMenuPage, input, timeNow);
|
|
} else if (menuState== MAIN_MENU) {
|
|
redraw |= updatePage(&mainMenuPage, input, timeNow);
|
|
} else if (menuState== ROTATOR_MENU) {
|
|
redraw |= updatePage(&rotatorMenuPage, input, timeNow);
|
|
} else if (menuState== ADJUST_MENU) {
|
|
redraw |= updatePage((const MenuPage*)&adjustMenuPage, input, timeNow);
|
|
} else if (menuState== SETUP_BR_MENU) {
|
|
redraw |= updatePage(&breathMenuPage, input, timeNow);
|
|
} else if (menuState== SETUP_CT_MENU) {
|
|
redraw |= updatePage(&controlMenuPage, input, timeNow);
|
|
} else if (menuState== VIBRATO_MENU) {
|
|
redraw |= updatePage(&vibratoMenuPage, input, timeNow);
|
|
} else if (menuState == ABOUT_MENU) {
|
|
redraw |= updatePage((const MenuPage*)&aboutMenuPage, input, timeNow);
|
|
} else if (menuState == EXTRAS_MENU) {
|
|
redraw |= updatePage((const MenuPage*)&extrasMenuPage, input, timeNow);
|
|
}
|
|
|
|
if(redraw) {
|
|
display.display();
|
|
}
|
|
}
|