about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Classes.h158
-rw-r--r--Constants.h140
-rw-r--r--Hexperiment.ino1820
-rw-r--r--Presets.h336
4 files changed, 1197 insertions, 1257 deletions
diff --git a/Classes.h b/Classes.h
new file mode 100644
index 0000000..133d3a5
--- /dev/null
+++ b/Classes.h
@@ -0,0 +1,158 @@
+// class definitions are in a header so that

+// they get read before compiling the main program.

+

+class tuningDef {

+public:

+  std::string name; // limit is 17 characters for GEM menu

+  byte cycleLength; // steps before period/cycle/octave repeats

+  float stepSize;   // in cents, 100 = "normal" semitone.

+  SelectOptionInt keyChoices[MAX_SCALE_DIVISIONS];

+  int spanCtoA() {

+    return (- keyChoices[0].val_int);

+  };

+};

+

+class layoutDef {

+public:

+  std::string name;    // limit is 17 characters for GEM menu

+  bool isPortrait;     // affects orientation of the GEM menu only.

+  byte hexMiddleC;     // instead of "what note is button 1", "what button is the middle"

+  int8_t acrossSteps;  // defined this way to be compatible with original v1.1 firmare

+  int8_t dnLeftSteps;  // defined this way to be compatible with original v1.1 firmare

+  byte tuning;         // index of the tuning that this layout is designed for

+};

+

+class colorDef {

+public:

+  float hue;

+  byte sat;

+  byte val;

+  colorDef mixWithWhite() {

+    colorDef temp;

+    temp.hue = this->hue;

+    temp.sat = ((this->sat > SAT_TINT) ? SAT_TINT : this->sat);

+    temp.val = VALUE_FULL;

+    return temp;

+  };

+};

+

+class paletteDef {

+public:

+  colorDef swatch[MAX_SCALE_DIVISIONS]; // the different colors used in this palette

+  byte colorNum[MAX_SCALE_DIVISIONS];   // map key (c,d...) to swatches

+  colorDef getColor(byte givenStepFromC) {

+    return swatch[colorNum[givenStepFromC] - 1];

+  };

+  float getHue(byte givenStepFromC) {

+    return getColor(givenStepFromC).hue;

+  };

+  byte getSat(byte givenStepFromC) {

+    return getColor(givenStepFromC).sat;

+  };

+  byte getVal(byte givenStepFromC) {

+    return getColor(givenStepFromC).val;

+  };

+};

+

+class buttonDef {

+public:

+  byte     btnState = 0;        // binary 00 = off, 01 = just pressed, 10 = just released, 11 = held

+  void interpBtnPress(bool isPress) {

+    btnState = (((btnState << 1) + isPress) & 3);

+  };

+  int8_t   coordRow = 0;        // hex coordinates

+  int8_t   coordCol = 0;        // hex coordinates

+  uint32_t timePressed = 0;     // timecode of last press

+  uint32_t LEDcolorAnim = 0;    // calculate it once and store value, to make LED playback snappier 

+  uint32_t LEDcolorPlay = 0;    // calculate it once and store value, to make LED playback snappier

+  uint32_t LEDcolorOn = 0;      // calculate it once and store value, to make LED playback snappier

+  uint32_t LEDcolorOff = 0;     // calculate it once and store value, to make LED playback snappier

+  bool     animate = 0;         // hex is flagged as part of the animation in this frame, helps make animations smoother

+  int16_t  stepsFromC = 0;      // number of steps from C4 (semitones in 12EDO; microtones if >12EDO)

+  bool     isCmd = 0;           // 0 if it's a MIDI note; 1 if it's a MIDI control cmd

+  bool     inScale = 0;         // 0 if it's not in the selected scale; 1 if it is

+  byte     note = UNUSED_NOTE;  // MIDI note or control parameter corresponding to this hex

+  int16_t  bend = 0;            // in microtonal mode, the pitch bend for this note needed to be tuned correctly

+  byte     channel = 0;         // what MIDI channel this note is playing on

+  float    frequency = 0.0;     // what frequency to ring on the buzzer

+};

+

+class wheelDef {

+public:

+  bool alternateMode; // two ways to control

+  bool isSticky;      // TRUE if you leave value unchanged when no buttons pressed

+  byte* topBtn;       // pointer to the key Status of the button you use as this button

+  byte* midBtn;

+  byte* botBtn;

+  int16_t minValue;

+  int16_t maxValue;

+  int* stepValue; // this can be changed via GEM menu

+  int16_t defValue; // snapback value

+  int16_t curValue;

+  int16_t targetValue;

+  uint32_t timeLastChanged;

+  void setTargetValue() {

+    if (alternateMode) {

+      if (*midBtn >> 1) { // middle button toggles target (0) vs. step (1) mode

+        int16_t temp = curValue;

+            if (*topBtn == 1)     {temp += *stepValue;}; // tap button

+            if (*botBtn == 1)     {temp -= *stepValue;}; // tap button

+            if (temp > maxValue)  {temp  = maxValue;} 

+        else if (temp <= minValue) {temp  = minValue;};

+        targetValue = temp;

+      } else {

+        switch (((*topBtn >> 1) << 1) + (*botBtn >> 1)) {

+          case 0b10:   targetValue = maxValue;     break;

+          case 0b11:   targetValue = defValue;     break;

+          case 0b01:   targetValue = minValue;     break;

+          default:     targetValue = curValue;     break;

+        };

+      };

+    } else {

+      switch (((*topBtn >> 1) << 2) + ((*midBtn >> 1) << 1) + (*botBtn >> 1)) {

+        case 0b100:  targetValue = maxValue;                         break;

+        case 0b110:  targetValue = (3 * maxValue + minValue) / 4;    break;

+        case 0b010:

+        case 0b111:

+        case 0b101:  targetValue = (maxValue + minValue) / 2;        break;

+        case 0b011:  targetValue = (maxValue + 3 * minValue) / 4;    break;

+        case 0b001:  targetValue = minValue;                         break;

+        case 0b000:  targetValue = (isSticky ? curValue : defValue); break;

+        default: break;

+      };

+    }

+  };

+  bool updateValue(uint32_t givenTime) {

+    int16_t temp = targetValue - curValue;

+    if (temp != 0) {

+      if ((givenTime - timeLastChanged) >= CC_MSG_COOLDOWN_MICROSECONDS ) {

+        timeLastChanged = givenTime;

+        if (abs(temp) < *stepValue) {

+          curValue = targetValue;

+        } else {

+          curValue = curValue + (*stepValue * (temp / abs(temp)));

+        };

+        return 1;

+      } else {

+        return 0;

+      };

+    } else {

+      return 0;

+    };

+  };   

+};

+// back button

+

+class scaleDef {

+public:

+  std::string name;

+  byte tuning;

+  byte pattern[MAX_SCALE_DIVISIONS];

+};

+

+// this class should only be touched by the 2nd core

+class oscillator {

+public:

+  uint16_t increment = 0;

+  uint16_t counter = 0;

+};
\ No newline at end of file
diff --git a/Constants.h b/Constants.h
new file mode 100644
index 0000000..a38732b
--- /dev/null
+++ b/Constants.h
@@ -0,0 +1,140 @@
+// hardware pins
+#define SDAPIN 16
+#define SCLPIN 17
+#define LED_PIN 22
+#define ROT_PIN_A 20
+#define ROT_PIN_B 21
+#define ROT_PIN_C 24
+#define MPLEX_1_PIN 4
+#define MPLEX_2_PIN 5
+#define MPLEX_4_PIN 2
+#define MPLEX_8_PIN 3
+#define COLUMN_PIN_0 6
+#define COLUMN_PIN_1 7
+#define COLUMN_PIN_2 8
+#define COLUMN_PIN_3 9
+#define COLUMN_PIN_4 10
+#define COLUMN_PIN_5 11
+#define COLUMN_PIN_6 12
+#define COLUMN_PIN_7 13
+#define COLUMN_PIN_8 14
+#define COLUMN_PIN_9 15
+#define TONEPIN 23
+
+// grid related
+#define LED_COUNT 140
+#define COLCOUNT 10
+#define ROWCOUNT 14
+
+#define HEX_DIRECTION_EAST 0
+#define HEX_DIRECTION_NE   1
+#define HEX_DIRECTION_NW   2
+#define HEX_DIRECTION_WEST 3
+#define HEX_DIRECTION_SW   4
+#define HEX_DIRECTION_SE   5
+
+#define CMDBTN_0 0
+#define CMDBTN_1 20
+#define CMDBTN_2 40
+#define CMDBTN_3 60
+#define CMDBTN_4 80
+#define CMDBTN_5 100
+#define CMDBTN_6 120
+#define CMDCOUNT 7
+
+// microtonal related
+#define TUNINGCOUNT 13
+
+#define TUNING_12EDO 0
+#define TUNING_17EDO 1
+#define TUNING_19EDO 2
+#define TUNING_22EDO 3
+#define TUNING_24EDO 4
+#define TUNING_31EDO 5
+#define TUNING_41EDO 6
+#define TUNING_53EDO 7
+#define TUNING_72EDO 8
+#define TUNING_BP 9
+#define TUNING_ALPHA 10
+#define TUNING_BETA 11
+#define TUNING_GAMMA 12     
+
+#define MAX_SCALE_DIVISIONS 72
+#define ALL_TUNINGS 255
+
+// MIDI-related
+#define CONCERT_A_HZ 440.0
+#define PITCH_BEND_SEMIS 2
+#define CMDB 192
+#define UNUSED_NOTE 255
+#define CC_MSG_COOLDOWN_MICROSECONDS 32768
+
+// buzzer related
+#define TONE_SL 3
+#define TONE_CH 1
+#define WAVEFORM_SQUARE 0
+#define WAVEFORM_SAW 1
+#define POLL_INTERVAL_IN_MICROSECONDS 32
+#define POLYPHONY_LIMIT 15
+#define ALARM_NUM 2
+#define ALARM_IRQ TIMER_IRQ_2
+#define BUZZ_OFF 0
+#define BUZZ_MONO 1
+#define BUZZ_ARPEGGIO 2
+#define BUZZ_POLY 3
+
+// LED related
+
+// value / brightness ranges from 0..255
+// black = 0, full strength = 255
+
+#define VALUE_BLACK 0
+#define VALUE_LOW   64
+#define VALUE_SHADE 128
+#define VALUE_NORMAL 192
+#define VALUE_FULL  255
+
+// saturation ranges from 0..255
+// 0 = black and white
+// 255 = full chroma
+
+#define SAT_BW 0
+#define SAT_TINT 32
+#define SAT_DULL 85
+#define SAT_MODERATE 170
+#define SAT_VIVID 255
+
+// hue is an angle from 0.0 to 359.9
+// there is a transform function to map "perceptual"
+// hues to RGB. the hue values below are perceptual.
+#define HUE_NONE 0.0
+#define HUE_RED 0.0
+#define HUE_ORANGE 36.0
+#define HUE_YELLOW 72.0
+#define HUE_LIME 108.0
+#define HUE_GREEN 144.0
+#define HUE_CYAN 180.0
+#define HUE_BLUE 216.0
+#define HUE_INDIGO 252.0
+#define HUE_PURPLE 288.0
+#define HUE_MAGENTA 324.0
+
+#define RAINBOW_MODE 0
+#define TIERED_COLOR_MODE 1
+
+// animations
+#define ANIMATE_NONE 0
+#define ANIMATE_STAR 1 
+#define ANIMATE_SPLASH 2 
+#define ANIMATE_ORBIT 3 
+#define ANIMATE_OCTAVE 4 
+#define ANIMATE_BY_NOTE 5
+
+// menu-related
+#define MENU_ITEM_HEIGHT 10
+#define MENU_PAGE_SCREEN_TOP_OFFSET 10
+#define MENU_VALUES_LEFT_OFFSET 78
+
+// debug
+#define DIAGNOSTIC_OFF 0
+#define DIAGNOSTIC_ON 1
\ No newline at end of file
diff --git a/Hexperiment.ino b/Hexperiment.ino
index a5aab7a..9b93007 100644
--- a/Hexperiment.ino
+++ b/Hexperiment.ino
@@ -35,875 +35,212 @@
   #include <queue>              // std::queue construct to store open channels in microtonal mode
   #include <string>
   
-  // hardware pins
-    #define SDAPIN 16
-    #define SCLPIN 17
+  #include "Constants.h"       // preprocessor constants / macros
+  #include "Classes.h"         // type definitions
+  #include "Presets.h"         // pre-load tuning, scale, palette, layout definitions
+
+  // ====== useful math functions
+    int positiveMod(int n, int d) {
+      return (((n % d) + d) % d);
+    }
+        
+    byte byteLerp(byte xOne, byte xTwo, float yOne, float yTwo, float y) {
+      float weight = (y - yOne) / (yTwo - yOne);
+      int temp = xOne + ((xTwo - xOne) * weight);
+      if (temp < xOne) {temp = xOne;};
+      if (temp > xTwo) {temp = xTwo;};
+      return temp;
+    }
 
-  // USB MIDI object //
     Adafruit_USBD_MIDI usb_midi;
     // Create a new instance of the Arduino MIDI Library,
     // and attach usb_midi as the transport.
     MIDI_CREATE_INSTANCE(Adafruit_USBD_MIDI, usb_midi, MIDI);
-    #define CONCERT_A_HZ 440.0
-    int16_t channelBend[16];   // what's the current note bend on this channel
-    byte channelPoly[16];      // how many notes are playing on this channel
-    std::queue<byte> openChannelQueue;
-    #define PITCH_BEND_SEMIS 2
 
-  // LED SETUP //
-    #define LED_PIN 22
-    #define LED_COUNT 140
     Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
 
-  // ENCODER SETUP //
-    #define ROT_PIN_A 20
-    #define ROT_PIN_B 21
-    #define ROT_PIN_C 24
     Rotary rotary = Rotary(ROT_PIN_A, ROT_PIN_B);
     bool rotaryIsClicked = HIGH;          //
     bool rotaryWasClicked = HIGH;         //
     int8_t rotaryKnobTurns = 0;           //
-    byte maxKnobTurns = 10;
+    byte maxKnobTurns = 3;
 
   // Create an instance of the U8g2 graphics library.
     U8G2_SH1107_SEEED_128X128_F_HW_I2C u8g2(U8G2_R2, /* reset=*/ U8X8_PIN_NONE);
 
   // Create menu object of class GEM_u8g2. Supply its constructor with reference to u8g2 object we created earlier
-    #define MENU_ITEM_HEIGHT 10
-    #define MENU_PAGE_SCREEN_TOP_OFFSET 10
-    #define MENU_VALUES_LEFT_OFFSET 78
     GEM_u8g2 menu(
       u8g2, GEM_POINTER_ROW, GEM_ITEMS_COUNT_AUTO, 
       MENU_ITEM_HEIGHT, MENU_PAGE_SCREEN_TOP_OFFSET, MENU_VALUES_LEFT_OFFSET
     );
-    const byte defaultContrast = 63;           // GFX default contrast
-    bool screenSaverOn = 0;                    //
-    uint32_t screenTime = 0;                   // GFX timer to count if screensaver should go on
-    const uint32_t screenSaverMillis = 10'000; //
+    const byte defaultContrast = 63;                // GFX default contrast
+    bool screenSaverOn = 0;                         //
+    uint64_t screenTime = 0;                        // GFX timer to count if screensaver should go on
+    const uint64_t screenSaverTimeout = (1u << 23); // 2^23 microseconds ~ 8 seconds
 
-  // DIAGNOSTICS //
-    // 1 = Full button test (1 and 0)
-    // 2 = Button test (button number)
-    // 3 = MIDI output test
-    // 4 = Loop timing readout in milliseconds
-    const byte diagnostics = 0;
+    const byte diagnostics = DIAGNOSTIC_ON;
 
   // Global time variables
-    uint32_t runTime = 0;                // Program loop consistent variable for time in milliseconds since power on
-    uint32_t lapTime = 0;                // Used to keep track of how long each loop takes. Useful for rate-limiting.
-    uint32_t loopTime = 0;               // Used to check speed of the loop in diagnostics mode 4
-    byte animationFPS = 32; // actually frames per 2^10 seconds. close enough to 30fps
-    int16_t rainbowDegreeTime = 64; // ms to go through 1/360 of rainbow.
+    uint64_t runTime = 0;                // Program loop consistent variable for time in microseconds since power on
+    uint64_t lapTime = 0;                // Used to keep track of how long each loop takes. Useful for rate-limiting.
+    uint64_t loopTime = 0;               // Used to check speed of the loop in diagnostics mode 4
+
+  // animation variables    E NE NW  W SW SE
+    int8_t vertical[] =   { 0,-1,-1, 0, 1, 1};
+    int8_t horizontal[] = { 2, 1,-1,-2,-1, 1};
+
+    byte animationFPS = 32; // actually frames per 2^20 microseconds. close enough to 30fps
+    int32_t rainbowDegreeTime = 65'536; // microseconds to go through 1/360 of rainbow
 
   // Button matrix and LED locations (PROD unit only)
-    #define MPLEX_1_PIN 4
-    #define MPLEX_2_PIN 5
-    #define MPLEX_4_PIN 2
-    #define MPLEX_8_PIN 3
     const byte mPin[] = { 
       MPLEX_1_PIN, MPLEX_2_PIN, MPLEX_4_PIN, MPLEX_8_PIN 
     };
-    #define COLUMN_PIN_0 6
-    #define COLUMN_PIN_1 7
-    #define COLUMN_PIN_2 8
-    #define COLUMN_PIN_3 9
-    #define COLUMN_PIN_4 10
-    #define COLUMN_PIN_5 11
-    #define COLUMN_PIN_6 12
-    #define COLUMN_PIN_7 13
-    #define COLUMN_PIN_8 14
-    #define COLUMN_PIN_9 15
     const byte cPin[] = { 
       COLUMN_PIN_0, COLUMN_PIN_1, COLUMN_PIN_2, COLUMN_PIN_3,
       COLUMN_PIN_4, COLUMN_PIN_5, COLUMN_PIN_6, 
       COLUMN_PIN_7, COLUMN_PIN_8, COLUMN_PIN_9 
     };
-    #define COLCOUNT 10
-    #define ROWCOUNT 14
-  
-    // Since MIDI only uses 7 bits, we can give greater values special meanings.
-    // (see commandPress)
-    // start CMDB in a range that won't interfere with layouts.
-    #define CMDB 192
-    #define UNUSED_NOTE 255
-
-    // LED addresses for CMD buttons. (consequencely, also the button address too)
-    #define CMDBTN_0 0
-    #define CMDBTN_1 20
-    #define CMDBTN_2 40
-    #define CMDBTN_3 60
-    #define CMDBTN_4 80
-    #define CMDBTN_5 100
-    #define CMDBTN_6 120
     const byte assignCmd[] = { 
       CMDBTN_0, CMDBTN_1, CMDBTN_2, CMDBTN_3, 
       CMDBTN_4, CMDBTN_5, CMDBTN_6
     };
-    #define CMDCOUNT 7
 
   // MIDI note layout tables overhauled procedure since v1.1
-  // FIRST, some introductory declarations
-    typedef struct {  // defines the hex-grid coordinates using a double-offset system
-      int8_t row;
-      int8_t col;
-    } coordinates;    // probably could have done this as a std::pair, but was too lazy
-    
-    enum { 
-      Right, UpRight, UpLeft, Left, DnLeft, DnRight 
-    };        // the six cardinal directions on the hex grid are 0 thru 5, counter-clockwise
-
-    enum {
-      Twelve, Seventeen, Nineteen, TwentyTwo, 
-      TwentyFour, ThirtyOne, FortyOne, FiftyThree, 
-      SeventyTwo, BohlenPierce, 
-      CarlosA, CarlosB, CarlosG
-    };       // this is supposed to help with code legibility, weird as it looks.
-            // tuning # 0 is 12-EDO, so refer to that index 0 as "Twelve"
-            // then the next tuning #1 is 17-EDO so refer to index 1 as Seventeen, etc.
-
-  // SECOND, each button is an object, of type "buttonDef"
-    typedef struct {
-      byte keyState = 0;            // binary 00 = off, 01 = just pressed, 10 = just released, 11 = held
-      coordinates coords = {0,0};   // the hexagonal coordinates
-      uint32_t timePressed = 0;     // timecode of last press
-      uint32_t LEDcolorAnim = 0;    // calculate it once and store value, to make LED playback snappier 
-      uint32_t LEDcolorPlay = 0;    // calculate it once and store value, to make LED playback snappier
-      uint32_t LEDcolorOn = 0;      // calculate it once and store value, to make LED playback snappier
-      uint32_t LEDcolorOff = 0;     // calculate it once and store value, to make LED playback snappier
-      bool animate = 0;             // hex is flagged as part of the animation in this frame, helps make animations smoother
-      int16_t steps = 0;            // number of steps from key center (semitones in 12EDO; microtones if >12EDO)
-      bool isCmd = 0;               // 0 if it's a MIDI note; 1 if it's a MIDI control cmd
-      bool inScale = 0;             // 0 if it's not in the selected scale; 1 if it is
-      byte note = UNUSED_NOTE;      // MIDI note or control parameter corresponding to this hex
-      int16_t bend;                 // in microtonal mode, the pitch bend for this note needed to be tuned correctly
-      byte channel;                 // what MIDI channel this note is playing on
-      float frequency;              // what frequency to ring on the buzzer
-      void updateKeyState(bool keyPressed) {
-        keyState = (((keyState << 1) + keyPressed) & 3);
-        if (keyState == 1) {
-          timePressed = millis();   // log the time
-        };
-      };
-      uint32_t animFrame() {     
-        if (timePressed) {          // 2^10 milliseconds is close enough to 1 second
-          return 1 + (((runTime - timePressed) * animationFPS) >> 10);
-        } else {
-          return 0;
-        };
-      };
-    } buttonDef;                    // a better C++ programmer than me would turn this into some
-                                    // fancy class definition in a header. i'm not that programmer!
 
     buttonDef h[LED_COUNT];         // a collection of all the buttons from 0 to 139
                                     // h[i] refers to the button with the LED address = i.
-
-  // THIRD, each layout can be built on the fly. used to be done
-    // separately in the ./makeLayout.py script, but not anymore.
-    // with the introduction of microtonal tunings, note that each tuning
-    // has its own list of layouts that are useful in that tuning.
-
-    typedef struct {
-      std::string name;
-      bool isPortrait;
-      byte rootHex;        // instead of "what note is button 1", "what button is the middle"
-      int8_t acrossSteps;  // defined this way to be compatible with original v1.1 firmare
-      int8_t dnLeftSteps;  // defined this way to be compatible with original v1.1 firmare
-      byte tuning;
-    } layoutDef;
-
-    layoutDef layoutOptions[] = {
-      { "Wicki-Hayden",      1, 64,   2,  -7, Twelve       },
-      { "Harmonic Table",    0, 75,  -7,   3, Twelve       },
-      { "Janko",             0, 65,  -1,  -1, Twelve       },
-      { "Gerhard",           0, 65,  -1,  -3, Twelve       },
-      { "Accordion C-sys.",  1, 75,   2,  -3, Twelve       },
-      { "Accordion B-sys.",  1, 64,   1,  -3, Twelve       },
-      { "Full Layout",       1, 65,  -1,  -9, Twelve       },
-      { "Bosanquet, 17",     0, 65,  -2,  -1, Seventeen    },
-      { "Full Layout",       1, 65,  -1,  -9, Seventeen    },
-      { "Bosanquet, 19",     0, 65,  -1,  -2, Nineteen     },
-      { "Full Layout",       1, 65,  -1,  -9, Nineteen     },
-      { "Bosanquet, 22",     0, 65,  -3,  -1, TwentyTwo    },
-      { "Full Layout",       1, 65,  -1,  -9, TwentyTwo    },
-      { "Bosanquet, 24",     0, 65,  -1,  -3, TwentyFour   },
-      { "Full Layout",       1, 65,  -1,  -9, TwentyFour   },
-      { "Bosanquet, 31",     0, 65,  -2,  -3, ThirtyOne    },
-      { "Full Layout",       1, 65,  -1,  -9, ThirtyOne    },
-      { "Bosanquet, 41",     0, 65,  -4,  -3, FortyOne     },  // forty-one #1
-      { "Gerhard, 41",       0, 65,   3, -10, FortyOne     },  // forty-one #2
-      { "Full Layout, 41",   0, 65,  -1,  -8, FortyOne     },  // forty-one #3
-      { "Wicki-Hayden, 53",  1, 64,   9, -31, FiftyThree   },
-      { "Harmonic Tbl, 53",  0, 75, -31,  14, FiftyThree   },
-      { "Bosanquet, 53",     0, 65,  -5,  -4, FiftyThree   },
-      { "Full Layout, 53",   0, 65,  -1,  -9, FiftyThree   },
-      { "Full Layout, 72",   0, 65,  -1,  -9, SeventyTwo   },
-      { "Full Layout",       1, 65,  -1,  -9, BohlenPierce },
-      { "Full Layout",       1, 65,  -1,  -9, CarlosA      },
-      { "Full Layout",       1, 65,  -1,  -9, CarlosB      },
-      { "Full Layout",       1, 65,  -1,  -9, CarlosG      }
-    };
+    byte enableMIDI = 1;
     const byte layoutCount = sizeof(layoutOptions) / sizeof(layoutDef);
+    const byte scaleCount = sizeof(scaleOptions) / sizeof(scaleDef);
 
-  // FOURTH, since we updated routine for the piezo buzzer
-    // we no longer rely on the Arduino tone() function.
-    // instead we wrote our own pulse generator using the
-    // system clock, and can pass precise frequencies
-    // up to ~12kHz. the exact frequency of each button
-    // depends on the tuning system, defined in the struct below.
-
-    typedef struct {
-      std::string name;
-      byte cycleLength; // steps before repeat
-      float stepSize;   // in cents, 100 = "normal" semitone.
-    } tuningDef;
-
-    tuningDef tuningOptions[] = {
-      // replaces the idea of const byte EDO[] = { 12, 17, 19, 22, 24, 31, 41, 53, 72 };
-      { "12 EDO",           12,  100.0 },
-      { "17 EDO",           17, 1200.0 / 17 },
-      { "19 EDO",           19, 1200.0 / 19 },
-      { "22 EDO",           22, 1200.0 / 22 },
-      { "24 EDO",           24,   50.0 },
-      { "31 EDO",           31, 1200.0 / 31 },
-      { "41 EDO",           41, 1200.0 / 41 },
-      { "53 EDO",           53, 1200.0 / 53 },
-      { "72 EDO",           72,  100.0 / 6 },
-      { "Bohlen-Pierce",    13, 1901.955 / 13 }, //
-      { "Carlos Alpha",      9, 77.965 }, //
-      { "Carlos Beta",      11, 63.833 }, //
-      { "Carlos Gamma",     20, 35.099 }
-    };
-    const byte tuningCount = sizeof(tuningOptions) / sizeof(tuningDef);
+  // Tone and Arpeggiator variables
+    oscillator synth[POLYPHONY_LIMIT]; // maximum polyphony
+    byte poly = 0; // current polyphony
+    std::queue<byte> openChannelQueue;
+    const byte attenuation[] = {67,67,48,39,34,30,28,26,24,23,22,21,20,19,18,17};
 
-    #define TONEPIN 23
-    #define TONE_SL 3
-    #define TONE_CH 1
-    #define WAVE_RESOLUTION 16
-    #define ALARM_NUM 2
-    #define ALARM_IRQ TIMER_IRQ_2
+    byte arpeggiatingNow = UNUSED_NOTE;         // if this is 255, buzzer set to off (0% duty cycle)
+    uint64_t arpeggiateTime = 0;         // Used to keep track of when this note started buzzin
+    uint32_t arpeggiateLength = 65'536;   // in microseconds
 
-    typedef struct {
-      std::string name;
-      byte lvl[WAVE_RESOLUTION];
-    } waveDef;
-    waveDef wf[] = { // from [0..129]
-      {"Square",  {0,0,0,0,0,0,0,0,129,129,129,129,129,129,129,129}},
-      {"Saw",  {0,9,17,26,34,43,52,60,69,77,86,95,103,112,120,129}},
-      {"3iangle",  {0,16,32,48,65,81,97,113,129,113,97,81,65,48,32,16}},
-      {"Sine",  {0,5,19,40,65,89,110,124,129,124,110,89,65,40,19,5}}
-    };
-    byte wfTick = 0;
-    byte wfLvl = 0;
+    byte scaleLock = 0;
+    byte perceptual = 1;
 
-  // Tone and Arpeggiator variables
-    uint32_t microSecondsPerCycle = 1000000;
-    uint32_t microSecondsPerTick = microSecondsPerCycle / WAVE_RESOLUTION;
-    byte currentHexBuzzing = 255;         // if this is 255, buzzer set to off (0% duty cycle)
-    uint32_t currentBuzzTime = 0;         // Used to keep track of when this note started buzzin
-    uint32_t arpeggiateLength = 60;       //
+    int velWheelSpeed = 8;
+    int modWheelSpeed = 8;
+    int pbWheelSpeed = 1024;
 
-  // Pitch bend and mod wheel variables overhauled to use an internal emulation structure as follows
-    const uint16_t ccMsgCoolDown = 32; // milliseconds between steps
-    typedef struct {
-      byte* topBtn;
-      byte* midBtn;
-      byte* botBtn;
-      int16_t minValue;
-      int16_t maxValue;
-      uint16_t stepValue;
-      int16_t defValue;
-      int16_t curValue;
-      int16_t targetValue;
-      uint32_t timeLastChanged;
-      void setTargetValue() {
-        if (*midBtn >> 1) { // middle button toggles target (0) vs. step (1) mode
-          int16_t temp = curValue;
-              if (*topBtn == 1)     {temp += stepValue;};
-              if (*botBtn == 1)     {temp -= stepValue;};
-              if (temp > maxValue)  {temp  = maxValue;} 
-          else if (temp <= minValue) {temp  = minValue;};
-          targetValue = temp;
-        } else {
-          switch (((*topBtn >> 1) << 1) + (*botBtn >> 1)) {
-            case 0b10:   targetValue = maxValue;     break;
-            case 0b11:   targetValue = defValue;     break;
-            case 0b01:   targetValue = minValue;     break;
-            default:     targetValue = curValue;     break;
-          };
-        };
-      };
-      bool updateValue() {
-        int16_t temp = targetValue - curValue;
-        if (temp != 0) {
-          if ((runTime - timeLastChanged) >= ccMsgCoolDown) {
-            timeLastChanged = runTime;
-            if (abs(temp) < stepValue) {
-              curValue = targetValue;
-            } else {
-              curValue = curValue + (stepValue * (temp / abs(temp)));
-            };
-            return 1;
-          } else {
-            return 0;
-          };
-        } else {
-          return 0;
-        };
-      };   
-    } wheelDef;
-    wheelDef modWheel = {
-      &h[assignCmd[4]].keyState,
-      &h[assignCmd[5]].keyState,
-      &h[assignCmd[6]].keyState,
-      0, 127, 8,
-      0, 0, 0
+    wheelDef modWheel = { false, false, // standard mode, not sticky
+      &h[assignCmd[4]].btnState, &h[assignCmd[5]].btnState, &h[assignCmd[6]].btnState,
+      0, 127, &modWheelSpeed, 0, 0, 0, 0
     }; 
-    wheelDef pbWheel =  {
-      &h[assignCmd[4]].keyState,
-      &h[assignCmd[5]].keyState,
-      &h[assignCmd[6]].keyState,
-      -8192, 8192, 1024,
-      0, 0, 0
+    wheelDef pbWheel =  { false, false, // standard mode, not sticky
+      &h[assignCmd[4]].btnState, &h[assignCmd[5]].btnState, &h[assignCmd[6]].btnState,
+      -8192, 8191, &pbWheelSpeed, 0, 0, 0, 0
     };
-    wheelDef velWheel = {
-      &h[assignCmd[0]].keyState,
-      &h[assignCmd[1]].keyState,
-      &h[assignCmd[2]].keyState,
-      0, 127, 8,  
-      96, 96, 96
+    wheelDef velWheel = { false, true, // standard mode, sticky
+      &h[assignCmd[0]].btnState, &h[assignCmd[1]].btnState, &h[assignCmd[2]].btnState,
+      0, 127, &velWheelSpeed, 96, 96, 96, 0
     };
     bool toggleWheel = 0; // 0 for mod, 1 for pb
 
-  /* Sequencer mode has not yet been restored
-
-    // Variables for sequencer mode
-    // Sequencer mode probably needs some love before it will be useful/usable.
-    // The device is held vertically, and two rows create a "lane".
-    // the first 8 buttons from each row are the steps (giving you 4 measures with quarter-note precision)
-    // The extra 3 (4?) buttons are for bank switching, muting, and solo-ing
-    typedef struct {
-      // The first 16 are for bank 0, and the second 16 are for bank 1.
-      bool steps[32] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
-      bool bank = 0;
-      int state = 0;  // TODO: change to enum: normal, mute, solo, mute&solo
-      int instrument = 0; // What midi note this lane will send to the computer.
-    } Lane;
-    #define STATE_MUTE 1
-    #define STATE_SOLO 2
-
-    #define NLANES 7
-    Lane lanes[NLANES];
-
-    int sequencerStep = 0;  // 0 - 31
-
-    // You have to push a button to switch modes
-    bool sequencerMode = 0;
-
-    // THESE CAN BE USED TO RESET THE SEQUENCE POSITION
-    // void handleStart(void);
-    // void handleContinue(void);
-    // void handleStop(void);
-
-    // THIS WILL BE USED FOR THE SEQUENCER CLOCK (24 frames per quarter note)
-    // void handleTimeCodeQuarterFrame(byte data);
-    // We should be able to adjust the division in the menu to have different sequence speeds.
-
-    void handleNoteOn(byte channel, byte pitch, byte velocity) {
-      // Rosegarden sends its metronome this way. Using for testing...
-      if (1 == sequencerMode && 10 == channel && 100 == pitch) {
-        sequencerPlayNextNote();
-      }
-    }
-
-    */
-
-  // ====== initialize list of supported scales / modes / raga / maqam
-
-    typedef struct {
-      std::string name;
-      byte tuning;
-      byte step[16]; // 16 bytes = 128 bits, 1 = in scale; 0 = not
-    } scaleDef;
-    scaleDef scaleOptions[] = {
-      { "None",              255,        { 255,        255,         255,255,255,255,255,255,255,255,255,255,255,255,255,255} },
-      { "Major",             Twelve,     { 0b10101101, 0b0101'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
-      { "Minor, natural",    Twelve,     { 0b10110101, 0b1010'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
-      { "Minor, melodic",    Twelve,     { 0b10110101, 0b0101'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
-      { "Minor, harmonic",   Twelve,     { 0b10110101, 0b1001'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
-      { "Pentatonic, major", Twelve,     { 0b10101001, 0b0100'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
-      { "Pentatonic, minor", Twelve,     { 0b10010101, 0b0010'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
-      { "Blues",             Twelve,     { 0b10010111, 0b0010'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
-      { "Double Harmonic",   Twelve,     { 0b11001101, 0b1001'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
-      { "Phrygian",          Twelve,     { 0b11010101, 0b1010'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
-      { "Phrygian Dominant", Twelve,     { 0b11001101, 0b1010'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
-      { "Dorian",            Twelve,     { 0b10110101, 0b0110'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
-      { "Lydian",            Twelve,     { 0b10101011, 0b0101'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
-      { "Lydian Dominant",   Twelve,     { 0b10101011, 0b0110'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
-      { "Mixolydian",        Twelve,     { 0b10101101, 0b0110'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
-      { "Locrian",           Twelve,     { 0b11010110, 0b1010'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
-      { "Whole tone",        Twelve,     { 0b10101010, 0b1010'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
-      { "Octatonic",         Twelve,     { 0b10110110, 0b1101'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
-      { "Rast maqam",        TwentyFour, { 0b10001001, 0b00100010, 0b00101100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
-      { "Rast makam",        FiftyThree, { 0b10000000, 0b01000000, 0b01000010, 0b00000001,
-                                                  0b00000000, 0b10001000, 0b10000'000, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
-    };
-    const byte scaleCount = sizeof(scaleOptions) / sizeof(scaleDef);
-
-  // ====== initialize key notation and coloring routines
-
-    enum { DARK = 0, VeryDIM = 1, DIM = 32, BRIGHT = 127, VeryBRIGHT = 255 };
-    enum { GRAY = 0, DULL = 127, VIVID = 255 };
-    enum colors       { W,    R,    O,    Y,    L,     G,     C,     B,     I,     P,     M,
-                              r,    o,    y,    l,     g,     c,     b,     i,     p,     m     };
-    float hueCode[] = { 0.0,  0.0,  36.0, 72.0, 108.0, 144.0, 180.0, 216.0, 252.0, 288.0, 324.0,
-                              0.0,  36.0, 72.0, 108.0, 144.0, 180.0, 216.0, 252.0, 288.0, 324.0  };
-    byte satCode[] =  { GRAY, VIVID,VIVID,VIVID,VIVID, VIVID, VIVID, VIVID, VIVID, VIVID, VIVID, 
-                              DULL, DULL, DULL, DULL,  DULL,  DULL,  DULL,  DULL,  DULL,  DULL  };
-
-    typedef struct {
-      std::string name;
-      byte tuning;
-      int8_t offset;      // steps from constant A4 to that key class 
-      colors tierColor;
-    } keyDef;
-    keyDef keyOptions[] = {
-      // 12 EDO, whole tone = 2, #/b = 1
-        { " C        (B#)", Twelve, -9, W },
-        { " C# / Db",       Twelve, -8, i },
-        { "      D",        Twelve, -7, W },
-        { "      D# / Eb",  Twelve, -6, i },
-        { "     (Fb)  E",   Twelve, -5, W },
-        { "      F   (E#)", Twelve, -4, c },
-        { " Gb / F#",       Twelve, -3, I },
-        { " G",             Twelve, -2, c },
-        { " G# / Ab",       Twelve, -1, I },
-        { "      A",        Twelve,  0, c },
-        { "      A# / Bb",  Twelve,  1, I },
-        { "(Cb)       B",   Twelve,  2, c },
-      // 17 EDO, whole tone = 3, #/b = 2, +/d = 1
-        { " C        (B+)", Seventeen,  -13, W },
-        { " C+ / Db / B#",  Seventeen,  -12, R },
-        { " C# / Dd",       Seventeen,  -11, I },
-        { "      D",        Seventeen,  -10, W },
-        { "      D+ / Eb",  Seventeen,   -9, R },
-        { " Fb / D# / Ed",  Seventeen,   -8, I },
-        { "(Fd)       E",   Seventeen,   -7, W },
-        { " F        (E+)", Seventeen,   -6, W },
-        { " F+ / Gb / E#",  Seventeen,   -5, R },
-        { " F# / Gd",       Seventeen,   -4, I },
-        { "      G",        Seventeen,   -3, W },
-        { "      G+ / Ab",  Seventeen,   -2, R },
-        { "      G# / Ad",  Seventeen,   -1, I },
-        { "           A",   Seventeen,    0, W },
-        { "      Bb / A+",  Seventeen,    1, R },
-        { " Cb / Bd / A#",  Seventeen,    2, I },
-        { "(Cd)  B"      ,  Seventeen,    3, W },
-      // 19 EDO, whole tone = 3, #/b = 1
-        { " C",       Nineteen, -14, W },
-        { " C#",      Nineteen, -13, R },
-        { " Db",      Nineteen, -12, I },
-        { " D",       Nineteen, -11, W },
-        { " D#",      Nineteen, -10, R },
-        { " Eb",      Nineteen,  -9, I },
-        { " E",       Nineteen,  -8, W },
-        { " E# / Fb", Nineteen,  -7, m },
-        { "      F",  Nineteen,  -6, W },
-        { "      F#", Nineteen,  -5, R },
-        { "      Gb", Nineteen,  -4, I },
-        { "      G",  Nineteen,  -3, W },
-        { "      G#", Nineteen,  -2, R },
-        { "      Ab", Nineteen,  -1, I },
-        { "      A",  Nineteen,   0, W },
-        { "      A#", Nineteen,   1, R },
-        { "      Bb", Nineteen,   2, I },
-        { "      B",  Nineteen,   3, W },
-        { " Cb / B#", Nineteen,   4, m },
-      // 22 EDO, whole tone = 4, #/b = 3, ^/v = 1
-        { "  C         (^B)", TwentyTwo, -17, W },
-        { " ^C  /  Db / vB#", TwentyTwo, -16, l },
-        { " vC# / ^Db /  B#", TwentyTwo, -15, C },
-        { "  C# / vD",        TwentyTwo, -14, i },
-        { "        D",        TwentyTwo, -13, W },
-        { "       ^D  /  Eb", TwentyTwo, -12, l },
-        { "  Fb / vD# / ^Eb", TwentyTwo, -11, C },
-        { " ^Fb /  D# / vE",  TwentyTwo, -10, i },
-        { "(vF)          E",  TwentyTwo,  -9, W },
-        { "  F         (^E)", TwentyTwo,  -8, W },
-        { " ^F  /  Gb / vE#", TwentyTwo,  -7, l },
-        { " vF# / ^Gb /  E#", TwentyTwo,  -6, C },
-        { "  F# / vG",        TwentyTwo,  -5, i },
-        { "        G",        TwentyTwo,  -4, W },
-        { "       ^G  /  Ab", TwentyTwo,  -3, l },
-        { "       vG# / ^Ab", TwentyTwo,  -2, C },
-        { "        G# / vA",  TwentyTwo,  -1, i },
-        { "              A",  TwentyTwo,   0, W },
-        { "        Bb / ^A",  TwentyTwo,   1, l },
-        { "  Cb / ^Bb / vA#", TwentyTwo,   2, C },
-        { " ^Cb / vB  /  A#", TwentyTwo,   3, i },
-        { "(vC)    B",        TwentyTwo,   4, W },
-      // 24 EDO, whole tone = 4, #/b = 2, +/d = 1
-        { " C  / B#", TwentyFour, -18, W },
-        { " C+",      TwentyFour, -17, r },
-        { " C# / Db", TwentyFour, -16, I },
-        { "      Dd", TwentyFour, -15, g },
-        { "      D",  TwentyFour, -14, W },
-        { "      D+", TwentyFour, -13, r },
-        { " Eb / D#", TwentyFour, -12, I },
-        { " Ed",      TwentyFour, -11, g },
-        { " E  / Fb", TwentyFour, -10, W },
-        { " E+ / Fd", TwentyFour,  -9, y },
-        { " E# / F",  TwentyFour,  -8, W },
-        { "      F+", TwentyFour,  -7, r },
-        { " Gb / F#", TwentyFour,  -6, I },
-        { " Gd",      TwentyFour,  -5, g },
-        { " G",       TwentyFour,  -4, W },
-        { " G+",      TwentyFour,  -3, r },
-        { " G# / Ab", TwentyFour,  -2, I },
-        { "      Ad", TwentyFour,  -1, g },
-        { "      A",  TwentyFour,   0, W },
-        { "      A+", TwentyFour,   1, r },
-        { " Bb / A#", TwentyFour,   2, I },
-        { " Bd",      TwentyFour,   3, g },
-        { " B  / Cb", TwentyFour,   4, W },
-        { " B+ / Cd", TwentyFour,   5, y },
-      // 31 EDO, whole tone = 5, #/b = 2, +/d = 1
-        { " C",       ThirtyOne, -23, W },
-        { " C+",      ThirtyOne, -22, R },
-        { " C#",      ThirtyOne, -21, Y },
-        { " Db",      ThirtyOne, -20, C },
-        { " Dd",      ThirtyOne, -19, I },
-        { " D",       ThirtyOne, -18, W },
-        { " D+",      ThirtyOne, -17, R },
-        { " D#",      ThirtyOne, -16, Y },
-        { " Eb",      ThirtyOne, -15, C },
-        { " Ed",      ThirtyOne, -14, I },
-        { " E",       ThirtyOne, -13, W },
-        { " E+ / Fb", ThirtyOne, -12, L },
-        { " E# / Fd", ThirtyOne, -11, M },
-        { "      F",  ThirtyOne, -10, W },
-        { "      F+", ThirtyOne,  -9, R },
-        { "      F#", ThirtyOne,  -8, Y },
-        { "      Gb", ThirtyOne,  -7, C },
-        { "      Gd", ThirtyOne,  -6, I },
-        { "      G",  ThirtyOne,  -5, W },
-        { "      G+", ThirtyOne,  -4, R },
-        { "      G#", ThirtyOne,  -3, Y },
-        { "      Ab", ThirtyOne,  -2, C },
-        { "      Ad", ThirtyOne,  -1, I },
-        { "      A",  ThirtyOne,   0, W },
-        { "      A+", ThirtyOne,   1, R },
-        { "      A#", ThirtyOne,   2, Y },
-        { "      Bb", ThirtyOne,   3, C },
-        { "      Bd", ThirtyOne,   4, I },
-        { "      B",  ThirtyOne,   5, W },
-        { " Cb / B+", ThirtyOne,   6, L },
-        { " Cd / B#", ThirtyOne,   7, M },
-      // 41 EDO, whole tone = 7, #/b = 4, +/d = 2, ^/v = 1
-        { "  C         (vB#)", FortyOne, -31, W },
-        { " ^C        /  B#",  FortyOne, -30, c },
-        { "  C+ ",             FortyOne, -29, O },
-        { " vC# /  Db",        FortyOne, -28, I },
-        { "  C# / ^Db",        FortyOne, -27, R },
-        { "        Dd",        FortyOne, -26, B },
-        { "       vD",         FortyOne, -25, y },
-        { "        D",         FortyOne, -24, W },
-        { "       ^D",         FortyOne, -23, c },
-        { "        D+",        FortyOne, -22, O },
-        { "       vD# /  Eb",  FortyOne, -21, I },
-        { "        D# / ^Eb",  FortyOne, -20, R },
-        { "              Ed",  FortyOne, -19, B },
-        { "             vE",   FortyOne, -18, y },
-        { "      (^Fb)   E",   FortyOne, -17, W },
-        { "        Fd / ^E",   FortyOne, -16, c },
-        { "       vF  /  E+",  FortyOne, -15, y },
-        { "        F   (vE#)", FortyOne, -14, W },
-        { "       ^F  /  E#",  FortyOne, -13, c },
-        { "        F+",        FortyOne, -12, O },
-        { "  Gb / vF#",        FortyOne, -11, I },
-        { " ^Gb /  F#",        FortyOne, -10, R },
-        { "  Gd",              FortyOne,  -9, B },
-        { " vG",               FortyOne,  -8, y },
-        { "  G",               FortyOne,  -7, W },
-        { " ^G",               FortyOne,  -6, c },
-        { "  G+",              FortyOne,  -5, O },
-        { " vG# /  Ab",        FortyOne,  -4, I },
-        { "  G# / ^Ab",        FortyOne,  -3, R },
-        { "        Ad",        FortyOne,  -2, B },
-        { "       vA",         FortyOne,  -1, y },
-        { "        A",         FortyOne,   0, W },
-        { "       ^A",         FortyOne,   1, c },
-        { "        A+",        FortyOne,   2, O },
-        { "       vA# /  Bb",  FortyOne,   3, I },
-        { "        A# / ^Bb",  FortyOne,   4, R },
-        { "              Bd",  FortyOne,   5, B },
-        { "             vB",   FortyOne,   6, y },
-        { "      (^Cb)   B",   FortyOne,   7, W },
-        { "        Cd / ^B",   FortyOne,   8, c },
-        { "       vC  /  B+",  FortyOne,   9, y },
-      // 53 EDO, whole tone = 9, #/b = 5, >/< = 2, ^/v = 1
-        { "  C         (vB#)", FiftyThree, -40, W },
-        { " ^C     /     B#",  FiftyThree, -39, c },
-        { " >C  / <Db",        FiftyThree, -38, l },
-        { " <C# / vDb",        FiftyThree, -37, O },
-        { " vC# /  Db",        FiftyThree, -36, I },
-        { "  C# / ^Db",        FiftyThree, -35, R },
-        { " ^C# / >Db",        FiftyThree, -34, B },
-        { " >C# / <D",         FiftyThree, -33, g },
-        { "       vD",         FiftyThree, -32, y },
-        { "        D",         FiftyThree, -31, W },
-        { "       ^D",         FiftyThree, -30, c },
-        { "       >D  / <Eb",  FiftyThree, -29, l },
-        { "       <D# / vEb",  FiftyThree, -28, O },
-        { "       vD# /  Eb",  FiftyThree, -27, I },
-        { "        D# / ^Eb",  FiftyThree, -26, R },
-        { "       ^D# / >Eb",  FiftyThree, -25, B },
-        { "       >D# / <E",   FiftyThree, -24, g },
-        { "  Fb    /    vE",   FiftyThree, -23, y },
-        { "(^Fb)         E",   FiftyThree, -22, W },
-        { "(>Fb)        ^E",   FiftyThree, -21, c },
-        { " <F     /    >E",   FiftyThree, -20, G },
-        { " vF         (<E#)", FiftyThree, -19, y },
-        { "  F         (vE#)", FiftyThree, -18, W },
-        { " ^F     /     E#",  FiftyThree, -17, c },
-        { " >F  / <Gb",        FiftyThree, -16, l },
-        { " <F# / vGb",        FiftyThree, -15, O },
-        { " vF# /  Gb",        FiftyThree, -14, I },
-        { "  F# / ^Gb",        FiftyThree, -13, R },
-        { " ^F# / >Gb",        FiftyThree, -12, B },
-        { " >F# / <G",         FiftyThree, -11, g },
-        { "       vG",         FiftyThree, -10, y },
-        { "        G",         FiftyThree,  -9, W },
-        { "       ^G",         FiftyThree,  -8, c },
-        { "       >G  / <Ab",  FiftyThree,  -7, l },
-        { "       <G# / vAb",  FiftyThree,  -6, O },
-        { "       vG# /  Ab",  FiftyThree,  -5, I },
-        { "        G# / ^Ab",  FiftyThree,  -4, R },
-        { "       ^G# / >Ab",  FiftyThree,  -3, B },
-        { "       >G# / <A",   FiftyThree,  -2, g },
-        { "             vA",   FiftyThree,  -1, y },
-        { "              A",   FiftyThree,   0, W },
-        { "             ^A",   FiftyThree,   1, c },
-        { "       <Bb / >A",   FiftyThree,   2, l },
-        { "       vBb / <A#",  FiftyThree,   3, O },
-        { "        Bb / vA#",  FiftyThree,   4, I },
-        { "       ^Bb /  A#",  FiftyThree,   5, R },
-        { "       >Bb / ^A#",  FiftyThree,   6, B },
-        { "       <B  / >A#",  FiftyThree,   7, g },
-        { "  Cb / vB",         FiftyThree,   8, y },
-        { "(^Cb)   B",         FiftyThree,   9, W },
-        { "(>Cb)  ^B",         FiftyThree,  10, c },
-        { " <C  / >B",         FiftyThree,  11, G },
-        { " vC   (<B#)",       FiftyThree,  12, y },
-      // 72 EDO, whole tone = 12, #/b = 6, +/d = 3, ^/v = 1
-        { "  C    (B#)", SeventyTwo, -54, W },
-        { " ^C",         SeventyTwo, -53, g },
-        { " vC+",        SeventyTwo, -52, r },
-        { "  C+",        SeventyTwo, -51, p },
-        { " ^C+",        SeventyTwo, -50, b },
-        { " vC#",        SeventyTwo, -49, y },
-        { "  C# /  Db",  SeventyTwo, -48, I },
-        { " ^C# / ^Db",  SeventyTwo, -47, g },
-        { "       vDd",  SeventyTwo, -46, r },
-        { "        Dd",  SeventyTwo, -45, p },
-        { "       ^Dd",  SeventyTwo, -44, b },
-        { "       vD",   SeventyTwo, -43, y },
-        { "        D",   SeventyTwo, -42, W },
-        { "       ^D",   SeventyTwo, -41, g },
-        { "       vD+",  SeventyTwo, -40, r },
-        { "        D+",  SeventyTwo, -39, p },
-        { "       ^D+",  SeventyTwo, -38, b },
-        { " vEb / vD#",  SeventyTwo, -37, y },
-        { "  Eb /  D#",  SeventyTwo, -36, I },
-        { " ^Eb / ^D#",  SeventyTwo, -35, g },
-        { " vEd",        SeventyTwo, -34, r },
-        { "  Ed",        SeventyTwo, -33, p },
-        { " ^Ed",        SeventyTwo, -32, b },
-        { " vE   (vFb)", SeventyTwo, -31, y },
-        { "  E    (Fb)", SeventyTwo, -30, W },
-        { " ^E   (^Fb)", SeventyTwo, -29, g },
-        { " vE+ / vFd",  SeventyTwo, -28, r },
-        { "  E+ /  Fd",  SeventyTwo, -27, p },
-        { " ^E+ / ^Fd",  SeventyTwo, -26, b },
-        { "(vE#)  vF",   SeventyTwo, -25, y },
-        { " (E#)   F",   SeventyTwo, -24, W },
-        { "(^E#)  ^F",   SeventyTwo, -23, g },
-        { "       vF+",  SeventyTwo, -22, r },
-        { "        F+",  SeventyTwo, -21, p },
-        { "       ^F+",  SeventyTwo, -20, b },
-        { " vGb / vF#",  SeventyTwo, -19, y },
-        { "  Gb /  F#",  SeventyTwo, -18, I },
-        { " ^Gb / ^F#",  SeventyTwo, -17, g },
-        { " vGd",        SeventyTwo, -16, r },
-        { "  Gd",        SeventyTwo, -15, p },
-        { " ^Gd",        SeventyTwo, -14, b },
-        { " vG",         SeventyTwo, -13, y },
-        { "  G",         SeventyTwo, -12, W },
-        { " ^G",         SeventyTwo, -11, g },
-        { " vG+",        SeventyTwo, -10, r },
-        { "  G+",        SeventyTwo,  -9, p },
-        { " ^G+",        SeventyTwo,  -8, b },
-        { " vG# / vAb",  SeventyTwo,  -7, y },
-        { "  G# /  Ab",  SeventyTwo,  -6, I },
-        { " ^G# / ^Ab",  SeventyTwo,  -5, g },
-        { "       vAd",  SeventyTwo,  -4, r },
-        { "        Ad",  SeventyTwo,  -3, p },
-        { "       ^Ad",  SeventyTwo,  -2, b },
-        { "       vA",   SeventyTwo,  -1, y },
-        { "        A",   SeventyTwo,   0, W },
-        { "       ^A",   SeventyTwo,   1, g },
-        { "       vA+",  SeventyTwo,   2, r },
-        { "        A+",  SeventyTwo,   3, p },
-        { "       ^A+",  SeventyTwo,   4, b },
-        { " vBb / vA#",  SeventyTwo,   5, y },
-        { "  Bb /  A#",  SeventyTwo,   6, I },
-        { " ^Bb / ^A#",  SeventyTwo,   7, g },
-        { " vBd",        SeventyTwo,   8, r },
-        { "  Bd",        SeventyTwo,   9, p },
-        { " ^Bd",        SeventyTwo,  10, b },
-        { " vB   (vCb)", SeventyTwo,  11, y },
-        { "  B    (Cb)", SeventyTwo,  12, W },
-        { " ^B   (^Cb)", SeventyTwo,  13, g },
-        { " vB+ / vCd",  SeventyTwo,  14, r },
-        { "  B+ /  Cd",  SeventyTwo,  15, p },
-        { " ^B+ / ^Cd",  SeventyTwo,  16, b },
-        { "(vB#)  vC",   SeventyTwo,  17, y },
-      //
-        { "Note 0",BohlenPierce,0,W},
-        { "Note 1",BohlenPierce,1,Y},
-        { "Note 2",BohlenPierce,2,L},
-        { "Note 3",BohlenPierce,3,G},
-        { "Note 4",BohlenPierce,4,C},
-        { "Note 5",BohlenPierce,5,B},
-        { "Note 6",BohlenPierce,6,I},
-        { "Note 7",BohlenPierce,7,P},
-        { "Note 8",BohlenPierce,8,M},
-        { "Note 9",BohlenPierce,9,R},
-        { "Note 10",BohlenPierce,10,O},
-        { "Note 11",BohlenPierce,11,g},
-        { "Note 12",BohlenPierce,12,b},
-      //
-        { "Note 0",CarlosA,0,W},
-        { "Note 1",CarlosA,1,Y},
-        { "Note 2",CarlosA,2,G},
-        { "Note 3",CarlosA,3,B},
-        { "Note 4",CarlosA,4,P},
-        { "Note 5",CarlosA,5,R},
-        { "Note 6",CarlosA,6,c},
-        { "Note 7",CarlosA,7,l},
-        { "Note 8",CarlosA,8,m},
-      //
-        { "Note 0",CarlosB,0,W},
-        { "Note 1",CarlosB,1,Y},
-        { "Note 2",CarlosB,2,L},
-        { "Note 3",CarlosB,3,G},
-        { "Note 4",CarlosB,4,C},
-        { "Note 5",CarlosB,5,B},
-        { "Note 6",CarlosB,6,I},
-        { "Note 7",CarlosB,7,P},
-        { "Note 8",CarlosB,8,M},
-        { "Note 9",CarlosB,9,R},
-        { "Note 10",CarlosB,10,O},
-      //
-        { "Note 0",CarlosG,0,Y},
-        { "Note 1",CarlosG,1,y},
-        { "Note 2",CarlosG,2,L},
-        { "Note 3",CarlosG,3,l},
-        { "Note 4",CarlosG,4,G},
-        { "Note 5",CarlosG,5,g},
-        { "Note 6",CarlosG,6,C},
-        { "Note 7",CarlosG,7,c},
-        { "Note 8",CarlosG,8,B},
-        { "Note 9",CarlosG,9,b},
-        { "Note 10",CarlosG,10,I},
-        { "Note 11",CarlosG,11,i},
-        { "Note 12",CarlosG,12,P},
-        { "Note 13",CarlosG,13,p},
-        { "Note 14",CarlosG,14,M},
-        { "Note 15",CarlosG,15,m},
-        { "Note 16",CarlosG,16,R},
-        { "Note 17",CarlosG,17,r},
-        { "Note 18",CarlosG,18,O},
-        { "Note 19",CarlosG,19,o},
-
-    };
-    const int keyCount = sizeof(keyOptions) / sizeof(keyDef);
-
   // MENU SYSTEM SETUP //
     // Create menu page object of class GEMPage. Menu page holds menu items (GEMItem) and represents menu level.
     // Menu can have multiple menu pages (linked to each other) with multiple menu items each
 
     GEMPage  menuPageMain("HexBoard MIDI Controller");
     GEMPage  menuPageTuning("Tuning");
-    GEMPage  menuPageLayout("Layout");
-    GEMPage  menuPageScales("Scales");
-    GEMPage  menuPageKeys("Keys");
-
+    GEMItem  menuTuningBack("<< Back", menuPageMain);
     GEMItem  menuGotoTuning("Tuning", menuPageTuning);
-    GEMItem* menuItemTuning[tuningCount]; // dynamically generate item based on tunings
-
+    GEMPage  menuPageLayout("Layout");
     GEMItem  menuGotoLayout("Layout", menuPageLayout); 
-    GEMItem* menuItemLayout[layoutCount]; // dynamically generate item based on presets
-
+    GEMItem  menuLayoutBack("<< Back", menuPageMain);
+    GEMPage  menuPageScales("Scales");
     GEMItem  menuGotoScales("Scales", menuPageScales); 
-    GEMItem* menuItemScales[scaleCount];  // dynamically generate item based on presets and if allowed in given EDO tuning
-
-    GEMItem  menuGotoKeys("Keys",     menuPageKeys);
-    GEMItem* menuItemKeys[keyCount];   // dynamically generate item based on presets
-
-    byte scaleLock = 0;
-    byte perceptual = 1;
-    void resetHexLEDs();
-    byte enableMIDI = 1;
-    byte MPE = 0; // microtonal mode. if zero then attempt to self-manage multiple channels. 
-                  // if one then on certain synths that are MPE compatible will send in that mode.
-    void prepMIDIforMicrotones();
-    SelectOptionByte optionByteYesOrNo[] =  { { "No"     , 0 },       
-                                              { "Yes"    , 1 } };
+    GEMItem  menuScalesBack("<< Back", menuPageMain);
+    GEMPage  menuPageControl("Control wheel");
+    GEMItem  menuGotoControl("Control wheel", menuPageControl);
+    GEMItem  menuControlBack("<< Back", menuPageMain);
+    
+    // the following get initialized in the setup() routine.
+    GEMItem* menuItemTuning[TUNINGCOUNT];       
+    GEMItem* menuItemLayout[layoutCount];  
+    GEMItem* menuItemScales[scaleCount];       
+    GEMSelect* selectKey[TUNINGCOUNT];         
+    GEMItem* menuItemKeys[TUNINGCOUNT];       
+
+    void resetHexLEDs(); // forward-declaration
+    SelectOptionByte optionByteYesOrNo[] =  { { "No", 0 }, { "Yes" , 1 } };
     GEMSelect selectYesOrNo( sizeof(optionByteYesOrNo)  / sizeof(SelectOptionByte), optionByteYesOrNo);
-    GEMItem  menuItemScaleLock( "Scale lock?",   scaleLock,     selectYesOrNo);
-    GEMItem  menuItemPercep(    "Fix color:",    perceptual,    selectYesOrNo, resetHexLEDs);
-    GEMItem  menuItemMIDI(      "Enable MIDI:",  enableMIDI,    selectYesOrNo);
-    GEMItem  menuItemMPE(       "MPE Mode:",     MPE,           selectYesOrNo, prepMIDIforMicrotones);
-
-    byte playbackMode = 2;
-    SelectOptionByte optionBytePlayback[] = { { "Off"    , 0 },
-                                              { "Mono"   , 1 }, 
-                                              { "Arp'gio", 2 } };
+    GEMItem  menuItemScaleLock( "Scale lock?", scaleLock, selectYesOrNo);
+    GEMItem  menuItemPercep( "Fix color:", perceptual, selectYesOrNo, resetHexLEDs);
+    
+    byte playbackMode = BUZZ_POLY;
+    SelectOptionByte optionBytePlayback[] = { { "Off", BUZZ_OFF }, { "Mono", BUZZ_MONO }, { "Arp'gio", BUZZ_ARPEGGIO }, { "Poly", BUZZ_POLY } };
     GEMSelect selectPlayback(sizeof(optionBytePlayback) / sizeof(SelectOptionByte), optionBytePlayback);
     GEMItem  menuItemPlayback(  "Buzzer:",       playbackMode,  selectPlayback);
 
-    byte colorMode = 1;
-    SelectOptionByte optionByteColor[] =    { { "Rainbow", 0 },
-                                              { "Tiered" , 1 } };
-    GEMSelect selectColor(   sizeof(optionByteColor)    / sizeof(SelectOptionByte), optionByteColor);
-    GEMItem  menuItemColor(     "Color mode:",   colorMode,     selectColor,   resetHexLEDs);
-
-    enum { NoAnim, StarAnim, SplashAnim, OrbitAnim, OctaveAnim, NoteAnim };
-    byte animationType = NoAnim;
-    SelectOptionByte optionByteAnimate[] =  { { "None"   , NoAnim }, 
-                                              { "Octave" , OctaveAnim},   
-                                              { "By Note", NoteAnim},   
-                                              { "Star"   , StarAnim},   
-                                              { "Splash" , SplashAnim},   
-                                              { "Orbit"  , OrbitAnim} };
+    void changeTranspose(); // forward-declaration
+    int transposeSteps = 0;
+    // doing this long-hand because the STRUCT has problems accepting string conversions of numbers for some reason
+    SelectOptionInt optionIntTransposeSteps[] = {
+      {"-127",-127},{"-126",-126},{"-125",-125},{"-124",-124},{"-123",-123},{"-122",-122},{"-121",-121},{"-120",-120},{"-119",-119},{"-118",-118},{"-117",-117},{"-116",-116},{"-115",-115},{"-114",-114},{"-113",-113},
+      {"-112",-112},{"-111",-111},{"-110",-110},{"-109",-109},{"-108",-108},{"-107",-107},{"-106",-106},{"-105",-105},{"-104",-104},{"-103",-103},{"-102",-102},{"-101",-101},{"-100",-100},{"- 99",- 99},{"- 98",- 98},
+      {"- 97",- 97},{"- 96",- 96},{"- 95",- 95},{"- 94",- 94},{"- 93",- 93},{"- 92",- 92},{"- 91",- 91},{"- 90",- 90},{"- 89",- 89},{"- 88",- 88},{"- 87",- 87},{"- 86",- 86},{"- 85",- 85},{"- 84",- 84},{"- 83",- 83},
+      {"- 82",- 82},{"- 81",- 81},{"- 80",- 80},{"- 79",- 79},{"- 78",- 78},{"- 77",- 77},{"- 76",- 76},{"- 75",- 75},{"- 74",- 74},{"- 73",- 73},{"- 72",- 72},{"- 71",- 71},{"- 70",- 70},{"- 69",- 69},{"- 68",- 68},
+      {"- 67",- 67},{"- 66",- 66},{"- 65",- 65},{"- 64",- 64},{"- 63",- 63},{"- 62",- 62},{"- 61",- 61},{"- 60",- 60},{"- 59",- 59},{"- 58",- 58},{"- 57",- 57},{"- 56",- 56},{"- 55",- 55},{"- 54",- 54},{"- 53",- 53},
+      {"- 52",- 52},{"- 51",- 51},{"- 50",- 50},{"- 49",- 49},{"- 48",- 48},{"- 47",- 47},{"- 46",- 46},{"- 45",- 45},{"- 44",- 44},{"- 43",- 43},{"- 42",- 42},{"- 41",- 41},{"- 40",- 40},{"- 39",- 39},{"- 38",- 38},
+      {"- 37",- 37},{"- 36",- 36},{"- 35",- 35},{"- 34",- 34},{"- 33",- 33},{"- 32",- 32},{"- 31",- 31},{"- 30",- 30},{"- 29",- 29},{"- 28",- 28},{"- 27",- 27},{"- 26",- 26},{"- 25",- 25},{"- 24",- 24},{"- 23",- 23},
+      {"- 22",- 22},{"- 21",- 21},{"- 20",- 20},{"- 19",- 19},{"- 18",- 18},{"- 17",- 17},{"- 16",- 16},{"- 15",- 15},{"- 14",- 14},{"- 13",- 13},{"- 12",- 12},{"- 11",- 11},{"- 10",- 10},{"-  9",-  9},{"-  8",-  8},
+      {"-  7",-  7},{"-  6",-  6},{"-  5",-  5},{"-  4",-  4},{"-  3",-  3},{"-  2",-  2},{"-  1",-  1},{"+/-0",   0},{"+  1",   1},{"+  2",   2},{"+  3",   3},{"+  4",   4},{"+  5",   5},{"+  6",   6},{"+  7",   7},
+      {"+  8",   8},{"+  9",   9},{"+ 10",  10},{"+ 11",  11},{"+ 12",  12},{"+ 13",  13},{"+ 14",  14},{"+ 15",  15},{"+ 16",  16},{"+ 17",  17},{"+ 18",  18},{"+ 19",  19},{"+ 20",  20},{"+ 21",  21},{"+ 22",  22},
+      {"+ 23",  23},{"+ 24",  24},{"+ 25",  25},{"+ 26",  26},{"+ 27",  27},{"+ 28",  28},{"+ 29",  29},{"+ 30",  30},{"+ 31",  31},{"+ 32",  32},{"+ 33",  33},{"+ 34",  34},{"+ 35",  35},{"+ 36",  36},{"+ 37",  37},
+      {"+ 38",  38},{"+ 39",  39},{"+ 40",  40},{"+ 41",  41},{"+ 42",  42},{"+ 43",  43},{"+ 44",  44},{"+ 45",  45},{"+ 46",  46},{"+ 47",  47},{"+ 48",  48},{"+ 49",  49},{"+ 50",  50},{"+ 51",  51},{"+ 52",  52},
+      {"+ 53",  53},{"+ 54",  54},{"+ 55",  55},{"+ 56",  56},{"+ 57",  57},{"+ 58",  58},{"+ 59",  59},{"+ 60",  60},{"+ 61",  61},{"+ 62",  62},{"+ 63",  63},{"+ 64",  64},{"+ 65",  65},{"+ 66",  66},{"+ 67",  67},
+      {"+ 68",  68},{"+ 69",  69},{"+ 70",  70},{"+ 71",  71},{"+ 72",  72},{"+ 73",  73},{"+ 74",  74},{"+ 75",  75},{"+ 76",  76},{"+ 77",  77},{"+ 78",  78},{"+ 79",  79},{"+ 80",  80},{"+ 81",  81},{"+ 82",  82},
+      {"+ 83",  83},{"+ 84",  84},{"+ 85",  85},{"+ 86",  86},{"+ 87",  87},{"+ 88",  88},{"+ 89",  89},{"+ 90",  90},{"+ 91",  91},{"+ 92",  92},{"+ 93",  93},{"+ 94",  94},{"+ 95",  95},{"+ 96",  96},{"+ 97",  97},
+      {"+ 98",  98},{"+ 99",  99},{"+100", 100},{"+101", 101},{"+102", 102},{"+103", 103},{"+104", 104},{"+105", 105},{"+106", 106},{"+107", 107},{"+108", 108},{"+109", 109},{"+110", 110},{"+111", 111},{"+112", 112},
+      {"+113", 113},{"+114", 114},{"+115", 115},{"+116", 116},{"+117", 117},{"+118", 118},{"+119", 119},{"+120", 120},{"+121", 121},{"+122", 122},{"+123", 123},{"+124", 124},{"+125", 125},{"+126", 126},{"+127", 127}
+    };
+    GEMSelect selectTransposeSteps( 255, optionIntTransposeSteps);
+    GEMItem  menuItemTransposeSteps( "Transpose:",   transposeSteps,  selectTransposeSteps, changeTranspose);
+    
+    byte colorMode = TIERED_COLOR_MODE;
+    SelectOptionByte optionByteColor[] =    { { "Rainbow", RAINBOW_MODE }, { "Tiered" , TIERED_COLOR_MODE } };
+    GEMSelect selectColor( sizeof(optionByteColor) / sizeof(SelectOptionByte), optionByteColor);
+    GEMItem  menuItemColor( "Color mode:", colorMode, selectColor, resetHexLEDs);
+
+    byte animationType = ANIMATE_NONE;
+    SelectOptionByte optionByteAnimate[] =  { { "None" , ANIMATE_NONE }, { "Octave", ANIMATE_OCTAVE },
+      { "By Note", ANIMATE_BY_NOTE }, { "Star", ANIMATE_STAR }, { "Splash" , ANIMATE_SPLASH }, { "Orbit", ANIMATE_ORBIT } };
     GEMSelect selectAnimate( sizeof(optionByteAnimate)  / sizeof(SelectOptionByte), optionByteAnimate);
-    GEMItem  menuItemAnimate(   "Animation:",    animationType, selectAnimate);
+    GEMItem  menuItemAnimate( "Animation:", animationType, selectAnimate);
 
-    byte currWave = 0;
-    SelectOptionByte optionByteWaveform[] = { { wf[0].name.c_str(), 0 },
-                                              { wf[1].name.c_str(), 1 },
-                                              { wf[2].name.c_str(), 2 },
-                                              { wf[3].name.c_str(), 3 }  };
+    byte currWave = WAVEFORM_SAW;
+    SelectOptionByte optionByteWaveform[] = { { "Square", WAVEFORM_SQUARE }, { "Saw", WAVEFORM_SAW } };
     GEMSelect selectWaveform(sizeof(optionByteWaveform) / sizeof(SelectOptionByte), optionByteWaveform);
-    GEMItem  menuItemWaveform(  "Waveform:",     currWave,      selectWaveform);
+    GEMItem  menuItemWaveform( "Waveform:", currWave, selectWaveform);
+
+    SelectOptionInt optionIntModWheel[] = { { "too slo", 1 }, { "Turtle", 2 }, { "Slow", 4 }, 
+      { "Medium",    8 }, { "Fast",     16 }, { "Cheetah",  32 }, { "Instant", 127 } };
+    GEMSelect selectModSpeed(sizeof(optionIntModWheel) / sizeof(SelectOptionInt), optionIntModWheel);
+    GEMItem  menuItemModSpeed( "Mod wheel:", modWheelSpeed, selectModSpeed);
+    GEMItem  menuItemVelSpeed( "Vel wheel:", velWheelSpeed, selectModSpeed);
+
+    SelectOptionInt optionIntPBWheel[] =  { { "too slo", 128 }, { "Turtle", 256 }, { "Slow", 512 },  
+      { "Medium", 1024 }, { "Fast", 2048 }, { "Cheetah", 4096 },  { "Instant", 16384 } };
+    GEMSelect selectPBSpeed(sizeof(optionIntPBWheel) / sizeof(SelectOptionInt), optionIntPBWheel);
+    GEMItem  menuItemPBSpeed( "PB wheel:", pbWheelSpeed, selectPBSpeed);
 
   // put all user-selectable options into a class so that down the line these can be saved and loaded.
-    typedef struct { 
+    class presetDef { 
+    public:
       std::string presetName; 
       int tuningIndex;     // instead of using pointers, i chose to store index value of each option, to be saved to a .pref or .ini or something
       int layoutIndex;
       int scaleIndex;
-      int keyIndex;
+      int keyStepsFromA; // what key the scale is in, where zero equals A.
       int transpose;
       // define simple recall functions
       tuningDef tuning() {
@@ -915,11 +252,8 @@
       scaleDef scale() {
         return scaleOptions[scaleIndex];
       };
-      keyDef key() {
-        return keyOptions[keyIndex];
-      };
       int layoutsBegin() {
-        if (tuningIndex == Twelve) {
+        if (tuningIndex == TUNING_12EDO) {
           return 0;
         } else {
           int temp = 0;
@@ -929,90 +263,25 @@
           return temp;
         };
       };
-      int keysBegin() {
-        if (tuningIndex == Twelve) {
-          return 0;
-        } else {
-          int temp = 0;
-          while (keyOptions[temp].tuning < tuningIndex) {
-            temp++;
-          };
-          return temp;
-        };
+      int keyStepsFromC() {
+        return -(tuning().spanCtoA() + keyStepsFromA);
       };
-      int findC() {
-        return keyOptions[keysBegin()].offset;
+      int pitchRelToA4(int givenStepsFromC) {
+        return givenStepsFromC - tuning().spanCtoA() + transpose;
+      };
+      int keyDegree(int givenStepsFromC) {
+        return positiveMod(givenStepsFromC + keyStepsFromC(), tuning().cycleLength);
       };
-    } presetDef;
-    presetDef current = {
-      "Default", 
-      Twelve,     // see the relevant enum{} statement
-      0,          // default to the first layout, wicki hayden
-      0,          // default to using no scale (chromatic)
-      0,          // default to the key of C
-      0           // default to no transposition
     };
 
-// ====== functions
-  byte byteLerp(byte xOne, byte xTwo, float yOne, float yTwo, float y) {
-    float weight = (y - yOne) / (yTwo - yOne);
-    int temp = xOne + ((xTwo - xOne) * weight);
-    if (temp < xOne) {temp = xOne;};
-    if (temp > xTwo) {temp = xTwo;};
-    return temp;
-  }
-  int positiveMod(int n, int d) {
-    return (((n % d) + d) % d);
-  }
-  coordinates indexToCoord(byte x) {
-    coordinates temp;
-    temp.row = (x / 10);
-    temp.col = (2 * (x % 10)) + (temp.row & 1);
-    return temp;
-  }
-  bool hexOOB(coordinates c) {
-    return (c.row < 0) 
-        || (c.row >= ROWCOUNT)
-        || (c.col < 0) 
-        || (c.col >= (2 * COLCOUNT))
-        || ((c.col + c.row) & 1);
-  }
-  byte coordToIndex(coordinates c) {
-    if (hexOOB(c)) {
-      return 255;
-    } else {
-      return (10 * c.row) + (c.col / 2);
+    presetDef current = {
+      "Default",      // name
+      TUNING_12EDO,      // tuning
+      0,              // default to the first layout, wicki hayden
+      0,              // default to using no scale (chromatic)
+      0,              // default to the key of C, which in 12EDO is -9 steps from A.
+      0               // default to no transposition
     };
-  }
-  coordinates hexVector(byte direction, byte distance) {
-    coordinates temp;
-    int8_t vertical[]   = {0,-1,-1, 0, 1,1};
-    int8_t horizontal[] = {2, 1,-1,-2,-1,1}; 
-    temp.row = vertical[direction] * distance;
-    temp.col = horizontal[direction] * distance;
-    return temp;
-  }
-  coordinates hexOffset(coordinates a, coordinates b) {
-    coordinates temp;
-    temp.row = a.row + b.row;
-    temp.col = a.col + b.col;
-    return temp;
-  }
-  coordinates hexDistance(coordinates origin, coordinates destination) {
-    coordinates temp;
-    temp.row = destination.row - origin.row;
-    temp.col = destination.col - origin.col;
-    return temp;
-  }
-  float freqToMIDI(float Hz) {             // formula to convert from Hz to MIDI note
-    return 69.0 + 12.0 * log2f(Hz / 440.0);
-  }
-  float MIDItoFreq(float MIDI) {           // formula to convert from MIDI note to Hz
-    return 440.0 * exp2((MIDI - 69.0) / 12.0);
-  }
-  float stepsToMIDI(int16_t stepsFromA) {  // return the MIDI pitch associated
-    return freqToMIDI(CONCERT_A_HZ) + ((float)stepsFromA * (float)current.tuning().stepSize / 100.0);
-  }
 
 // ====== diagnostic wrapper
 
@@ -1024,60 +293,127 @@
 
 // ====== LED routines
 
-  int16_t transformHue(float D) {
-    if ((!perceptual) || (D > 360.0)) {
-      return 65536 * (D / 360.0);
-    } else {
-      //                red             yellow                 green               blue
-      int hueIn[] =  {    0,    9,   18,   90,  108,  126,  135,  150,  198,  243,  252,  261,  306,  333,  360};
-      //          #ff0000            #ffff00           #00ff00       #00ffff     #0000ff     #ff00ff
-      int hueOut[] = {    0, 3640, 5461,10922,12743,16384,21845,27306,32768,38229,43690,49152,54613,58254,65535};
-      byte B = 0;
-      while (D - hueIn[B] > 0) {
-        B++;
-      };
-      return hueOut[B - 1] + (hueOut[B] - hueOut[B - 1]) * ((D - (float)hueIn[B - 1])/(float)(hueIn[B] - hueIn[B - 1]));
+  int16_t transformHue(float h) {
+    //                red             yellow                 green               blue
+    int hueIn[] =  {    0,    9,   18,   90,  108,  126,  135,  150,  198,  243,  252,  261,  306,  333,  360};
+    //          #ff0000            #ffff00           #00ff00       #00ffff     #0000ff     #ff00ff
+    int hueOut[] = {    0, 3640, 5461,10922,12743,16384,21845,27306,32768,38229,43690,49152,54613,58254,65535};
+    float D = fmod(h,360);
+    byte B = 0;
+    while (D - hueIn[B] > 0) {
+      B++;
     };
+    float T = (D - hueIn[B - 1]) / (float)(hueIn[B] - hueIn[B - 1]);
+    return (hueOut[B - 1] * (1 - T)) + (hueOut[B] * T);
   }
+
+  uint32_t getLEDcode(colorDef c) {
+    return strip.gamma32(strip.ColorHSV(transformHue(c.hue),c.sat,c.val));
+  }
+
   void resetHexLEDs() { // calculate color codes for each hex, store for playback
-    int16_t hue;
-    float hueDegrees;
-    byte sat;
-    colors c;
     for (byte i = 0; i < LED_COUNT; i++) {
       if (!(h[i].isCmd)) {
-        byte scaleDegree = positiveMod(h[i].steps + current.key().offset - current.findC(),current.tuning().cycleLength);
+        colorDef setColor;
+        byte paletteIndex = positiveMod(h[i].stepsFromC,current.tuning().cycleLength);
         switch (colorMode) {
-          case 1:
-            c = keyOptions[current.keysBegin() + scaleDegree].tierColor;
-            hueDegrees = hueCode[c];
-            sat = satCode[c];
+          case TIERED_COLOR_MODE:
+            setColor = palette[current.tuningIndex].getColor(paletteIndex);
             break;
           default:
-            hueDegrees = 360.0 * ((float)scaleDegree / (float)current.tuning().cycleLength);
-            sat = 255;
+            setColor = 
+              { 360.0 * ((float)paletteIndex / (float)current.tuning().cycleLength)
+              , SAT_VIVID
+              , VALUE_NORMAL
+              };
             break;
         };
-        hue = transformHue(hueDegrees);
-        h[i].LEDcolorPlay = strip.gamma32(strip.ColorHSV(hue,sat,VeryBRIGHT));
-        h[i].LEDcolorOn = strip.gamma32(strip.ColorHSV(hue,sat,BRIGHT));
-        h[i].LEDcolorOff = strip.gamma32(strip.ColorHSV(hue,sat,DIM));
-        h[i].LEDcolorAnim = strip.ColorHSV(hue,0,255);
-      } else {
-        //
+        h[i].LEDcolorOn   = getLEDcode(setColor);
+        h[i].LEDcolorPlay = getLEDcode(setColor.mixWithWhite()); // "mix with white"
+        setColor = {HUE_NONE,SAT_BW,VALUE_BLACK};
+        h[i].LEDcolorOff  = getLEDcode(setColor);                // turn off entirely
+        h[i].LEDcolorAnim = h[i].LEDcolorPlay;
       };
     };
+    sendToLog("LED codes re-calculated.");
+  }
+
+  void resetVelocityLEDs() {
+    colorDef tempColor = 
+      { (runTime % (rainbowDegreeTime * 360)) / rainbowDegreeTime
+      , SAT_MODERATE
+      , byteLerp(0,255,85,127,velWheel.curValue)
+      };
+    strip.setPixelColor(assignCmd[0], getLEDcode(tempColor));
+
+    tempColor.val = byteLerp(0,255,42,85,velWheel.curValue);
+    strip.setPixelColor(assignCmd[1], getLEDcode(tempColor));
+    
+    tempColor.val = byteLerp(0,255,0,42,velWheel.curValue);
+    strip.setPixelColor(assignCmd[2], getLEDcode(tempColor));
+  }
+  void resetWheelLEDs() {
+    // middle button
+    int tempSat = SAT_BW;
+    colorDef tempColor = {HUE_NONE, tempSat, (toggleWheel ? VALUE_SHADE : VALUE_LOW)};
+    strip.setPixelColor(assignCmd[3], getLEDcode(tempColor));
+    if (toggleWheel) {
+      // pb red / green
+      tempSat = byteLerp(SAT_BW,SAT_VIVID,0,8192,abs(pbWheel.curValue));
+      tempColor = {((pbWheel.curValue > 0) ? HUE_RED : HUE_CYAN), tempSat, VALUE_FULL};
+      strip.setPixelColor(assignCmd[5], getLEDcode(tempColor));
+
+      tempColor.val = tempSat * (pbWheel.curValue > 0);
+      strip.setPixelColor(assignCmd[4], getLEDcode(tempColor));
+
+      tempColor.val = tempSat * (pbWheel.curValue < 0);
+      strip.setPixelColor(assignCmd[6], getLEDcode(tempColor));
+    } else {
+      // mod blue / yellow
+      tempSat = byteLerp(SAT_BW,SAT_VIVID,0,64,abs(modWheel.curValue - 63));
+      tempColor = {((modWheel.curValue > 63) ? HUE_YELLOW : HUE_INDIGO), tempSat, 127 + (tempSat / 2)};
+      strip.setPixelColor(assignCmd[6], getLEDcode(tempColor));
+
+      if (modWheel.curValue <= 63) {
+        tempColor.val = 127 - (tempSat / 2);
+      }
+      strip.setPixelColor(assignCmd[5], getLEDcode(tempColor));
+      
+      tempColor.val = tempSat * (modWheel.curValue > 63);
+      strip.setPixelColor(assignCmd[4], getLEDcode(tempColor));
+    };
+  }
+  uint32_t applyNotePixelColor(byte x){
+           if (h[x].animate) {     return h[x].LEDcolorAnim;
+    } else if (h[x].channel) {     return h[x].LEDcolorPlay;
+    } else if (h[x].inScale) {     return h[x].LEDcolorOn;
+    } else {                       return h[x].LEDcolorOff;
+    };
   }
 
 // ====== layout routines
 
+  float freqToMIDI(float Hz) {             // formula to convert from Hz to MIDI note
+    return 69.0 + 12.0 * log2f(Hz / 440.0);
+  }
+  float MIDItoFreq(float MIDI) {           // formula to convert from MIDI note to Hz
+    return 440.0 * exp2((MIDI - 69.0) / 12.0);
+  }
+  float stepsToMIDI(int16_t stepsFromA) {  // return the MIDI pitch associated
+    return freqToMIDI(CONCERT_A_HZ) + ((float)stepsFromA * (float)current.tuning().stepSize / 100.0);
+  }
+
   void assignPitches() {     // run this if the layout, key, or transposition changes, but not if color or scale changes
     sendToLog("assignPitch was called:");
     for (byte i = 0; i < LED_COUNT; i++) {
       if (!(h[i].isCmd)) {
-        float N = stepsToMIDI(h[i].steps + current.key().offset + current.transpose);
+        // steps is the distance from C
+        // the stepsToMIDI function needs distance from A4
+        // it also needs to reflect any transposition, but
+        // NOT the key of the scale.
+        float N = stepsToMIDI(current.pitchRelToA4(h[i].stepsFromC));
         if (N < 0 || N >= 128) {
-          h[i].note = 255;
+          h[i].note = UNUSED_NOTE;
           h[i].bend = 0;
           h[i].frequency = 0.0;
         } else {
@@ -1087,7 +423,7 @@
         };
         sendToLog(
           "hex #" + std::to_string(i) + ", " +
-          "steps=" + std::to_string(h[i].steps) + ", " +
+          "steps=" + std::to_string(h[i].stepsFromC) + ", " +
           "isCmd? " + std::to_string(h[i].isCmd) + ", " +
           "note=" + std::to_string(h[i].note) + ", " + 
           "bend=" + std::to_string(h[i].bend) + ", " + 
@@ -1102,14 +438,25 @@
     sendToLog("applyScale was called:");
     for (byte i = 0; i < LED_COUNT; i++) {
       if (!(h[i].isCmd)) {
-        byte degree = positiveMod(h[i].steps, current.tuning().cycleLength);
-        byte whichByte = degree / 8;
-        byte bitShift = 7 - (degree - (whichByte << 3));
-        byte digitMask = 1 << bitShift;
-        h[i].inScale = (current.scale().step[whichByte] & digitMask) >> bitShift;
+        if (current.scale().tuning == ALL_TUNINGS) {
+          h[i].inScale = 1;
+        } else {
+          byte degree = current.keyDegree(h[i].stepsFromC); 
+          if (degree == 0) {
+            h[i].inScale = 1;    // the root is always in the scale
+          } else {
+            byte tempSum = 0;
+            byte iterator = 0;
+            while (degree > tempSum) {
+              tempSum += current.scale().pattern[iterator];
+              iterator++;
+            };  // add the steps in the scale, and you're in scale
+            h[i].inScale = (tempSum == degree);   // if the note lands on one of those sums
+          };
+        };
         sendToLog(
           "hex #" + std::to_string(i) + ", " +
-          "steps=" + std::to_string(h[i].steps) + ", " +
+          "steps=" + std::to_string(h[i].stepsFromC) + ", " +
           "isCmd? " + std::to_string(h[i].isCmd) + ", " +
           "note=" + std::to_string(h[i].note) + ", " + 
           "inScale? " + std::to_string(h[i].inScale) + "."
@@ -1119,21 +466,23 @@
     resetHexLEDs();
     sendToLog("applyScale complete.");
   }
+
   void applyLayout() {       // call this function when the layout changes
     sendToLog("buildLayout was called:");
     for (byte i = 0; i < LED_COUNT; i++) {
-      if (!(h[i].isCmd)) {
-        coordinates dist = hexDistance(h[current.layout().rootHex].coords, h[i].coords);
-        h[i].steps = (
-          (dist.col * current.layout().acrossSteps) + 
-          (dist.row * (
+      if (!(h[i].isCmd)) {        
+        int8_t distCol = h[i].coordCol - h[current.layout().hexMiddleC].coordCol;
+        int8_t distRow = h[i].coordRow - h[current.layout().hexMiddleC].coordRow;
+        h[i].stepsFromC = (
+          (distCol * current.layout().acrossSteps) + 
+          (distRow * (
             current.layout().acrossSteps + 
             (2 * current.layout().dnLeftSteps)
           ))
-        ) / 2;
+        ) / 2;  
         sendToLog(
           "hex #" + std::to_string(i) + ", " +
-          "steps=" + std::to_string(h[i].steps) + "."
+          "steps from C4=" + std::to_string(h[i].stepsFromC) + "."
         );
       };
     };
@@ -1143,7 +492,6 @@
     sendToLog("buildLayout complete.");
   }
 // ====== buzzer routines
-
   // the piezo buzzer is an on/off switch that can buzz as fast as the processor clock (133MHz)
   // the processor is fast enough to emulate analog signals.
   // the RP2040 has pulse width modulation (PWM) built into the hardware.
@@ -1152,64 +500,83 @@
   // to emulate an 8-bit (0-255) analog sample, with phase-correction, we need a 9 bit (512) cycle.
   // we can safely sample up to 260kHz (133MHz / 512) this way.
   // the highest frequency note in MIDI is about 12.5kHz.
-  // it is therefore possible to emulate waveforms with 4 bits resolution (260kHz / 12.5kHz, it's > 16 but < 32).
+  // it is theoretically possible to emulate waveforms with 4 bits resolution (260kHz / 12.5kHz)
+  // but we are limited by calculation time.
+  // the macro POLLING_INTERVAL_IN_MICROSECONDS is set to a value that is long enough
+  // that the audio output is accurate, but short enough to allow as much resolution as possible.
+  // currently, 32 microseconds appears to be sufficient (about 500 CPU cycles).
+  //
   // 1) set a constant PWM signal at F_CPU/512 (260kHz) to play on pin 23
   //    the PWM signal can emulate an analog value from 0 to 255.
   //    this is done in setup1().
-  // 2) if a note is to be played on the buzzer, get the frequency, and express as a period in microseconds.
+  // 2) if a note is to be played on the buzzer, assign a channel (same as MPE mode for MIDI)
+  //    and calculate the frequency. this might include pitch bends.
   //    this is done in buzz().
-  // 3) divide the period into 16 subperiods. fractions of microsecond are distributed across the 16 ticks.
-  // 4) every subperiod, change the level of the PWM output so that you emulate the next in the sequence of
-  //    16 analog sample values. those values are based on the waveform shape chosen (square, sine, etc)
-  // 5) this value is also scaled by the MIDI velocity wheel.
+  // 3) the frequency is expressed as "amount you'd increment a counter every polling interval
+  //    so that you roll over a 16-bit (65536) value at that frequency.
+  // example: 440Hz note, 32microS polling
+  //    65536 x 440/s x .000032s = an increment of 923 per poll
+  //    this is done in buzz().
+  // 4) the object called synth[] stores the increment and counter for each channel (0-14)=MIDI(2 thru 16)
+  //    at every poll, each counter is incremented (will roll over since the type is 16-bit unsigned integer)
+  //    and depending on the waveform, the 8-bit analog level is calculated.
+  //    example: square waves return 0 if the counter is 0-32767, 255 if 32768-65535.
+  //             saw waves return (counter / 256).
+  // 5) the analog levels are mixed. i use an attenuation function, basically (# of simultaneous notes) ^ -0.5,
+  //    so the perceived volume is consistent. the velocity wheel is also multiplied in.
   // 6) hardware timers are used because they will interrupt and run even if other code is active.
   //    otherwise, the subperiod is essentially floored at the length of the main loop() which is
   //    thousands of microseconds long!
-  // the implementation of 6) is to make a single timer that calls back an interrupt function called advanceTick().
+  //    further, we can run this process on the 2nd core so it doesn't interrupt the user experience
+  // the implementation of 6) is to make a single timer that calls back an interrupt function called poll().
   // the callback function then resets the interrupt flag and resets the timer alarm.
-  // the timer is set to go off at the time of the last timer, plus the subperiod (stored based on the last frequency played).
-  // after the timer is reset, the function then changes the level of the PWM based on 4) and 5) above.
-  // example:
-  // to buzz note 69 (A=440Hz) at velocity 96:
-  //   period = 2273 microseconds
-  //   subperiod = 142 microseconds for 15 ticks, 143 microseconds for 1 tick
-  //   for a square wave play 8 periods at zero level, 8 periods at 96 * 129 / 64 = 203 level
+  // the timer is set to go off at the time of the last timer + the polling interval
+
 
-  void advanceTick() {
+  // RUN ON CORE 2
+  void poll() {
     hw_clear_bits(&timer_hw->intr, 1u << ALARM_NUM);
-    wfTick = ((wfTick + 1) & (WAVE_RESOLUTION - 1));
-    microSecondsPerTick = (microSecondsPerCycle / WAVE_RESOLUTION)
-       + ((microSecondsPerCycle % WAVE_RESOLUTION) < wfTick)
-      ;
-    timer_hw->alarm[ALARM_NUM] = timer_hw->timerawl + microSecondsPerTick;
-    wfLvl = ((playbackMode && (currentHexBuzzing <= LED_COUNT)) ? (wf[currWave].lvl[wfTick] * velWheel.curValue) >> 6 : 0);
-    pwm_set_chan_level(TONE_SL, TONE_CH, wfLvl);
-  }
-  void buzz() {
-    if (playbackMode && (currentHexBuzzing <= LED_COUNT)) {
-      microSecondsPerCycle = round(1000000 / (exp2(pbWheel.curValue * PITCH_BEND_SEMIS / 98304.0) * h[currentHexBuzzing].frequency));
-    };
-  }
-  byte nextHeldNote() {
-    byte n = 255;
-    for (byte i = 1; i < LED_COUNT; i++) {
-      byte j = positiveMod(currentHexBuzzing + i, LED_COUNT);
-      if ((h[j].channel) && (!h[j].isCmd)) {
-        n = j;
-        break;
+    timer_hw->alarm[ALARM_NUM] = timer_hw->timerawl + POLL_INTERVAL_IN_MICROSECONDS;
+    uint16_t lvl = 0;
+    byte p;
+    for (byte i = 0; i < POLYPHONY_LIMIT; i++) {
+      synth[i].counter += synth[i].increment; // should loop from 65536 -> 0
+      switch (currWave) {
+        case WAVEFORM_SQUARE:
+          p = 0 - ((synth[i].counter & 0x8000) >> 15);   // grab first bit -> 0 or -1 (255)
+          break;
+        case WAVEFORM_SAW:
+          p = (synth[i].counter >> 8);  // 0 thru 255
+          break;
+        default:
+          p = 0;
+          break;
       };
+      lvl += p;  // for polyphony=15, cap=255*15=3825
     };
-    return n;
-  }
-  void tryBuzzing(byte x) {        
-    currentHexBuzzing = x;
-    if ((h[x].isCmd) || (h[x].note >= 128)) {
-      currentHexBuzzing = 255;  // send 128 or larger to turn off tone
+    lvl = (lvl * attenuation[poly]) >> 8; // cap = 3825 * 17 / 256 = 254
+    lvl = (lvl * velWheel.curValue) >> 7;
+    pwm_set_chan_level(TONE_SL, TONE_CH, lvl);
+  }
+  // RUN ON CORE 1
+  void buzz(byte x, bool p) {
+    byte ch = h[x].channel - 2;
+    synth[ch].counter = 0;
+    if (p) {
+      synth[ch].increment = h[x].frequency                     // note frequency
+        * exp2(pbWheel.curValue * PITCH_BEND_SEMIS / 98304.0)  // adjusted for global pitch bend
+        * ((POLL_INTERVAL_IN_MICROSECONDS << 16) / 1000000);   // cycle 0-65535 at resultant frequency
+    } else {
+      synth[ch].increment = 0;                                 // zero effectively silences the channel
     };
-    buzz();
   }
-// ====== MIDI routines
 
+// ====== MIDI routines
+  void setAllNotesOff(byte Ch) {
+    if (enableMIDI) {
+      MIDI.sendControlChange(123, 0, Ch);
+    }
+  }
   void setPitchBendRange(byte Ch, byte semitones) {
     if (enableMIDI) {     // MIDI mode only
       MIDI.beginRpn(0, Ch);
@@ -1217,7 +584,8 @@
       MIDI.endRpn(Ch);
       sendToLog(
         "set pitch bend range on ch " +
-        std::to_string(Ch) + " to be " + std::to_string(semitones) + " semitones"
+        std::to_string(Ch) + " to be " + 
+        std::to_string(semitones) + " semitones"
       );
     }
   }
@@ -1232,144 +600,96 @@
       );
     }
   }
-  void prepMIDIforMicrotones() {
-    bool makeZone = (MPE && (current.tuningIndex != Twelve)); // if MPE flag is on and tuning <> 12EDO
-    setMPEzone(1, (8 * makeZone));   // MPE zone 1 = ch 2 thru 9 (or reset if not using MPE)
-    delay(ccMsgCoolDown);
-    setMPEzone(16, (5 * makeZone));  // MPE zone 16 = ch 11 thru 15 (or reset if not using MPE)
-    delay(ccMsgCoolDown);
+  void applyMPEmode() {
+    while (!openChannelQueue.empty()) {         // empty the channel queue
+      openChannelQueue.pop();
+    };
     for (byte i = 1; i <= 16; i++) {
-      setPitchBendRange(i, PITCH_BEND_SEMIS);  // some synths try to set PB range to 48 semitones.
-      delay(ccMsgCoolDown);                  // this forces it back to the expected range of 2 semitones.
-      if ((i != 10) && ((!makeZone) || ((i > 1) && (i < 16)))) {
-        openChannelQueue.push(i);
-        sendToLog("pushed ch " + std::to_string(i) + " to the open channel queue");
-      };
-      channelBend[i - 1] = 0;
-      channelPoly[i - 1] = 0;
+      setAllNotesOff(i);                        // turn off all notes
+      setPitchBendRange(i, PITCH_BEND_SEMIS);   // force pitch bend back to the expected range of 2 semitones.
+    };
+    setMPEzone(1, POLYPHONY_LIMIT);   // MPE zone 1 = ch 2 thru 16
+    for (byte i = 0; i < POLYPHONY_LIMIT; i++) {
+      openChannelQueue.push(i + 2);
+      sendToLog("pushed ch " + std::to_string(i + 2) + " to the open channel queue");
     };
   }
   void chgModulation() {
     if (enableMIDI) {     // MIDI mode only
-      if (current.tuningIndex == Twelve) {
-        MIDI.sendControlChange(1, modWheel.curValue, 1);
-        sendToLog("sent mod value " + std::to_string(modWheel.curValue) + " to ch 1");
-      } else if (MPE) {
-        MIDI.sendControlChange(1, modWheel.curValue, 1);
-        sendToLog("sent mod value " + std::to_string(modWheel.curValue) + " to ch 1");
-        MIDI.sendControlChange(1, modWheel.curValue, 16);
-        sendToLog("sent mod value " + std::to_string(modWheel.curValue) + " to ch 16");
-      } else {
-        for (byte i = 0; i < 16; i++) {
-          MIDI.sendControlChange(1, modWheel.curValue, i + 1);
-          sendToLog("sent mod value " + std::to_string(modWheel.curValue) + " to ch " + std::to_string(i+1));
-        };
-      };
+      MIDI.sendControlChange(1, modWheel.curValue, 1);
+      sendToLog("sent mod value " + std::to_string(modWheel.curValue) + " to ch 1");
     };
-  };
+  }
   void chgUniversalPB() {
     if (enableMIDI) {     // MIDI mode only
-      if (current.tuningIndex == Twelve) {
-        MIDI.sendPitchBend(pbWheel.curValue, 1);
-        sendToLog("sent pb value " + std::to_string(pbWheel.curValue) + " to ch 1");
-      } else if (MPE) {
-        MIDI.sendPitchBend(pbWheel.curValue, 1);
-        sendToLog("sent pb value " + std::to_string(pbWheel.curValue) + " to ch 1");
-        MIDI.sendPitchBend(pbWheel.curValue, 16);
-        sendToLog("sent pb value " + std::to_string(pbWheel.curValue) + " to ch 16");
-      } else {
-        for (byte i = 0; i < 16; i++) {
-          MIDI.sendPitchBend(channelBend[i] + pbWheel.curValue, i + 1);
-          sendToLog("sent pb value " + std::to_string(channelBend[i] + pbWheel.curValue) + " to ch " + std::to_string(i+1));
-        };
-      };
-    };
-  }
-  byte assignChannel(byte x) {
-    if (current.tuningIndex == Twelve) {
-      return 1;
-    } else {
-      byte temp = 17;
-      for (byte c = MPE; c < (16 - MPE); c++) {  // MPE - look at ch 2 thru 15 [c 1-14]; otherwise ch 1 thru 16 [c 0-15]
-        if ((c + 1 != 10) && (h[x].bend == channelBend[c])) {  // not using drum channel ch 10 in either case
-          temp = c + 1;
-          sendToLog("found a matching channel: ch " + std::to_string(temp) + " has pitch bend " + std::to_string(channelBend[c]));
-          break;
-        };
-      };
-      if (temp = 17) {
-        if (openChannelQueue.empty()) {
-          sendToLog("channel queue was empty so we didn't send a note on");
-        } else {
-          temp = openChannelQueue.front();
-          openChannelQueue.pop();
-          sendToLog("popped " + std::to_string(temp) + " off the queue");
+      MIDI.sendPitchBend(pbWheel.curValue, 1);
+      for (byte i = 0; i < LED_COUNT; i++) {
+        if (!(h[i].isCmd)) {
+          if (h[i].channel) {
+            buzz(i,true);           // rebuzz all notes if the pitch bend changes
+          };
         };
       };
-      return temp;
+      sendToLog("sent pb wheel value " + std::to_string(pbWheel.curValue) + " to ch 1");
     };
   }
-
+  
 // ====== hex press routines
 
-  void noteOn(byte x) {
-    byte c = assignChannel(x);
-    if (c <= 16) {
-      h[x].channel = c;       // value is 1 - 16
-      if (current.tuningIndex != Twelve) {
-        channelPoly[c - 1]++;   // array is 0 - 15
-      };    
-      if (playbackMode) {
-        tryBuzzing(x);
+  void playNote(byte x) {
+    // this gets called on any non-command hex
+    // that is not scale-locked.
+    if (!(h[x].channel)) {    // but just in case, check
+      if (openChannelQueue.empty()) {   // if there aren't any open channels
+        sendToLog("channel queue was empty so did not play");
       } else {
-        if (current.tuningIndex != Twelve) {
-          MIDI.sendPitchBend(h[x].bend, c); // ch 1-16
+        h[x].channel = openChannelQueue.front();   // value in MIDI terms (1-16)
+        openChannelQueue.pop();
+        sendToLog("popped " + std::to_string(h[x].channel) + " off the queue");
+        if (!(playbackMode == BUZZ_OFF)) {
+          buzz(x, true);
+        };
+        if (enableMIDI) {
+          MIDI.sendPitchBend(h[x].bend, h[x].channel); // ch 1-16
+          MIDI.sendNoteOn(h[x].note, velWheel.curValue, h[x].channel); // ch 1-16
+          sendToLog(
+            "sent MIDI noteOn: " + std::to_string(h[x].note) +
+            " pb "  + std::to_string(h[x].bend) +
+            " vel " + std::to_string(velWheel.curValue) +
+            " ch "  + std::to_string(h[x].channel)
+          );
         };
-        MIDI.sendNoteOn(h[x].note, velWheel.curValue, c); // ch 1-16
-        sendToLog(
-          "sent note on: " + std::to_string(h[x].note) +
-          " pb " + std::to_string(h[x].bend) +
-          " vel " + std::to_string(velWheel.curValue) +
-          " ch " + std::to_string(c)
-        );
       };
     };
   }
-  void noteOff(byte x) {
-    byte c = h[x].channel;
-    if (c) {
-      h[x].channel = 0;
-      if (current.tuningIndex != Twelve) {
-        switch (channelPoly[c - 1]) {
-          case 1:
-            channelPoly[c - 1]--;
-            openChannelQueue.push(c);
-            break;
-          case 0:
-            break;
-          default:
-            channelPoly[c - 1]--;
-            break;
-        };
+  void stopNote(byte x) {
+    // this gets called on any non-command hex
+    // that is not scale-locked.
+    if (h[x].channel) {    // but just in case, check
+      openChannelQueue.push(h[x].channel);
+      sendToLog("pushed " + std::to_string(h[x].channel) + " on the queue");
+      if (!(playbackMode == BUZZ_OFF)) {
+        buzz(x, false);
       };
-      if (playbackMode) {
-        tryBuzzing(nextHeldNote());
-      } else {
-        MIDI.sendNoteOff(h[x].note, velWheel.curValue, c);
+      if (playbackMode == BUZZ_ARPEGGIO) {
+        arpeggiateTime = 0;    // trigger arpeggiate function early if any note changes
+      };
+      if (enableMIDI) {
+        MIDI.sendNoteOff(h[x].note, velWheel.curValue, h[x].channel);
         sendToLog(
           "sent note off: " + std::to_string(h[x].note) +
           " pb " + std::to_string(h[x].bend) +
           " vel " + std::to_string(velWheel.curValue) +
-          " ch " + std::to_string(c)
+          " ch " + std::to_string(h[x].channel)
         );
       };
+      h[x].channel = 0;
     };
   }
   void cmdOn(byte x) {   // volume and mod wheel read all current buttons
     switch (h[x].note) {
       case CMDB + 3:
         toggleWheel = !toggleWheel;
-        // recolorHex(x);
         break;
       default:
         // the rest should all be taken care of within the wheelDef structure
@@ -1377,14 +697,28 @@
     };
   }
   void cmdOff(byte x) {   // pitch bend wheel only if buttons held.
-    // nothing; should all be taken care of within the wheelDef structure
+    switch (h[x].note) {
+      default:
+        break;  // nothing; should all be taken care of within the wheelDef structure
+    };
   }
 
 // ====== animations
-
-  void flagToAnimate(coordinates C) {
-    if (!hexOOB(C)) {
-      h[coordToIndex(C)].animate = 1;
+  uint64_t animFrame(byte x) {     
+    if (h[x].timePressed) {          // 2^20 microseconds is close enough to 1 second
+      return 1 + (((runTime - h[x].timePressed) * animationFPS) >> 20);
+    } else {
+      return 0;
+    };
+  }
+  void flagToAnimate(int8_t r, int8_t c) {
+    if (! 
+      (    ( r < 0 ) || ( r >= ROWCOUNT )
+        || ( c < 0 ) || ( c >= (2 * COLCOUNT) )
+        || ( ( c + r ) & 1 )
+      )
+    ) {
+      h[(10 * r) + (c / 2)].animate = 1;
     };
   }
   void animateMirror() {
@@ -1392,8 +726,8 @@
       if ((!(h[i].isCmd)) && (h[i].channel)) {              // that is a held note     
         for (byte j = 0; j < LED_COUNT; j++) {               // compare to every hex
           if ((!(h[j].isCmd)) && (!(h[j].channel))) {       // that is a note not being played
-            int16_t temp = h[i].steps - h[j].steps;         // look at difference between notes
-            if (animationType == OctaveAnim) {              // set octave diff to zero if need be
+            int16_t temp = h[i].stepsFromC - h[j].stepsFromC;         // look at difference between notes
+            if (animationType == ANIMATE_OCTAVE) {              // set octave diff to zero if need be
               temp = positiveMod(temp, current.tuning().cycleLength);
             };
             if (temp == 0) {                                // highlight if diff is zero
@@ -1404,24 +738,29 @@
       };
     };
   }
+
   void animateOrbit() {
     for (byte i = 0; i < LED_COUNT; i++) {                               // check every hex
-      if ((!(h[i].isCmd)) && (h[i].channel)) {                          // that is a held note     
-        flagToAnimate(hexOffset(h[i].coords,hexVector((h[i].animFrame() % 6),1)));       // different neighbor each frame
+      if ((!(h[i].isCmd)) && (h[i].channel) && ((h[i].inScale) || (!scaleLock))) {    // that is a held note
+        byte tempDir = (animFrame(i) % 6);
+        flagToAnimate(h[i].coordRow + vertical[tempDir], h[i].coordCol + horizontal[tempDir]);       // different neighbor each frame
       };
     };
   }
+
   void animateRadial() {
-    for (byte i = 0; i < LED_COUNT; i++) {                               // check every hex
-      if (!(h[i].isCmd)) {                                               // that is a note
-        uint32_t radius = h[i].animFrame();
-        if ((radius > 0) && (radius < 16)) {                              // played in the last 16 frames
-          byte steps = ((animationType == SplashAnim) ? radius : 1);    // star = 1 step to next corner; ring = 1 step per hex
-          coordinates temp = hexOffset(h[i].coords,hexVector(DnLeft,radius));  // start at one corner of the ring
-          for (byte dir = 0; dir < 6; dir++) {                          // walk along the ring in each of the 6 hex directions
+    for (byte i = 0; i < LED_COUNT; i++) {                              // check every hex
+      if (!(h[i].isCmd) && ((h[i].inScale) || (!scaleLock))) {                                              // that is a note
+        uint64_t radius = animFrame(i);
+        if ((radius > 0) && (radius < 16)) {                            // played in the last 16 frames
+          byte steps = ((animationType == ANIMATE_SPLASH) ? radius : 1);    // star = 1 step to next corner; ring = 1 step per hex
+          int8_t turtleRow = h[i].coordRow + (radius * vertical[HEX_DIRECTION_SW]);
+          int8_t turtleCol = h[i].coordCol + (radius * horizontal[HEX_DIRECTION_SW]);
+          for (byte dir = HEX_DIRECTION_EAST; dir < 6; dir++) {        // walk along the ring in each of the 6 hex directions
             for (byte i = 0; i < steps; i++) {                          // # of steps to the next corner 
-              flagToAnimate(temp);                                       // flag for animation
-              temp = hexOffset(temp, hexVector(dir,radius / steps));    // then next step
+              flagToAnimate(turtleRow,turtleCol);                     // flag for animation
+              turtleRow += (vertical[dir] * (radius / steps));
+              turtleCol += (horizontal[dir] * (radius / steps));
             };
           };
         };
@@ -1430,7 +769,6 @@
   }
 
 // ====== menu routines
-
   void menuHome() {
     menu.setMenuPageCurrent(menuPageMain);
     menu.drawMenu();
@@ -1443,13 +781,13 @@
   }
   void showOnlyValidScaleChoices() { // re-run at setup and whenever tuning changes
     for (int S = 0; S < scaleCount; S++) {
-      menuItemScales[S]->hide((scaleOptions[S].tuning != current.tuningIndex) && (scaleOptions[S].tuning != 255));
+      menuItemScales[S]->hide((scaleOptions[S].tuning != current.tuningIndex) && (scaleOptions[S].tuning != ALL_TUNINGS));
     };
     sendToLog("menu: Scale choices were updated.");
   }
   void showOnlyValidKeyChoices() { // re-run at setup and whenever tuning changes
-    for (int K = 0; K < keyCount; K++) {
-      menuItemKeys[K]->hide((keyOptions[K].tuning != current.tuningIndex));
+    for (int T = 0; T < TUNINGCOUNT; T++) {
+      menuItemKeys[T]->hide((T != current.tuningIndex));
     };
     sendToLog("menu: Key choices were updated.");
   }
@@ -1469,72 +807,66 @@
     };
     menuHome();
   }
-  void changeKey(GEMCallbackData callbackData) {     // when you change the key via the menu
-    int selection = callbackData.valInt;
-    if (selection != current.keyIndex) {
-      current.keyIndex = selection;
-      assignPitches();
-    };
-    menuHome();
+  void changeKey() {     // when you change the key via the menu
+    applyScale();
+  }
+  void changeTranspose() {     // when you change the transpose via the menu
+    current.transpose = transposeSteps;
+    assignPitches();
   }
   void changeTuning(GEMCallbackData callbackData) { // not working yet
     byte selection = callbackData.valByte;
     if (selection != current.tuningIndex) {
       current.tuningIndex = selection;
-      current.layoutIndex = current.layoutsBegin();
-      current.scaleIndex = 0;
-      current.keyIndex = current.keysBegin();
-      showOnlyValidLayoutChoices();
-      showOnlyValidScaleChoices();
-      showOnlyValidKeyChoices();
-      applyLayout();
-      prepMIDIforMicrotones();
+      current.layoutIndex = current.layoutsBegin();        // reset layout to first in list
+      current.scaleIndex = 0;                              // reset scale to "no scale"
+      current.keyStepsFromA = current.tuning().spanCtoA(); // reset key to C
+      showOnlyValidLayoutChoices();                        // change list of choices in GEM Menu
+      showOnlyValidScaleChoices();                         // change list of choices in GEM Menu
+      showOnlyValidKeyChoices();                           // change list of choices in GEM Menu
+      applyLayout();   // apply changes above
+      applyMPEmode();  // clear out MIDI queue
     };
     menuHome();
   }
-  void buildMenu() {
-    menuPageMain.addMenuItem(menuGotoTuning);
-    for (byte T = 0; T < tuningCount; T++) { // create pointers to all tuning choices
+  void createTuningMenuItems() {
+    for (byte T = 0; T < TUNINGCOUNT; T++) {
       menuItemTuning[T] = new GEMItem(tuningOptions[T].name.c_str(), changeTuning, T);
       menuPageTuning.addMenuItem(*menuItemTuning[T]);
     };
-
-    menuPageMain.addMenuItem(menuGotoLayout);
+  }
+  void createLayoutMenuItems() {
     for (byte L = 0; L < layoutCount; L++) { // create pointers to all layouts
       menuItemLayout[L] = new GEMItem(layoutOptions[L].name.c_str(), changeLayout, L);
       menuPageLayout.addMenuItem(*menuItemLayout[L]);
     };
     showOnlyValidLayoutChoices();
-
-    menuPageMain.addMenuItem(menuGotoScales);
+  }
+  void createKeyMenuItems() {
+    for (byte T = 0; T < TUNINGCOUNT; T++) {
+      selectKey[T] = new GEMSelect(tuningOptions[T].cycleLength, tuningOptions[T].keyChoices);
+      menuItemKeys[T] = new GEMItem("Key:", current.keyStepsFromA, *selectKey[T], changeKey);
+      menuPageScales.addMenuItem(*menuItemKeys[T]);
+    };
+    showOnlyValidKeyChoices();
+  }
+  void createScaleMenuItems() {
     for (int S = 0; S < scaleCount; S++) {  // create pointers to all scale items, filter them as you go
       menuItemScales[S] = new GEMItem(scaleOptions[S].name.c_str(), changeScale, S);
       menuPageScales.addMenuItem(*menuItemScales[S]);
     };
     showOnlyValidScaleChoices();
-
-    menuPageMain.addMenuItem(menuGotoKeys);
-    for (int K = 0; K < keyCount; K++) {
-      menuItemKeys[K] = new GEMItem(keyOptions[K].name.c_str(), changeKey, K);
-      menuPageKeys.addMenuItem(*menuItemKeys[K]);
-    };
-    showOnlyValidKeyChoices();
-
-    menuPageMain.addMenuItem(menuItemScaleLock);
-    menuPageMain.addMenuItem(menuItemColor);
-    menuPageMain.addMenuItem(menuItemMIDI);
-    menuPageMain.addMenuItem(menuItemPlayback);  
-    menuPageMain.addMenuItem(menuItemWaveform);
-    menuPageMain.addMenuItem(menuItemAnimate);
-    menuPageMain.addMenuItem(menuItemPercep);
-    menuPageMain.addMenuItem(menuItemMPE);
   }
 
 // ====== setup routines
-
+  void testDiagnostics() {
+    sendToLog("theHDM was here");
+  }
   void setupMIDI() {
     usb_midi.setStringDescriptor("HexBoard MIDI");  // Initialize MIDI, and listen to all MIDI channels
     MIDI.begin(MIDI_CHANNEL_OMNI);                  // This will also call usb_midi's begin()
+    applyMPEmode();
+    sendToLog("setupMIDI okay");
   }
   void setupFileSystem() {
     Serial.begin(115200);     // Set serial to make uploads work without bootsel button
@@ -1544,81 +876,98 @@
     LittleFS.begin();  // Mounts file system.
     if (!LittleFS.begin()) {
       sendToLog("An Error has occurred while mounting LittleFS");
+    } else {
+      sendToLog("LittleFS mounted OK");
     }
   }
   void setupPins() {
-    for (byte p = 0; p < sizeof(cPin); p++)  // For each column pin...
-    {
+    for (byte p = 0; p < sizeof(cPin); p++) { // For each column pin...
       pinMode(cPin[p], INPUT_PULLUP);  // set the pinMode to INPUT_PULLUP (+3.3V / HIGH).
     }
-    for (byte p = 0; p < sizeof(mPin); p++)  // For each column pin...
-    {
+    for (byte p = 0; p < sizeof(mPin); p++) { // For each column pin...
       pinMode(mPin[p], OUTPUT);  // Setting the row multiplexer pins to output.
     }
     Wire.setSDA(SDAPIN);
     Wire.setSCL(SCLPIN);
     pinMode(ROT_PIN_C, INPUT_PULLUP);
+    sendToLog("Pins mounted");
   }
   void setupGrid() {
-    /*
-    sendToLog("initializing hex grid..."));
-    */
     for (byte i = 0; i < LED_COUNT; i++) {
-      h[i].coords = indexToCoord(i);
+      h[i].coordRow = (i / 10);
+      h[i].coordCol = (2 * (i % 10)) + (h[i].coordRow & 1);
       h[i].isCmd = 0;
-      h[i].note = 255;
-      h[i].keyState = 0;
+      h[i].note = UNUSED_NOTE;
+      h[i].btnState = 0;
     };
     for (byte c = 0; c < CMDCOUNT; c++) {
       h[assignCmd[c]].isCmd = 1;
       h[assignCmd[c]].note = CMDB + c;
     };
+    sendToLog("initializing hex grid...");
     applyLayout();
   }
-  void setupLEDs() {  // need layout
+  void setupLEDs() { 
     strip.begin();    // INITIALIZE NeoPixel strip object
     strip.show();     // Turn OFF all pixels ASAP
+    sendToLog("LEDs started..."); 
     resetHexLEDs();
   }
-  void setupMenu() {  // need menu
+  void setupMenu() { 
     menu.setSplashDelay(0);
     menu.init();
-    buildMenu();
+    menuPageMain.addMenuItem(menuGotoTuning);
+      createTuningMenuItems();
+      menuPageTuning.addMenuItem(menuTuningBack);
+    menuPageMain.addMenuItem(menuGotoLayout);
+      createLayoutMenuItems();
+      menuPageLayout.addMenuItem(menuLayoutBack);
+    menuPageMain.addMenuItem(menuGotoScales);
+      createKeyMenuItems();
+      menuPageScales.addMenuItem(menuItemScaleLock);
+      createScaleMenuItems();
+      menuPageScales.addMenuItem(menuScalesBack);
+    menuPageMain.addMenuItem(menuGotoControl);
+      menuPageControl.addMenuItem(menuItemPBSpeed);
+      menuPageControl.addMenuItem(menuItemModSpeed);
+      menuPageControl.addMenuItem(menuItemVelSpeed);
+      menuPageControl.addMenuItem(menuControlBack);
+    menuPageMain.addMenuItem(menuItemTransposeSteps);
+    menuPageMain.addMenuItem(menuItemColor);
+    menuPageMain.addMenuItem(menuItemPlayback);  
+    menuPageMain.addMenuItem(menuItemWaveform);
+    menuPageMain.addMenuItem(menuItemAnimate);
     menuHome();
   }
   void setupGFX() {
     u8g2.begin();                       // Menu and graphics setup
     u8g2.setBusClock(1000000);          // Speed up display
     u8g2.setContrast(defaultContrast);  // Set contrast
-  }
-  void testDiagnostics() {
-    /*
-    sendToLog("theHDM was here"));
-    */
+    sendToLog("U8G2 graphics initialized.");
   }
   void setupPiezo() {
-    gpio_set_function(TONEPIN, GPIO_FUNC_PWM);
-    pwm_set_phase_correct(TONE_SL, true);
-    pwm_set_wrap(TONE_SL, 254);
-    pwm_set_clkdiv(TONE_SL, 1.0f);
-    pwm_set_chan_level(TONE_SL, TONE_CH, 0);
-    pwm_set_enabled(TONE_SL, true);
+    gpio_set_function(TONEPIN, GPIO_FUNC_PWM);    // set that pin as PWM
+    pwm_set_phase_correct(TONE_SL, true);         // phase correct sounds better
+    pwm_set_wrap(TONE_SL, 254);                   // 0 - 254 allows 0 - 255 level
+    pwm_set_clkdiv(TONE_SL, 1.0f);                // run at full clock speed
+    pwm_set_chan_level(TONE_SL, TONE_CH, 0);      // initialize at zero to prevent whining sound
+    pwm_set_enabled(TONE_SL, true);               // ENGAGE!
     hw_set_bits(&timer_hw->inte, 1u << ALARM_NUM);  // initialize the timer
-    irq_set_exclusive_handler(ALARM_IRQ, advanceTick);  // function to run every interrupt
-    irq_set_enabled(ALARM_IRQ, true);
-    timer_hw->alarm[ALARM_NUM] = timer_hw->timerawl + microSecondsPerTick;
+    irq_set_exclusive_handler(ALARM_IRQ, poll);  // function to run every interrupt
+    irq_set_enabled(ALARM_IRQ, true);             // ENGAGE!
+    timer_hw->alarm[ALARM_NUM] = timer_hw->timerawl + POLL_INTERVAL_IN_MICROSECONDS;
+    sendToLog("buzzer is ready.");
   }
 
 // ====== loop routines
-
   void timeTracker() {
     lapTime = runTime - loopTime;
-    // sendToLog(lapTime));  // Print out the time it takes to run each loop
-    loopTime = runTime;  // Update previousTime variable to give us a reference point for next loop
-    runTime = millis();   // Store the current time in a uniform variable for this program loop
+    loopTime = runTime;                                 // Update previousTime variable to give us a reference point for next loop
+    runTime = timer_hw->timerawh;
+    runTime = (runTime << 32) + (timer_hw->timerawl);   // Store the current time in a uniform variable for this program loop
   }
   void screenSaver() {
-    if (screenTime <= screenSaverMillis) {
+    if (screenTime <= screenSaverTimeout) {
       screenTime = screenTime + lapTime;
       if (screenSaverOn) {
         screenSaverOn = 0;
@@ -1632,35 +981,39 @@
     }
   }
   void readHexes() {
-    for (byte r = 0; r < ROWCOUNT; r++) {  // Iterate through each of the row pins on the multiplexing chip.
+    for (byte r = 0; r < ROWCOUNT; r++) {      // Iterate through each of the row pins on the multiplexing chip.
       for (byte d = 0; d < 4; d++) {
         digitalWrite(mPin[d], (r >> d) & 1);
       }
-      for (byte c = 0; c < COLCOUNT; c++) {   // Now iterate through each of the column pins that are connected to the current row pin.
-        byte p = cPin[c];               // Hold the currently selected column pin in a variable.
-        pinMode(p, INPUT_PULLUP);             // Set that row pin to INPUT_PULLUP mode (+3.3V / HIGH).
-        delayMicroseconds(10);                // Delay to give the pin modes time to change state (false readings are caused otherwise).
+      for (byte c = 0; c < COLCOUNT; c++) {    // Now iterate through each of the column pins that are connected to the current row pin.
+        byte p = cPin[c];                      // Hold the currently selected column pin in a variable.
+        pinMode(p, INPUT_PULLUP);              // Set that row pin to INPUT_PULLUP mode (+3.3V / HIGH).
+        byte i = c + (r * COLCOUNT);
+        delayMicroseconds(6);                  // delay while column pin mode
         bool didYouPressHex = (digitalRead(p) == LOW);  // hex is pressed if it returns LOW. else not pressed
-        h[c + (r * COLCOUNT)].updateKeyState(didYouPressHex);
-        pinMode(p, INPUT);  // Set the selected column pin back to INPUT mode (0V / LOW).
+        h[i].interpBtnPress(didYouPressHex);
+        if (h[i].btnState == 1) {
+          h[i].timePressed = runTime;          // log the time
+        };
+        pinMode(p, INPUT);                     // Set the selected column pin back to INPUT mode (0V / LOW).
        }
     }
   }
   void actionHexes() { 
     for (byte i = 0; i < LED_COUNT; i++) {   // For all buttons in the deck
-      switch (h[i].keyState) {
+      switch (h[i].btnState) {
         case 1: // just pressed
           if (h[i].isCmd) {
             cmdOn(i);
           } else if (h[i].inScale || (!scaleLock)) {
-            noteOn(i);
+            playNote(i);
           };
           break;
         case 2: // just released
           if (h[i].isCmd) {
             cmdOff(i);
           } else if (h[i].inScale || (!scaleLock)) {
-            noteOff(i);
+            stopNote(i);
           };
           break;
         case 3: // held
@@ -1670,52 +1023,60 @@
       };
     };
   }
+
   void arpeggiate() {
-    if (playbackMode > 1) {
-      if (runTime - currentBuzzTime > arpeggiateLength) {
-        currentBuzzTime = millis();
-        byte n = nextHeldNote();
-        if (n != 255) {
-          tryBuzzing(nextHeldNote());
+    if (playbackMode == BUZZ_ARPEGGIO) {
+      if (runTime - arpeggiateTime > arpeggiateLength) {
+        arpeggiateTime = runTime;
+        byte n = UNUSED_NOTE;
+        for (byte i = 1; i < LED_COUNT; i++) {
+          byte j = positiveMod(arpeggiatingNow + i, LED_COUNT);
+          if ((h[j].channel) && (!h[j].isCmd)) {
+            n = j;
+            break;
+          };
+        };
+        arpeggiatingNow = n;
+        if (n != UNUSED_NOTE) {
+          buzz(n, true);
         };
       };
     };
   }
   void updateWheels() {  
     velWheel.setTargetValue();
-    bool upd = velWheel.updateValue();
+    bool upd = velWheel.updateValue(runTime);
     if (upd) {
-      buzz(); // update the volume live
       sendToLog("vel became " + std::to_string(velWheel.curValue));
     }
     if (toggleWheel) {
       pbWheel.setTargetValue();
-      upd = pbWheel.updateValue();
+      upd = pbWheel.updateValue(runTime);
       if (upd) {
-        buzz();
         chgUniversalPB();
       };
     } else {
       modWheel.setTargetValue();
-      upd = modWheel.updateValue();
+      upd = modWheel.updateValue(runTime);
       if (upd) {
         chgModulation();
       };
     };
   }
-  void animateLEDs() {    // TBD  
+  
+  void animateLEDs() {  
     for (byte i = 0; i < LED_COUNT; i++) {      
       h[i].animate = 0;
     };
     if (animationType) {
       switch (animationType) { 
-        case StarAnim: case SplashAnim:
+        case ANIMATE_STAR: case ANIMATE_SPLASH:
           animateRadial();
           break;
-        case OrbitAnim:
+        case ANIMATE_ORBIT:
           animateOrbit();
           break;
-        case OctaveAnim: case NoteAnim:
+        case ANIMATE_OCTAVE: case ANIMATE_BY_NOTE:
           animateMirror();
           break;  
         default:
@@ -1726,58 +1087,11 @@
   void lightUpLEDs() {   
     for (byte i = 0; i < LED_COUNT; i++) {      
       if (!(h[i].isCmd)) {
-        if (h[i].animate) {
-          strip.setPixelColor(i,h[i].LEDcolorAnim);
-        } else if (h[i].channel) {
-          strip.setPixelColor(i,h[i].LEDcolorPlay);
-        } else if (h[i].inScale) {
-          strip.setPixelColor(i,h[i].LEDcolorOn);
-        } else {
-          strip.setPixelColor(i,h[i].LEDcolorOff);
-        };
-      };
-    };
-    int16_t hueV = transformHue((runTime / rainbowDegreeTime) % 360);
-    strip.setPixelColor(assignCmd[0],strip.gamma32(strip.ColorHSV(
-      hueV,192,byteLerp(0,255,85,127,velWheel.curValue)
-    )));
-    strip.setPixelColor(assignCmd[1],strip.gamma32(strip.ColorHSV(
-      hueV,192,byteLerp(0,255,42,85,velWheel.curValue)
-    )));
-    strip.setPixelColor(assignCmd[2],strip.gamma32(strip.ColorHSV(
-      hueV,192,byteLerp(0,255,0,42,velWheel.curValue)
-    )));
-    if (toggleWheel) {
-      // pb red / green
-      int16_t hueP = transformHue((pbWheel.curValue > 0) ? 0 : 180);
-      byte satP = byteLerp(0,255,0,8192,abs(pbWheel.curValue));
-      strip.setPixelColor(assignCmd[3],strip.gamma32(strip.ColorHSV(
-        0,0,64
-      )));
-      strip.setPixelColor(assignCmd[4],strip.gamma32(strip.ColorHSV(
-        transformHue(0),satP * (pbWheel.curValue > 0),satP * (pbWheel.curValue > 0)
-      )));
-      strip.setPixelColor(assignCmd[5],strip.gamma32(strip.ColorHSV(
-        hueP,satP,255
-      )));
-      strip.setPixelColor(assignCmd[6],strip.gamma32(strip.ColorHSV(
-        transformHue(180),satP * (pbWheel.curValue < 0),satP * (pbWheel.curValue < 0)
-      )));
-    } else {
-      // mod blue / yellow
-      int16_t hueM = transformHue((modWheel.curValue > 63) ? 90 : 270);
-      byte satM = byteLerp(0,255,0,64,abs(modWheel.curValue - 63));
-      strip.setPixelColor(assignCmd[3],strip.gamma32(strip.ColorHSV(0,0,128)));
-      strip.setPixelColor(assignCmd[4],strip.gamma32(strip.ColorHSV(
-        hueM,satM,((modWheel.curValue > 63) ? satM : 0)
-      )));
-      strip.setPixelColor(assignCmd[5],strip.gamma32(strip.ColorHSV(
-        hueM,satM,((modWheel.curValue > 63) ? 127 + (satM / 2) : 127 - (satM / 2))
-      )));
-      strip.setPixelColor(assignCmd[6],strip.gamma32(strip.ColorHSV(
-        hueM,satM,127 + (satM / 2)
-      )));
+        strip.setPixelColor(i,applyNotePixelColor(i));
+      }
     };
+    resetVelocityLEDs();
+    resetWheelLEDs();
     strip.show();
   }
   void dealWithRotary() {
@@ -1802,20 +1116,12 @@
   }
   void keepTrackOfRotaryKnobTurns() {
     switch (rotary.process()) {
-      case DIR_CW:
-        rotaryKnobTurns++;
-        break;
-      case DIR_CCW:
-        rotaryKnobTurns--;
-        break;
+      case DIR_CW:      rotaryKnobTurns++;   break;
+      case DIR_CCW:     rotaryKnobTurns--;   break;
     }
     rotaryKnobTurns = (
-      rotaryKnobTurns > maxKnobTurns 
-      ? maxKnobTurns 
-      : (
-        rotaryKnobTurns < -maxKnobTurns 
-        ? -maxKnobTurns 
-        : rotaryKnobTurns
+      (rotaryKnobTurns > maxKnobTurns) ? maxKnobTurns : (
+        (rotaryKnobTurns < -maxKnobTurns) ? -maxKnobTurns : rotaryKnobTurns 
       )
     );
   }
@@ -1823,6 +1129,7 @@
 // ====== setup() and loop()
 
   void setup() {
+    testDiagnostics();  // Print diagnostic troubleshooting information to serial monitor
     #if (defined(ARDUINO_ARCH_MBED) && defined(ARDUINO_ARCH_RP2040))
     TinyUSB_Device_Init(0);  // Manual begin() is required on core without built-in support for TinyUSB such as mbed rp2040
     #endif
@@ -1836,11 +1143,10 @@
     for (byte i = 0; i < 5 && !TinyUSBDevice.mounted(); i++) {
       delay(1);  // wait until device mounted, maybe
     };
-    testDiagnostics();  // Print diagnostic troubleshooting information to serial monitor
   }
   void setup1() {  // set up on second core
     setupPiezo();
-  };
+  }
   void loop() {   // run on first core
     timeTracker();  // Time tracking functions
     screenSaver();  // Reduces wear-and-tear on OLED panel
diff --git a/Presets.h b/Presets.h
new file mode 100644
index 0000000..ffc4c87
--- /dev/null
+++ b/Presets.h
@@ -0,0 +1,336 @@
+// 1/8192 of a whole tone pitch bend accuracy ~ 0.025 cents.

+// over 128 possible notes, error shd be less than 0.0002 cents to avoid drift.

+// expressing cents to 6 sig figs should be sufficient.

+// notation -- comma delimited string.

+// first entry should be the label for A=440. 

+// last entry should be C, i.e. the "home key".

+// the rest of the scale C thru G will be spelled using the same pattern.

+// the number of commas is used to count where A and C are located in step space.

+tuningDef tuningOptions[] = {

+  { "12 EDO", 12, 100.000, 

+    {{"C" ,-9},{"C#",-8},{"D" ,-7},{"Eb",-6},{"E" ,-5},{"F",-4}

+    ,{"F#",-3},{"G" ,-2},{"G#",-1},{"A" , 0},{"Bb", 1},{"B", 2}

+  }},

+  { "17 EDO", 17, 70.5882, 

+    {{"C",-13},{"Db",-12},{"C#",-11},{"D",-10},{"Eb",-9},{"D#",-8}

+    ,{"E", -7},{"F" , -6},{"Gb", -5},{"F#",-4},{"G", -3},{"Ab",-2}

+    ,{"G#",-1},{"A" ,  0},{"Bb",  1},{"A#", 2},{"B",  3}

+  }},

+  { "19 EDO", 19, 63.1579, 

+    {{"C" ,-14},{"C#",-13},{"Db",-12},{"D",-11},{"D#",-10},{"Eb",-9},{"E",-8}

+    ,{"E#", -7},{"F" , -6},{"F#", -5},{"Gb",-4},{"G",  -3},{"G#",-2}

+    ,{"Ab", -1},{"A" ,  0},{"A#",  1},{"Bb", 2},{"B",   3},{"Cb", 4}

+  }},  

+  { "22 EDO", 22, 54.5455, 

+    {{" C", -17},{"^C",-16},{"vC#",-15},{"vD",-14},{" D",-13},{"^D",-12}

+    ,{"^Eb",-11},{"vE",-10},{" E",  -9},{" F", -8},{"^F", -7},{"vF#",-6}

+    ,{"vG",  -5},{" G", -4},{"^G",  -3},{"vG#",-2},{"vA", -1},{" A",  0}

+    ,{"^A",   1},{"^Bb", 2},{"vB",   3},{" B",  4}

+  }},

+  { "24 EDO", 24, 50.0000, 

+    {{"C", -18},{"C+",-17},{"C#",-16},{"Dd",-15},{"D",-14},{"D+",-13}

+    ,{"Eb",-12},{"Ed",-11},{"E", -10},{"E+", -9},{"F", -8},{"F+", -7}

+    ,{"F#", -6},{"Gd", -5},{"G",  -4},{"G+", -3},{"G#",-2},{"Ad", -1}

+    ,{"A",   0},{"A+",  1},{"Bb",  2},{"Bd",  3},{"B",  4},{"Cd",  5}

+  }},

+  { "31 EDO", 31, 38.7097, 

+    {{"C",-23},{"C+",-22},{"C#",-21},{"Db",-20},{"Dd",-19}

+    ,{"D",-18},{"D+",-17},{"D#",-16},{"Eb",-15},{"Ed",-14}

+    ,{"E",-13},{"E+",-12}                      ,{"Fd",-11}

+    ,{"F",-10},{"F+", -9},{"F#", -8},{"Gb", -7},{"Gd", -6}

+    ,{"G", -5},{"G+", -4},{"G#", -3},{"Ab", -2},{"Ad", -1}

+    ,{"A",  0},{"A+",  1},{"A#",  2},{"Bb",  3},{"Bd",  4}

+    ,{"B",  5},{"B+",  6}                      ,{"Cd",  7}

+  }},

+  { "41 EDO", 41, 29.2683, 

+    {{" C",-31},{"^C",-30},{" C+",-29},{" Db",-28},{" C#",-27},{" Dd",-26},{"vD",-24}

+    ,{" D",-24},{"^D",-23},{" D+",-22},{" Eb",-21},{" D#",-20},{" Ed",-19},{"vE",-18}

+    ,{" E",-17},{"^E",-16}                                                ,{"vF",-15}

+    ,{" F",-14},{"^F",-13},{" F+",-12},{" Gb",-11},{" F#",-10},{" Gd", -9},{"vG", -8}

+    ,{" G", -7},{"^G", -6},{" G+", -5},{" Ab", -4},{" G#", -3},{" Ad", -2},{"vA", -1}

+    ,{" A",  0},{"^A",  1},{" A+",  2},{" Bb",  3},{" A#",  4},{" Bd",  5},{"vB",  6}

+    ,{" B",  7},{"^B",  8}                                                ,{"vC",  9}

+  }},

+  { "53 EDO", 53, 22.6415, 

+    {{" C", -40},{"^C", -39},{">C",-38},{"vDb",-37},{"Db",-36}

+    ,{" C#",-35},{"^C#",-34},{"<D",-33},{"vD", -32}

+    ,{" D", -31},{"^D", -30},{">D",-29},{"vEb",-28},{"Eb",-27}

+    ,{" D#",-26},{"^D#",-25},{"<E",-24},{"vE", -23}

+    ,{" E", -22},{"^E", -21},{">E",-20},{"vF", -19}

+    ,{" F", -18},{"^F", -17},{">F",-16},{"vGb",-15},{"Gb",-14}

+    ,{" F#",-13},{"^F#",-12},{"<G",-11},{"vG", -10}

+    ,{" G",  -9},{"^G",  -8},{">G", -7},{"vAb", -6},{"Ab", -5}

+    ,{" G#", -4},{"^G#", -3},{"<A", -2},{"vA",  -1}

+    ,{" A",   0},{"^A",   1},{">A",  2},{"vBb",  3},{"Bb",  4}

+    ,{" A#",  5},{"^A#",  6},{"<B",  7},{"vB",   8}

+    ,{" B",   9},{"^B",  10},{"<C", 11},{"vC",  12}

+  }},

+  { "72 EDO", 72, 16.6667, 

+    {{" C", -54},{"^C", -53},{">C", -52},{" C+",-51},{"<C#",-50},{"vC#",-49}

+    ,{" C#",-48},{"^C#",-47},{">C#",-46},{" Dd",-45},{"<D" ,-44},{"vD" ,-43}

+    ,{" D", -42},{"^D", -41},{">D", -40},{" D+",-39},{"<Eb",-38},{"vEb",-37}

+    ,{" Eb",-36},{"^Eb",-35},{">Eb",-34},{" Ed",-33},{"<E" ,-32},{"vE" ,-31}

+    ,{" E", -30},{"^E", -29},{">E", -28},{" E+",-27},{"<F" ,-26},{"vF" ,-25}

+    ,{" F", -24},{"^F", -23},{">F", -22},{" F+",-21},{"<F#",-20},{"vF#",-19}

+    ,{" F#",-18},{"^F#",-17},{">F#",-16},{" Gd",-15},{"<G" ,-14},{"vG" ,-13}

+    ,{" G", -12},{"^G", -11},{">G", -10},{" G+", -9},{"<G#", -8},{"vG#", -7}

+    ,{" G#", -6},{"^G#", -5},{">G#", -4},{" Ad", -3},{"<A" , -2},{"vA" , -1}

+    ,{" A",   0},{"^A",   1},{">A",   2},{" A+",  3},{"<Bb",  4},{"vBb",  5}

+    ,{" Bb",  6},{"^Bb",  7},{">Bb",  8},{" Bd",  9},{"<B" , 10},{"vB" , 11}

+    ,{" B",  12},{"^B",  13},{">B",  14},{" Cd", 15},{"<C" , 16},{"vC" , 17}

+  }},

+  { "Bohlen-Pierce", 13, 146.304, 

+    {{"C",-10},{"Db",-9},{"D",-8},{"E",-7},{"F",-6},{"Gb",-5}

+    ,{"G",-4},{"H",-3},{"Jb",-2},{"J",-1},{"A",0},{"Bb",1},{"B",2}

+  }},

+  { "Carlos Alpha", 9, 77.9650, 

+    {{"I",0},{"I#",1},{"II-",2},{"II+",3},{"III",4}

+    ,{"III#",5},{"IV-",6},{"IV+",7},{"Ib",8}

+  }},

+  { "Carlos Beta", 11, 63.8329,

+    {{"I",0},{"I#",1},{"IIb",2},{"II",3},{"II#",4},{"III",5}

+    ,{"III#",6},{"IVb",7},{"IV",8},{"IV#",9},{"Ib",10}

+  }},

+  { "Carlos Gamma", 20, 35.0985,

+    {{" I",  0},{"^I",  1},{" IIb", 2},{"^IIb", 3},{" I#",   4},{"^I#",   5}

+    ,{" II", 6},{"^II", 7}

+    ,{" III",8},{"^III",9},{" IVb",10},{"^IVb",11},{" III#",12},{"^III#",13}

+    ,{" IV",14},{"^IV",15},{" Ib", 16},{"^Ib", 17},{" IV#", 18},{"^IV#", 19}

+  }},

+};

+

+paletteDef palette[] = {

+  // 12 EDO

+    {{ {HUE_NONE,    SAT_BW,    VALUE_NORMAL  }

+     , {HUE_PURPLE,  SAT_DULL,  VALUE_NORMAL  }

+     , {HUE_CYAN,    SAT_DULL,  VALUE_NORMAL  }

+     , {HUE_INDIGO,  SAT_VIVID, VALUE_NORMAL  }

+    }, {1,2,1,2,1,3,4,3,4,3,4,3}},

+  // 17 EDO

+    {{ {HUE_NONE,    SAT_BW,    VALUE_NORMAL  }

+     , {HUE_INDIGO,  SAT_VIVID, VALUE_NORMAL  }

+     , {HUE_RED,     SAT_VIVID, VALUE_NORMAL  }

+    }, {1,2,3,1,2,3,1,1,2,3,1,2,3,1,2,3,1}},

+  // 19 EDO

+    {{ {HUE_NONE,    SAT_BW,    VALUE_NORMAL  } // n

+     , {HUE_YELLOW,  SAT_VIVID, VALUE_NORMAL  } //  #

+     , {HUE_CYAN,    SAT_VIVID, VALUE_NORMAL  } //  b

+     , {HUE_MAGENTA, SAT_VIVID, VALUE_NORMAL  } // enh

+    }, {1,2,3,1,2,3,1,4,1,2,3,1,2,3,1,2,3,1,4}},

+  // 22 EDO

+    {{ {HUE_NONE,    SAT_BW,    VALUE_NORMAL  } // n

+     , {HUE_CYAN,    SAT_VIVID, VALUE_NORMAL  } // ^

+     , {HUE_MAGENTA, SAT_VIVID, VALUE_NORMAL  } // mid

+     , {HUE_YELLOW,  SAT_VIVID, VALUE_NORMAL  } // v

+    }, {1,2,3,4,1,2,3,4,1,1,2,3,4,1,2,3,4,1,2,3,4,1}},

+  // 24 EDO

+    {{ {HUE_NONE,    SAT_BW,    VALUE_NORMAL  } // n

+     , {HUE_GREEN,   SAT_DULL,  VALUE_SHADE } //  +

+     , {HUE_CYAN,    SAT_VIVID, VALUE_NORMAL  } //  #/b  

+     , {HUE_BLUE,    SAT_DULL,  VALUE_SHADE } //  d

+     , {HUE_CYAN,    SAT_DULL,  VALUE_SHADE } // enh

+    }, {1,2,3,4,1,2,3,4,1,5,1,2,3,4,1,2,3,4,1,2,3,4,1,5}},

+  // 31 EDO

+    {{ {HUE_NONE,    SAT_BW,    VALUE_NORMAL  } // n

+     , {HUE_RED,     SAT_DULL,  VALUE_NORMAL  } //  +

+     , {HUE_YELLOW,  SAT_DULL,  VALUE_SHADE } //  #

+     , {HUE_CYAN,    SAT_DULL,  VALUE_SHADE } //  b

+     , {HUE_INDIGO,  SAT_DULL,  VALUE_NORMAL  } //  d

+     , {HUE_RED,     SAT_DULL,  VALUE_SHADE } //  enh E+ Fb

+     , {HUE_INDIGO,  SAT_DULL,  VALUE_SHADE } //  enh E# Fd

+    }, {1,2,3,4,5,1,2,3,4,5,1,6,7,1,2,3,4,5,1,2,3,4,5,1,2,3,4,5,1,6,7}},

+  // 41 EDO

+    {{ {HUE_NONE,    SAT_BW,    VALUE_NORMAL  } // n

+     , {HUE_RED,     SAT_VIVID, VALUE_NORMAL  } //  ^

+     , {HUE_BLUE,    SAT_VIVID, VALUE_NORMAL  } //  +

+     , {HUE_CYAN,    SAT_DULL,  VALUE_SHADE } //  b

+     , {HUE_GREEN,   SAT_VIVID, VALUE_SHADE } //  #

+     , {HUE_MAGENTA, SAT_VIVID, VALUE_NORMAL  } //  d

+     , {HUE_YELLOW,  SAT_VIVID, VALUE_NORMAL  } //  v

+    }, {1,2,3,4,5,6,7,1,2,3,4,5,6,7,1,2,3,1,2,3,4,5,6,7,

+        1,2,3,4,5,6,7,1,2,3,4,5,6,7,1,6,7}},

+  // 53 EDO

+    {{ {HUE_NONE,    SAT_BW,    VALUE_NORMAL  } // n

+     , {HUE_ORANGE,  SAT_DULL,  VALUE_NORMAL  } //  ^

+     , {HUE_MAGENTA, SAT_DULL,  VALUE_NORMAL  } //  L

+     , {HUE_INDIGO,  SAT_VIVID, VALUE_NORMAL  } // bv

+     , {HUE_GREEN,   SAT_VIVID, VALUE_NORMAL  } // b

+     , {HUE_YELLOW,  SAT_VIVID, VALUE_NORMAL  } // #

+     , {HUE_RED,     SAT_VIVID, VALUE_NORMAL  } // #^

+     , {HUE_PURPLE,  SAT_DULL,  VALUE_NORMAL  } //  7

+     , {HUE_CYAN,    SAT_DULL,  VALUE_NORMAL  } //  v

+    }, {1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,1,2,3,9,1,2,3,4,5,6,7,8,9,

+        1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,1,2,3,9}},

+  // 72 EDO

+    {{ {HUE_NONE,    SAT_BW,    VALUE_NORMAL  } // n

+     , {HUE_GREEN,   SAT_DULL,  VALUE_SHADE } // ^

+     , {HUE_RED,     SAT_DULL,  VALUE_SHADE } // L

+     , {HUE_PURPLE,  SAT_DULL,  VALUE_SHADE } // +/d

+     , {HUE_BLUE,    SAT_DULL,  VALUE_SHADE } // 7

+     , {HUE_YELLOW,  SAT_DULL,  VALUE_SHADE } // v

+     , {HUE_INDIGO,  SAT_VIVID, VALUE_NORMAL  } // #/b

+    }, {1,2,3,4,5,6,7,2,3,4,5,6,1,2,3,4,5,6,7,2,3,4,5,6,1,2,3,4,5,6,1,2,3,4,5,6,

+        7,2,3,4,5,6,1,2,3,4,5,6,7,2,3,4,5,6,1,2,3,4,5,6,7,2,3,4,5,6,1,2,3,4,5,6}},

+  // BOHLEN PIERCE

+    {{ {HUE_NONE,    SAT_BW,    VALUE_NORMAL  }

+     , {HUE_INDIGO,  SAT_VIVID, VALUE_NORMAL  }

+     , {HUE_RED,     SAT_VIVID, VALUE_NORMAL  }

+    }, {1,2,3,1,2,3,1,1,2,3,1,2,3}},

+  // ALPHA

+    {{ {HUE_NONE,    SAT_BW,    VALUE_NORMAL  } // n

+     , {HUE_YELLOW,  SAT_VIVID, VALUE_NORMAL  } // #

+     , {HUE_INDIGO,  SAT_VIVID, VALUE_NORMAL  } // d

+     , {HUE_LIME,    SAT_VIVID, VALUE_NORMAL  } // +

+     , {HUE_RED,     SAT_VIVID, VALUE_NORMAL  } // enharmonic

+     , {HUE_CYAN,    SAT_VIVID, VALUE_NORMAL  } // b

+    }, {1,2,3,4,1,2,3,5,6}},

+  // BETA

+    {{ {HUE_NONE,    SAT_BW,    VALUE_NORMAL  } // n

+     , {HUE_INDIGO,  SAT_VIVID, VALUE_NORMAL  } // #

+     , {HUE_RED,     SAT_VIVID, VALUE_NORMAL  } // b

+     , {HUE_MAGENTA, SAT_DULL,  VALUE_NORMAL  } // enharmonic

+    }, {1,2,3,1,4,1,2,3,1,2,3}},

+  // GAMMA

+    {{ {HUE_NONE,    SAT_BW,    VALUE_NORMAL  } // n

+     , {HUE_RED,     SAT_VIVID, VALUE_NORMAL  } // b

+     , {HUE_BLUE,    SAT_VIVID, VALUE_NORMAL  } // #

+     , {HUE_YELLOW,  SAT_VIVID, VALUE_NORMAL  } // n^

+     , {HUE_PURPLE,  SAT_VIVID, VALUE_NORMAL  } // b^

+     , {HUE_GREEN,   SAT_VIVID, VALUE_NORMAL  } // #^

+    }, {1,4,2,5,3,6,1,4,1,4,2,5,3,6,1,4,2,5,3,6}},

+};

+

+layoutDef layoutOptions[] = {

+  { "Wicki-Hayden",      1, 64,   2,  -7, TUNING_12EDO },

+  { "Harmonic Table",    0, 75,  -7,   3, TUNING_12EDO },

+  { "Janko",             0, 65,  -1,  -1, TUNING_12EDO },

+  { "Gerhard",           0, 65,  -1,  -3, TUNING_12EDO },

+  { "Accordion C-sys.",  1, 75,   2,  -3, TUNING_12EDO },

+  { "Accordion B-sys.",  1, 64,   1,  -3, TUNING_12EDO },

+  { "Full Gamut",        1, 65,  -1,  -9, TUNING_12EDO },

+  { "Bosanquet-Wilson",  0, 65,  -2,  -1, TUNING_17EDO },

+  { "Neutral Thirds A",  0, 65,  -1,  -2, TUNING_17EDO },

+  { "Neutral Thirds B",  0, 65,   1,  -3, TUNING_17EDO },

+  { "Full Gamut",        1, 65,  -1,  -9, TUNING_17EDO },

+  { "Bosanquet-Wilson",  0, 65,  -1,  -2, TUNING_19EDO },

+  { "Kleismic",          0, 65,  -1,  -4, TUNING_19EDO },

+  { "Full Gamut",        1, 65,  -1,  -9, TUNING_19EDO },

+  { "Bosanquet-Wilson",  0, 65,  -3,  -1, TUNING_22EDO },

+  { "Porcupine",         0, 65,   1,  -4, TUNING_22EDO },

+  { "Full Gamut",        1, 65,   1,  -8, TUNING_22EDO },

+  { "Bosanquet-Wilson",  0, 65,  -1,  -3, TUNING_24EDO },

+  { "Inverted",          0, 65,   1,  -4, TUNING_24EDO },

+  { "Full Gamut",        1, 65,  -1,  -9, TUNING_24EDO },

+  { "Bosanquet-Wilson",  0, 65,  -2,  -3, TUNING_31EDO },

+  { "Double Bosanquet",  0, 65,  -1,  -4, TUNING_31EDO },

+  { "Anti-Double Bos.",  0, 65,   1,  -5, TUNING_31EDO },

+  { "Full Gamut",        1, 65,  -1,  -7, TUNING_31EDO },

+  { "Bosanquet-Wilson",  0, 65,  -4,  -3, TUNING_41EDO },  // forty-one #1

+  { "Gerhard",           0, 65,   3, -10, TUNING_41EDO },  // forty-one #2

+  { "Baldy",             0, 65,  -1,  -6, TUNING_41EDO },  

+  { "Rodan",             1, 65,  -1,  -7, TUNING_41EDO },  

+  { "Full Gamut",        0, 65,  -1,  -8, TUNING_41EDO },  // forty-one #3

+  { "Bosanquet-Wilson",  0, 65,  -5,  -4, TUNING_53EDO },

+  { "Kleismic A",        0, 65,  -8,  -3, TUNING_53EDO },

+  { "Kleismic B",        0, 65,  -5,  -3, TUNING_53EDO },

+  { "Wicki-Hayden",      1, 64,   9, -31, TUNING_53EDO },

+  { "Harmonic Table",    0, 75, -31,  14, TUNING_53EDO },

+  { "Buzzard",           0, 65,  -9,  -1, TUNING_53EDO },

+  { "Expanded Janko",    0, 65,  -1,  -6, TUNING_72EDO },

+  { "Full Gamut",        1, 65,  -1,  -9, TUNING_72EDO },

+  { "Standard",          0, 65,  -2,  -1, TUNING_BP },

+  { "Full Gamut",        1, 65,  -1,  -9, TUNING_BP },

+  { "Compressed",        0, 65,  -2,  -1, TUNING_ALPHA },

+  { "Full Gamut",        1, 65,  -1,  -9, TUNING_ALPHA },

+  { "Compressed",        0, 65,  -2,  -1, TUNING_BETA },

+  { "Full Gamut",        1, 65,  -1,  -9, TUNING_BETA },

+  { "Compressed",        0, 65,  -2,  -1, TUNING_GAMMA },

+  { "Full Gamut",        1, 65,  -1,  -9, TUNING_GAMMA }

+};

+

+scaleDef scaleOptions[] = {

+  { "None",              ALL_TUNINGS,      { 0 } },

+  // 12 EDO

+  { "Major",             TUNING_12EDO,     { 2,2,1,2,2,2,1 } },

+  { "Minor, natural",    TUNING_12EDO,     { 2,1,2,2,1,2,2 } },

+  { "Minor, melodic",    TUNING_12EDO,     { 2,1,2,2,2,2,1 } },

+  { "Minor, harmonic",   TUNING_12EDO,     { 2,1,2,2,1,3,1 } },

+  { "Pentatonic, major", TUNING_12EDO,     { 2,2,3,2,3 } },

+  { "Pentatonic, minor", TUNING_12EDO,     { 3,2,2,3,2 } },

+  { "Blues",             TUNING_12EDO,     { 3,1,1,1,1,3,2 } },

+  { "Double Harmonic",   TUNING_12EDO,     { 1,3,1,2,1,3,1 } },

+  { "Phrygian",          TUNING_12EDO,     { 1,2,2,2,1,2,2 } },

+  { "Phrygian Dominant", TUNING_12EDO,     { 1,3,1,2,1,2,2 } },

+  { "Dorian",            TUNING_12EDO,     { 2,1,2,2,2,1,2 } },

+  { "Lydian",            TUNING_12EDO,     { 2,2,2,1,2,2,1 } },

+  { "Lydian Dominant",   TUNING_12EDO,     { 2,2,2,1,2,1,2 } },

+  { "Mixolydian",        TUNING_12EDO,     { 2,2,1,2,2,1,2 } },

+  { "Locrian",           TUNING_12EDO,     { 1,2,2,1,2,2,2 } },

+  { "Whole tone",        TUNING_12EDO,     { 2,2,2,2,2,2 } },

+  { "Octatonic",         TUNING_12EDO,     { 2,1,2,1,2,1,2,1 } },

+  // 17 EDO; for more: https://en.xen.wiki/w/17edo#Scales

+  { "Diatonic",          TUNING_17EDO,  { 3,3,1,3,3,3,1 } },

+  { "Pentatonic",        TUNING_17EDO,  { 3,3,4,3,4 } },

+  { "Harmonic",          TUNING_17EDO,  { 3,2,3,2,2,2,3 } },

+  { "Husayni maqam",     TUNING_17EDO,  { 2,2,3,3,2,1,1,3 } },

+  { "Blues",             TUNING_17EDO,  { 4,3,1,1,1,4,3 } },

+  { "Hydra",             TUNING_17EDO,  { 3,3,1,1,2,3,2,1,1 } },

+  // 19 EDO; for more: https://en.xen.wiki/w/19edo#Scales

+  { "Diatonic",          TUNING_19EDO,   { 3,3,2,3,3,3,2 } },

+  { "Pentatonic",        TUNING_19EDO,   { 3,3,5,3,5 } },

+  { "Semaphore",         TUNING_19EDO,   { 3,1,3,1,3,3,1,3,1 } },

+  { "Negri",             TUNING_19EDO,   { 2,2,2,2,2,1,2,2,2,2 } },

+  { "Sensi",             TUNING_19EDO,   { 2,2,1,2,2,2,1,2,2,2,1 } },

+  { "Kleismic",          TUNING_19EDO,   { 1,3,1,1,3,1,1,3,1,3,1 } },

+  { "Magic",             TUNING_19EDO,   { 3,1,1,1,3,1,1,1,3,1,1,1,1 } },

+  { "Kind of blues",     TUNING_19EDO,   { 4,4,1,2,4,4 } },

+  // 22 EDO; for more: https://en.xen.wiki/w/22edo_modes

+  { "Diatonic",          TUNING_22EDO,  { 4,4,1,4,4,4,1 } },

+  { "Pentatonic",        TUNING_22EDO,  { 4,4,5,4,5 } },

+  { "Orwell",            TUNING_22EDO,  { 3,2,3,2,3,2,3,2,2 } },

+  { "Porcupine",         TUNING_22EDO,  { 4,3,3,3,3,3,3 } },

+  { "Pajara",            TUNING_22EDO,  { 2,2,3,2,2,2,3,2,2,2 } },

+  // 24 EDO; for more: https://en.xen.wiki/w/24edo_scales

+  { "Diatonic 12",       TUNING_24EDO, { 4,4,2,4,4,4,2 } },

+  { "Diatonic Soft",     TUNING_24EDO, { 3,5,2,3,5,4,2 } },

+  { "Diatonic Neutral",  TUNING_24EDO, { 4,3,3,4,3,4,3 } },

+  { "Pentatonic (12)",   TUNING_24EDO, { 4,4,6,4,6 } },

+  { "Pentatonic (Hába)", TUNING_24EDO, { 5,5,5,5,4 } },

+  { "Invert Pentatonic", TUNING_24EDO, { 6,3,6,6,3 } },

+  { "Rast maqam",        TUNING_24EDO, { 4,3,3,4,4,2,1,3 } },

+  { "Bayati maqam",      TUNING_24EDO, { 3,3,4,4,2,1,3,4 } },      

+  { "Hijaz maqam",       TUNING_24EDO, { 2,6,2,4,2,1,3,4 } },

+  { "8-EDO",             TUNING_24EDO, { 3,3,3,3,3,3,3,3 } },

+  { "Wyschnegradsky",    TUNING_24EDO, { 2,2,2,2,2,1,2,2,2,2,2,2,1 } },

+  // 31 EDO; for more: https://en.xen.wiki/w/31edo#Scales

+  { "Diatonic",          TUNING_31EDO,  { 5,5,3,5,5,5,3 } },

+  { "Pentatonic",        TUNING_31EDO,  { 5,5,8,5,8 } },

+  { "Harmonic",          TUNING_31EDO,  { 5,5,4,4,4,3,3,3 } },

+  { "Mavila",            TUNING_31EDO,  { 5,3,3,3,5,3,3,3,3 } },

+  { "Quartal",           TUNING_31EDO,  { 2,2,7,2,2,7,2,7 } },

+  { "Orwell",            TUNING_31EDO,  { 4,3,4,3,4,3,4,3,3 } },

+  { "Neutral",           TUNING_31EDO,  { 4,4,4,4,4,4,4,3 } },

+  { "Miracle",           TUNING_31EDO,  { 4,3,3,3,3,3,3,3,3,3 } },

+  // 41 EDO; for more: https://en.xen.wiki/w/41edo#Scales_and_modes

+  { "Diatonic",          TUNING_41EDO,   { 7,7,3,7,7,7,3 } },

+  { "Pentatonic",        TUNING_41EDO,   { 7,7,10,7,10 } },

+  { "Pure major",        TUNING_41EDO,   { 7,6,4,7,6,7,4 } },

+  { "5-limit chromatic", TUNING_41EDO,   { 4,3,4,2,4,3,4,4,2,4,3,4 } },

+  { "7-limit chromatic", TUNING_41EDO,   { 3,4,2,4,4,3,4,2,4,3,3,4 } },

+  { "Harmonic",          TUNING_41EDO,   { 5,4,4,4,4,3,3,3,3,3,2,3 } },

+  { "Middle East-ish",   TUNING_41EDO,   { 7,5,7,5,5,7,5 } },

+  { "Thai",              TUNING_41EDO,   { 6,6,6,6,6,6,5 } },

+  { "Slendro",           TUNING_41EDO,   { 8,8,8,8,9 } },

+  { "Pelog / Mavila",    TUNING_41EDO,   { 8,5,5,8,5,5,5 } },

+  // 53 EDO

+  { "Diatonic",          TUNING_53EDO, { 9,9,4,9,9,9,4 } },

+  { "Pentatonic",        TUNING_53EDO, { 9,9,13,9,13 } },

+  { "Rast makam",        TUNING_53EDO, { 9,8,5,9,9,4,4,5 } },

+  // 72 EDO

+  { "Diatonic",          TUNING_72EDO, { 12,12,6,12,12,12,6 } },

+  { "Pentatonic",        TUNING_72EDO, { 12,12,18,12,18 } },

+  // BP

+  // Alpha

+  // Beta

+  // Gamma

+};