about summary refs log tree commit diff
path: root/HexBoard_V1.1.ino
diff options
context:
space:
mode:
Diffstat (limited to 'HexBoard_V1.1.ino')
-rw-r--r--HexBoard_V1.1.ino3403
1 files changed, 1708 insertions, 1695 deletions
diff --git a/HexBoard_V1.1.ino b/HexBoard_V1.1.ino
index 014f1c5..fb03d9a 100644
--- a/HexBoard_V1.1.ino
+++ b/HexBoard_V1.1.ino
@@ -1,1772 +1,1785 @@
-// Copyright 2022-2023 Jared DeCook and Zach DeCook
-// Licensed under the GNU GPL Version 3.
-// Hardware Information:
-// Generic RP2040 running at 133MHz with 16MB of flash
-// https://github.com/earlephilhower/arduino-pico
-// (Additional boards manager URL: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json)
-// Tools > USB Stack > (Adafruit TinyUSB)
-// Sketch > Export Compiled Binary
-//
-// Brilliant resource for dealing with hexagonal coordinates. https://www.redblobgames.com/grids/hexagons/
-// Used this to get my hexagonal animations sorted. http://ondras.github.io/rot.js/manual/#hex/indexing
-
-// Menu library documentation https://github.com/Spirik/GEM
-
-#include <Arduino.h>
-#include <Adafruit_TinyUSB.h>
-#include "LittleFS.h"
-#include <MIDI.h>
-#include <Adafruit_NeoPixel.h>
-#define GEM_DISABLE_GLCD
-#include <GEM_u8g2.h>
-#include <Wire.h>
-#include <Rotary.h>
-
-// Change before compile depending on target hardware
-// 1 = HexBoard 1.0 (dev unit)
-// 2 = HexBoard 1.1 (first retail unit)
-#define ModelNumber 2
-
-// 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);
-
-// LED SETUP //
-#define LED_PIN 22
-#define LED_COUNT 140
-#if ModelNumber == 1
-Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_RGB + NEO_KHZ800);
-int stripBrightness = 110;
-int defaultBrightness = 70;
-int dimBrightness = 20;
-int pressedBrightness = 255;
-#elif ModelNumber == 2
-Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);
-int stripBrightness = 130;
-int defaultBrightness = 100;
-int dimBrightness = 26;
-int pressedBrightness = 255;
-#endif
-
-
-// ENCODER SETUP //
-#define ROTA 20  // Rotary encoder A
-#define ROTB 21  // Rotary encoder B
-Rotary rotary = Rotary(ROTA, ROTB);
-const int encoderClick = 24;
-int encoderState = 0;
-int encoderLastState = 1;
-int8_t encoder_val = 0;
-uint8_t encoder_state;
-
-// Create an instance of the U8g2 graphics library.
-#if ModelNumber == 1
-U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R2, /* reset=*/U8X8_PIN_NONE);
-#elif ModelNumber == 2
-U8G2_SH1107_SEEED_128X128_F_HW_I2C u8g2(U8G2_R2, /* reset=*/U8X8_PIN_NONE);
-#endif
-int screenBrightness = stripBrightness / 2;
-
-// Button matrix and LED locations, Dev Unit Only.
-// Prod unit has matrix columns swapped and LEDs in a sane order (see ROW_FLIP macro).
-// Portrait orientation top view:
-//            9   8   7   6   5   4   3   2   1
-//         20  19  18  17  16  15  14  13  12  11
-//           29  28  27  26  25  24  23  22  21
-//         40  39  38  37  36  35  34  33  32  31
-//           49  48  47  46  45  44  43  42  41
-//         60  59  58  57  56  55  54  53  52  51
-//   10      69  68  67  66  65  64  63  62  61
-// 30      80  79  78  77  76  75  74  73  72  71
-//   50      89  88  87  86  85  84  83  82  81
-// 70     100 99  98  97  96  95  94  93  92  91
-//   90     109 108 107 106 105 104 103 102 101
-//110     120 119 118 117 116 115 114 113 112 111
-//  130     129 128 127 126 125 124 123 122 121
-//        140 139 138 137 136 135 134 133 132 131
-
-// DIAGNOSTICS //
-// 1 = Full button test (1 and 0)
-// 2 = Button test (button number)
-// 3 = MIDI output test
-// 4 = Loop timing readout in milliseconds
-int diagnostics = 0;
-
-// BUTTON MATRIX PINS //
-#if ModelNumber == 1
-const byte columns[] = { 14, 15, 13, 12, 11, 10, 9, 8, 7, 6 };  // Column pins in order from right to left
-#elif ModelNumber == 2
-const byte columns[] = { 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };  // New board revision
-#endif
-const int m1p = 4;  // Multiplexing chip control pins
-const int m2p = 5;
-const int m4p = 2;
-const int m8p = 3;
-// 16 & 17 reserved for lights.
-const byte columnCount = sizeof(columns);          // The number of columns in the matrix
-const byte rowCount = 14;                          // The number of rows in the matrix
-const byte elementCount = columnCount * rowCount;  // The number of elements in the matrix
-
-// Since MIDI only uses 7 bits, we can give greater values special meanings.
-// (see commandPress)
-const byte EXTR_1 = 128;
-const byte EXTR_2 = 129;
-const byte EXTR_3 = 130;
-const byte EXTR_4 = 131;
-const byte EXTR_5 = 132;
-// start CMDB in a range that won't interfere with layouts.
-const byte CMDB_1 = 201;
-const byte CMDB_2 = 202;
-const byte CMDB_3 = 203;
-const byte CMDB_4 = 204;
-const byte CMDB_5 = 205;
-const byte CMDB_6 = 206;
-const byte CMDB_7 = 207;
-const byte UNUSED = 255;
-
-// LED addresses for CMD buttons. (consequencely, also the button address too)
-#if ModelNumber == 1
-const byte cmdBtn1 = 10 - 1;
-const byte cmdBtn2 = 30 - 1;
-const byte cmdBtn3 = 50 - 1;
-const byte cmdBtn4 = 70 - 1;
-const byte cmdBtn5 = 90 - 1;
-const byte cmdBtn6 = 110 - 1;
-const byte cmdBtn7 = 130 - 1;
-#else
-const byte cmdBtn1 = 0;
-const byte cmdBtn2 = 20;
-const byte cmdBtn3 = 40;
-const byte cmdBtn4 = 60;
-const byte cmdBtn5 = 80;
-const byte cmdBtn6 = 100;
-const byte cmdBtn7 = 120;
-#endif
-
-// MIDI NOTE LAYOUTS //
-#if ModelNumber == 1
-#define ROW_FLIP(x, ix, viii, vii, vi, v, iv, iii, ii, i) i, ii, iii, iv, v, vi, vii, viii, ix, x
-//hacky macro because I (Jared) messed up the board layout - I'll do better next time! xD
-#else
-#define ROW_FLIP(i, ii, iii, iv, v, vi, vii, viii, ix, x) i, ii, iii, iv, v, vi, vii, viii, ix, x
-//fixed it the second time around!
-#endif
-
-// MIDI note layout tables
-
-// ./makeLayout.py 90 2 -7
-const byte wickiHaydenLayout[elementCount] = {
-  ROW_FLIP(CMDB_1, 90, 92, 94, 96, 98, 100, 102, 104, 106),
-        ROW_FLIP(83, 85, 87, 89, 91, 93, 95, 97, 99, 101),
-  ROW_FLIP(CMDB_2, 78, 80, 82, 84, 86, 88, 90, 92, 94),
-        ROW_FLIP(71, 73, 75, 77, 79, 81, 83, 85, 87, 89),
-  ROW_FLIP(CMDB_3, 66, 68, 70, 72, 74, 76, 78, 80, 82),
-        ROW_FLIP(59, 61, 63, 65, 67, 69, 71, 73, 75, 77),
-  ROW_FLIP(CMDB_4, 54, 56, 58, 60, 62, 64, 66, 68, 70),
-        ROW_FLIP(47, 49, 51, 53, 55, 57, 59, 61, 63, 65),
-  ROW_FLIP(CMDB_5, 42, 44, 46, 48, 50, 52, 54, 56, 58),
-        ROW_FLIP(35, 37, 39, 41, 43, 45, 47, 49, 51, 53),
-  ROW_FLIP(CMDB_6, 30, 32, 34, 36, 38, 40, 42, 44, 46),
-        ROW_FLIP(23, 25, 27, 29, 31, 33, 35, 37, 39, 41),
-  ROW_FLIP(CMDB_7, 18, 20, 22, 24, 26, 28, 30, 32, 34),
-        ROW_FLIP(11, 13, 15, 17, 19, 21, 23, 25, 27, 29)
-};
-// ./makeLayout.py 95 -7 3
-const byte harmonicTableLayout[elementCount] = {
-  ROW_FLIP(CMDB_1, 95, 88, 81, 74, 67, 60, 53, 46, 39),
-        ROW_FLIP(98, 91, 84, 77, 70, 63, 56, 49, 42, 35),
-  ROW_FLIP(CMDB_2, 94, 87, 80, 73, 66, 59, 52, 45, 38),
-        ROW_FLIP(97, 90, 83, 76, 69, 62, 55, 48, 41, 34),
-  ROW_FLIP(CMDB_3, 93, 86, 79, 72, 65, 58, 51, 44, 37),
-        ROW_FLIP(96, 89, 82, 75, 68, 61, 54, 47, 40, 33),
-  ROW_FLIP(CMDB_4, 92, 85, 78, 71, 64, 57, 50, 43, 36),
-        ROW_FLIP(95, 88, 81, 74, 67, 60, 53, 46, 39, 32),
-  ROW_FLIP(CMDB_5, 91, 84, 77, 70, 63, 56, 49, 42, 35),
-        ROW_FLIP(94, 87, 80, 73, 66, 59, 52, 45, 38, 31),
-  ROW_FLIP(CMDB_6, 90, 83, 76, 69, 62, 55, 48, 41, 34),
-        ROW_FLIP(93, 86, 79, 72, 65, 58, 51, 44, 37, 30),
-  ROW_FLIP(CMDB_7, 89, 82, 75, 68, 61, 54, 47, 40, 33),
-        ROW_FLIP(92, 85, 78, 71, 64, 57, 50, 43, 36, 29)
-};
-// ./makeLayout.py 86 -1 -3
-const byte gerhardLayout[elementCount] = {
-  ROW_FLIP(CMDB_1, 86, 85, 84, 83, 82, 81, 80, 79, 78),
-        ROW_FLIP(83, 82, 81, 80, 79, 78, 77, 76, 75, 74),
-  ROW_FLIP(CMDB_2, 79, 78, 77, 76, 75, 74, 73, 72, 71),
-        ROW_FLIP(76, 75, 74, 73, 72, 71, 70, 69, 68, 67),
-  ROW_FLIP(CMDB_3, 72, 71, 70, 69, 68, 67, 66, 65, 64),
-        ROW_FLIP(69, 68, 67, 66, 65, 64, 63, 62, 61, 60),
-  ROW_FLIP(CMDB_4, 65, 64, 63, 62, 61, 60, 59, 58, 57),
-        ROW_FLIP(62, 61, 60, 59, 58, 57, 56, 55, 54, 53),
-  ROW_FLIP(CMDB_5, 58, 57, 56, 55, 54, 53, 52, 51, 50),
-        ROW_FLIP(55, 54, 53, 52, 51, 50, 49, 48, 47, 46),
-  ROW_FLIP(CMDB_6, 51, 50, 49, 48, 47, 46, 45, 44, 43),
-        ROW_FLIP(48, 47, 46, 45, 44, 43, 42, 41, 40, 39),
-  ROW_FLIP(CMDB_7, 44, 43, 42, 41, 40, 39, 38, 37, 36),
-        ROW_FLIP(41, 40, 39, 38, 37, 36, 35, 34, 33, 32)
-};
-// ./makeLayout.py 74 -1 -1
-const byte bosanquetWilsonLayout[elementCount] = {
-  ROW_FLIP(CMDB_1, 74, 73, 72, 71, 70, 69, 68, 67, 66),
-        ROW_FLIP(73, 72, 71, 70, 69, 68, 67, 66, 65, 64),
-  ROW_FLIP(CMDB_2, 71, 70, 69, 68, 67, 66, 65, 64, 63),
-        ROW_FLIP(70, 69, 68, 67, 66, 65, 64, 63, 62, 61),
-  ROW_FLIP(CMDB_3, 68, 67, 66, 65, 64, 63, 62, 61, 60),
-        ROW_FLIP(67, 66, 65, 64, 63, 62, 61, 60, 59, 58),
-  ROW_FLIP(CMDB_4, 65, 64, 63, 62, 61, 60, 59, 58, 57),
-        ROW_FLIP(64, 63, 62, 61, 60, 59, 58, 57, 56, 55),
-  ROW_FLIP(CMDB_5, 62, 61, 60, 59, 58, 57, 56, 55, 54),
-        ROW_FLIP(61, 60, 59, 58, 57, 56, 55, 54, 53, 52),
-  ROW_FLIP(CMDB_6, 59, 58, 57, 56, 55, 54, 53, 52, 51),
-        ROW_FLIP(58, 57, 56, 55, 54, 53, 52, 51, 50, 49),
-  ROW_FLIP(CMDB_7, 56, 55, 54, 53, 52, 51, 50, 49, 48),
-        ROW_FLIP(55, 54, 53, 52, 51, 50, 49, 48, 47, 46)
-};
-// This layout can't be created by makeLayout.py
-const byte ezMajorLayout[elementCount] = { //Testing layout viability - probably will make this generative so we can easily add scales once figured out
-  ROW_FLIP(CMDB_1, 91, 93, 95, 96, 98, 100, 101, 103, 105),
-        ROW_FLIP(84, 86, 88, 89, 91, 93, 95, 96, 98, 100),
-  ROW_FLIP(CMDB_2, 79, 81, 83, 84, 86, 88, 89, 91, 93),
-        ROW_FLIP(72, 74, 76, 77, 79, 81, 83, 84, 86, 88),
-  ROW_FLIP(CMDB_3, 67, 69, 71, 72, 74, 76, 77, 79, 81),
-        ROW_FLIP(60, 62, 64, 65, 67, 69, 71, 72, 74, 76),
-  ROW_FLIP(CMDB_4, 55, 57, 59, 60, 62, 64, 65, 67, 69),
-        ROW_FLIP(48, 50, 52, 53, 55, 57, 59, 60, 62, 64),
-  ROW_FLIP(CMDB_5, 43, 45, 47, 48, 50, 52, 53, 55, 57),
-        ROW_FLIP(36, 38, 40, 41, 43, 45, 47, 48, 50, 52),
-  ROW_FLIP(CMDB_6, 31, 33, 35, 36, 38, 40, 41, 43, 45),
-        ROW_FLIP(24, 26, 28, 29, 31, 33, 35, 36, 38, 40),
-  ROW_FLIP(CMDB_7, 19, 21, 23, 24, 26, 28, 29, 31, 33),
-        ROW_FLIP(12, 14, 16, 17, 19, 21, 23, 24, 26, 28)
-};
-// ./makeLayout.py 132 -1 -9
-const byte fullLayout[elementCount] = {
-  ROW_FLIP(CMDB_1, 132, 131, 130, 129, 128, 127, 126, 125, 124),
-        ROW_FLIP(123, 122, 121, 120, 119, 118, 117, 116, 115, 114),
-  ROW_FLIP(CMDB_2, 113, 112, 111, 110, 109, 108, 107, 106, 105),
-        ROW_FLIP(104, 103, 102, 101, 100, 99, 98, 97, 96, 95),
-  ROW_FLIP(CMDB_3, 94, 93, 92, 91, 90, 89, 88, 87, 86),
-        ROW_FLIP(85, 84, 83, 82, 81, 80, 79, 78, 77, 76),
-  ROW_FLIP(CMDB_4, 75, 74, 73, 72, 71, 70, 69, 68, 67),
-        ROW_FLIP(66, 65, 64, 63, 62, 61, 60, 59, 58, 57),
-  ROW_FLIP(CMDB_5, 56, 55, 54, 53, 52, 51, 50, 49, 48),
-        ROW_FLIP(47, 46, 45, 44, 43, 42, 41, 40, 39, 38),
-  ROW_FLIP(CMDB_6, 37, 36, 35, 34, 33, 32, 31, 30, 29),
-        ROW_FLIP(28, 27, 26, 25, 24, 23, 22, 21, 20, 19),
-  ROW_FLIP(CMDB_7, 18, 17, 16, 15, 14, 13, 12, 11, 10),
-        ROW_FLIP(9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
-};
-
-// ./makeLayout.py 99 -4 -3
-const byte fourtyone1[elementCount] = {
-  ROW_FLIP(CMDB_1, 99, 95, 91, 87, 83, 79, 75, 71, 67),
-        ROW_FLIP(96, 92, 88, 84, 80, 76, 72, 68, 64, 60),
-  ROW_FLIP(CMDB_2, 89, 85, 81, 77, 73, 69, 65, 61, 57),
-        ROW_FLIP(86, 82, 78, 74, 70, 66, 62, 58, 54, 50),
-  ROW_FLIP(CMDB_3, 79, 75, 71, 67, 63, 59, 55, 51, 47),
-        ROW_FLIP(76, 72, 68, 64, 60, 56, 52, 48, 44, 40),
-  ROW_FLIP(CMDB_4, 69, 65, 61, 57, 53, 49, 45, 41, 37),
-        ROW_FLIP(66, 62, 58, 54, 50, 46, 42, 38, 34, 30),
-  ROW_FLIP(CMDB_5, 59, 55, 51, 47, 43, 39, 35, 31, 27),
-        ROW_FLIP(56, 52, 48, 44, 40, 36, 32, 28, 24, 20),
-  ROW_FLIP(CMDB_6, 49, 45, 41, 37, 33, 29, 25, 21, 17),
-        ROW_FLIP(46, 42, 38, 34, 30, 26, 22, 18, 14, 10),
-  ROW_FLIP(CMDB_7, 39, 35, 31, 27, 23, 19, 15, 11, 7),
-        ROW_FLIP(36, 32, 28, 24, 20, 16, 12, 8, 4, 0)
-};
-
-// TODO: Don't make this go outside of the normal range
-// ./makeLayout.py 138 3 -10
-const byte fourtyone2[elementCount] = {
-  ROW_FLIP(CMDB_1, 138, 141, 144, 147, 150, 153, 156, 159, 162),
-        ROW_FLIP(128, 131, 134, 137, 140, 143, 146, 149, 152, 155),
-  ROW_FLIP(CMDB_2, 121, 124, 127, 130, 133, 136, 139, 142, 145),
-        ROW_FLIP(111, 114, 117, 120, 123, 126, 129, 132, 135, 138),
-  ROW_FLIP(CMDB_3, 104, 107, 110, 113, 116, 119, 122, 125, 128),
-        ROW_FLIP(94, 97, 100, 103, 106, 109, 112, 115, 118, 121),
-  ROW_FLIP(CMDB_4, 87, 90, 93, 96, 99, 102, 105, 108, 111),
-        ROW_FLIP(77, 80, 83, 86, 89, 92, 95, 98, 101, 104),
-  ROW_FLIP(CMDB_5, 70, 73, 76, 79, 82, 85, 88, 91, 94),
-        ROW_FLIP(60, 63, 66, 69, 72, 75, 78, 81, 84, 87),
-  ROW_FLIP(CMDB_6, 53, 56, 59, 62, 65, 68, 71, 74, 77),
-        ROW_FLIP(43, 46, 49, 52, 55, 58, 61, 64, 67, 70),
-  ROW_FLIP(CMDB_7, 36, 39, 42, 45, 48, 51, 54, 57, 60),
-        ROW_FLIP(26, 29, 32, 35, 38, 41, 44, 47, 50, 53)
-};
-
-// TODO: Don't make this go outside of the normal range
-// ./makeLayout.py 152 -1 -8
-const byte fourtyone3[elementCount] = {
-  ROW_FLIP(CMDB_1, 152, 151, 150, 149, 148, 147, 146, 145, 144),
-        ROW_FLIP(144, 143, 142, 141, 140, 139, 138, 137, 136, 135),
-  ROW_FLIP(CMDB_2, 135, 134, 133, 132, 131, 130, 129, 128, 127),
-        ROW_FLIP(127, 126, 125, 124, 123, 122, 121, 120, 119, 118),
-  ROW_FLIP(CMDB_3, 118, 117, 116, 115, 114, 113, 112, 111, 110),
-        ROW_FLIP(110, 109, 108, 107, 106, 105, 104, 103, 102, 101),
-  ROW_FLIP(CMDB_4, 101, 100, 99, 98, 97, 96, 95, 94, 93),
-        ROW_FLIP(93, 92, 91, 90, 89, 88, 87, 86, 85, 84),
-  ROW_FLIP(CMDB_5, 84, 83, 82, 81, 80, 79, 78, 77, 76),
-        ROW_FLIP(76, 75, 74, 73, 72, 71, 70, 69, 68, 67),
-  ROW_FLIP(CMDB_6, 67, 66, 65, 64, 63, 62, 61, 60, 59),
-        ROW_FLIP(59, 58, 57, 56, 55, 54, 53, 52, 51, 50),
-  ROW_FLIP(CMDB_7, 50, 49, 48, 47, 46, 45, 44, 43, 42),
-        ROW_FLIP(42, 41, 40, 39, 38, 37, 36, 35, 34, 33)
-};
-
-const byte* currentLayout = wickiHaydenLayout;
-
-// These are for standard tuning only
-// We start an octave above standard midi because integer hertz only have so much resolution.
-const unsigned int pitches[128] = {
-  16, 17, 18, 19, 21, 22, 23, 25, 26, 28, 29, 31,                                  // Octave 0
-  33, 35, 37, 39, 41, 44, 46, 49, 52, 55, 58, 62,                                  // Octave 1
-  65, 69, 73, 78, 82, 87, 93, 98, 104, 110, 117, 123,                              // Octave 2
-  131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247,                      // Octave 3
-  262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494,                      // Octave 4
-  523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988,                      // Octave 5
-  1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976,          // Octave 6
-  2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951,          // Octave 7
-  4186, 4435, 4699, 4978, 5274, 5588, 5920, 6272, 6645, 7040, 7459, 7902,          // Octave 8
-  8372, 8870, 9397, 9956, 10548, 11175, 11840, 12544, 13290, 14080, 14917, 15804,  //9
-  16744,                                                                           // C10
-  17740,                                                                           // C#10
-  18795,                                                                           // D10
-  19912,                                                                           // D#10
-  21096,                                                                           // E10
-  22350,                                                                           // F10
-  23680                                                                            // F#10
-};
-// ./makePitches.py 220 19 -14
-const unsigned int pitches19[128] = {
-// start close to C3
-                         132, 137, 142, 147, 153, 158, 164, 170, 177, 183, 190, 197, 205, 212,
-220, 228, 237, 245, 255, 264, 274, 284, 295, 306, 317, 329, 341, 354, 367, 380, 394, 409, 424,
-// Octave starting at A=440
-440, 456, 473, 491, 509, 528, 548, 568, 589, 611, 634, 657, 682, 707, 733, 761, 789, 818, 848,
-880, 913, 947, 982, 1018, 1056, 1095, 1136, 1178, 1222, 1267, 1315, 1363, 1414, 1467, 1521, 1578, 1636, 1697,
-1760, 1825, 1893, 1964, 2037, 2112, 2191, 2272, 2356, 2444, 2535, 2629, 2727, 2828, 2933, 3042, 3155, 3272, 3394,
-3520, 3651, 3786, 3927, 4073, 4224, 4381, 4544, 4713, 4888, 5070, 5258, 5453, 5656, 5866, 6084, 6310, 6545, 6788,
-7040, 7302, 7573, 7854, 8146, 8449, 8763, 9088, 9426, 9776, 10139, 10516, 10907, 11312, 11732, 12168, 12620, 13089, 13576
-};
-// ./makePitches.py 220 24 -18
-const unsigned int pitches24[128] = {
-// start close to C3
-                              131, 135, 139, 143, 147, 151, 156, 160, 165, 170, 175, 180, 185, 190, 196, 202, 208, 214,
-220, 226, 233, 240, 247, 254, 262, 269, 277, 285, 294, 302, 311, 320, 330, 339, 349, 359, 370, 381, 392, 403, 415, 427,
-// Octave starting at A=440
-440, 453, 466, 480, 494, 508, 523, 539, 554, 571, 587, 605, 622, 640, 659, 679, 698, 719, 740, 762, 784, 807, 831, 855,
-880, 906, 932, 960, 988, 1017, 1047, 1077, 1109, 1141, 1175, 1209, 1245, 1281, 1319, 1357, 1397, 1438, 1480, 1523, 1568, 1614, 1661, 1710,
-1760, 1812, 1865, 1919, 1976, 2033, 2093, 2154, 2217, 2282, 2349, 2418, 2489, 2562, 2637, 2714, 2794, 2876, 2960, 3047, 3136, 3228, 3322, 3420,
-3520, 3623, 3729, 3839, 3951, 4067, 4186, 4309, 4435, 4565, 4699, 4836, 4978, 5124
-};
-// ./makePitches.py 220 31 -23
-const unsigned int pitches31[128] = {
-// Start close to C
-                                        132, 135, 138, 141, 144, 147, 150, 154, 157, 161, 165, 168, 172, 176, 180, 184, 188, 192, 197, 201, 206, 210, 215,
-220, 225, 230, 235, 241, 246, 252, 257, 263, 269, 275, 281, 288, 294, 301, 308, 315, 322, 329, 336, 344, 352, 360, 368, 376, 385, 393, 402, 411, 421, 430,
-440, 450, 460, 471, 481, 492, 503, 515, 526, 538, 550, 563, 575, 588, 602, 615, 629, 643, 658, 673, 688, 704, 720, 736, 753, 770, 787, 805, 823, 842, 861,
-880, 900, 920, 941, 962, 984, 1006, 1029, 1052, 1076, 1100, 1125, 1151, 1177, 1203, 1231, 1258, 1287, 1316, 1346, 1376, 1407, 1439, 1472, 1505, 1539, 1574, 1609, 1646, 1683, 1721,
-1760, 1800, 1840, 1882, 1925, 1968, 2013, 2058, 2105, 2152, 2201, 2251
-};
-
-// 41-TET 41 equal temperament
-// ./makePitches.py 220 41 +10
-const unsigned int pitches41[128] = {
-// Start close to C4
-261, 265, 269, 274, 279, 284, 288, 293, 298, 303, 309, 314, 319, 325, 330, 336, 341, 347, 353, 359, 365, 372, 378, 384, 391, 398, 404, 411, 418, 425, 433,
-// Octave starting at A=440
-440, 448, 455, 463, 471, 479, 487, 495, 504, 512, 521, 530, 539, 548, 557, 567, 577, 587, 597, 607, 617, 628, 638, 649, 660, 671, 683, 695, 706, 718, 731, 743, 756, 769, 782, 795, 809, 822, 836, 851, 865,
-880, 895, 910, 926, 942, 958, 974, 991, 1007, 1025, 1042, 1060, 1078, 1096, 1115, 1134, 1153, 1173, 1193, 1213, 1234, 1255, 1276, 1298, 1320, 1343, 1366, 1389, 1413, 1437, 1461, 1486, 1512, 1537, 1564, 1590, 1617, 1645, 1673, 1701, 1730,
-1760, 1790, 1821, 1852, 1883, 1915, 1948, 1981, 2015, 2049, 2084, 2120, 2156, 2193, 2230
-};
-// ./makePitches.py 220 72 18
-const unsigned int pitches72[128] = {
-262, 264, 267, 269, 272, 275, 277, 280, 283, 285, 288, 291, 294, 297, 299, 302, 305, 308, 311, 314, 317, 320, 323, 326, 330, 333, 336, 339, 343, 346, 349, 353, 356, 359, 363, 366, 370, 374, 377, 381, 385, 388, 392, 396, 400, 403, 407, 411, 415, 419, 423, 427, 432, 436, 440, 444, 449, 453, 457, 462, 466, 471, 475, 480, 484, 489, 494, 499, 503, 508, 513, 518, 523, 528, 533, 539, 544, 549, 554, 560, 565, 571, 576, 582, 587, 593, 599, 605, 610, 616, 622, 628, 634, 640, 647, 653, 659, 666, 672, 679, 685, 692, 698, 705, 712, 719, 726, 733, 740, 747, 754, 762, 769, 776, 784, 792, 799, 807, 815, 823, 831, 839, 847, 855, 863, 872, 880, 889
-};
-#define TONEPIN 23
-
-// Global time variables
-unsigned long currentTime = 0;   // Program loop consistent variable for time in milliseconds since power on
-unsigned long previousTime = 0;  // Used to check speed of the loop in diagnostics mode 4
-int loopTime = 0;                // Used to keep track of how long each loop takes. Useful for rate-limiting.
-int screenTime = 0;              // Used to dim screen after a set time to prolong the lifespan of the OLED
-
-// Tone and Arpeggiator variables
-int arpTime = 0;            // Measures time per note
-int arpThreshold = 20;   // Set arp speed (in milliseconds)
-byte curr_pitch = 128;
-// Keep track of whether this note is held or not.
-bool pitchRef[256];
-
-// Pitch bend and mod wheel variables
-int pitchBendNeutral = 0;   // The center position for the pitch bend "wheel." Could be adjusted for global tuning?
-int pitchBendPosition = 0;  // The actual pitch bend variable used for sending MIDI
-int pitchBendSpeed = 1024;  // The amount the pitch bend moves every time modPitchTime hits it's limit.
-int modPitchTime = 0;       // Used to rate-limit pitch bend and mod wheel updates
-byte modWheelPosition = 0;  // Actual mod wheel variable used for sending MIDI
-byte modWheelSpeed = 6;     // The amount the mod wheel moves every time modPitchTime hits it's limit.
-bool pitchModToggle = 1;    // Used to toggle between pitch bend and mod wheel
-
-// Variables for holding digital button states and activation times
-byte activeButtons[elementCount];               // Array to hold current note button states
-byte previousActiveButtons[elementCount];       // Array to hold previous note button states for comparison
-unsigned long activeButtonsTime[elementCount];  // Array to track last note button activation time for debounce
-byte animationStep[elementCount];               // Array to track reactive lighting steps
-byte cycleNumber = 0;                           // Used for animations that have a fixed cycle
-int animationTime = 0;                          // Used for tracking how long since last lighting update
-
-// 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();
-  }
-}
-
-// 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 menuPageLayout("Layout");
-GEMPage menuPageTesting("Unfinished Alpha Tests");
-
-GEMItem menuItemLayout("Layout", menuPageLayout);
-void wickiHayden();  //Forward declarations
-void harmonicTable();
-void gerhard();
-void bosanquetWilson();
-void ezMajor();
-void full();
-void fortyone1();
-void fortyone2();
-void fortyone3();
-GEMItem menuItemWickiHayden("Wicki-Hayden", wickiHayden);
-GEMItem menuItemHarmonicTable("Harmonic Table", harmonicTable);
-GEMItem menuItemGerhard("Gerhard", gerhard);
-GEMItem menuItemBosanquetWilson("Bosanquet-Wilson", bosanquetWilson);
-GEMItem menuItemEzMajor("EZ Major", ezMajor);
-GEMItem menuItemFull("Full", full);
-GEMItem menuItem411("41-1", fortyone1);
-GEMItem menuItem412("41-2", fortyone2);
-GEMItem menuItem413("41-3", fortyone3);
-
-void setLayoutLEDs();  //Forward declaration
-byte key = 0;
-SelectOptionByte selectKeyOptions[] = { { "C", 0 }, { "C#", 1 }, { "D", 2 }, { "D#", 3 }, { "E", 4 }, { "F", 5 }, { "F#", 6 }, { "G", 7 }, { "G#", 8 }, { "A", 9 }, { "A#", 10 }, { "B", 11 } };
-GEMSelect selectKey(sizeof(selectKeyOptions) / sizeof(SelectOptionByte), selectKeyOptions);
-GEMItem menuItemKey("Key:", key, selectKey, setLayoutLEDs);
-
-byte scale = 0;
-SelectOptionByte selectScaleOptions[] = {
-  { "ALL", 0 }, { "Major", 1 }, { "HarMin", 2 }, { "MelMin", 3 }, { "NatMin", 4 }, { "PentMaj", 5 },
-  { "PentMin", 6 }, { "Blues", 7 }, { "DoubHar", 8 }, { "Phrygia", 9 }, { "PhryDom", 10 }, { "Dorian", 11 },
-  { "Lydian", 12 }, { "Mixolyd", 13 }, { "Locrian", 14 }, { "NONE", 15 }
+// ====== Hexperiment
+  // Sketch to program the hexBoard to handle microtones
+  // March 2024, theHDM / Nicholas Fox
+  // major thanks to Zach and Jared!
+  // Arduino IDE setup:
+  // Board = Generic RP2040 (use the following additional board manager repo:
+  // https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json)
+  // Patches needed for U8G2, Rotary.h
+  // ==============================================================================
+  // list of things remaining to do:
+  // -- program the wheel -- OK!
+  // -- put back the animations -- OK!
+  // -- test MPE working on iPad garageband -- works on pianoteq
+without MPE; need to get powered connection to iOS.
+  // -- volume control test on buzzer
+  // -- save and load presets
+  // -- sequencer restore
+  // ==============================================================================
+  //
+  #include <Arduino.h>
+  #include <Wire.h>
+  #include <LittleFS.h>
+  #include <queue>              // std::queue construct to store open
+channels in microtonal mode
+  const byte diagnostics = 1;
+// ====== initialize timers
+
+  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
+
+// ====== initialize SDA and SCL pins for hardware I/O
+
+  const byte lightPinSDA = 16;
+  const byte lightPinSCL = 17;
+
+// ====== initialize MIDI
+
+  #include <Adafruit_TinyUSB.h>
+  #include <MIDI.h>
+  Adafruit_USBD_MIDI usb_midi;
+  MIDI_CREATE_INSTANCE(Adafruit_USBD_MIDI, usb_midi, MIDI);
+  float concertA = 440.0;               // tuning of A4 in Hz
+  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.
+  int16_t channelBend[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+0, 0, 0 };  // what's the current note bend on this channel
+  byte channelPoly[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+0, 0 };      // how many notes are playing on this channel
+  std::queue<byte> openChannelQueue;
+  const byte defaultPBRange = 2;
+
+// ====== initialize LEDs
+
+  #include <Adafruit_NeoPixel.h>
+  const byte multiplexPins[] = { 4, 5, 2, 3 };  // m1p, m2p, m4p, m8p
+  const byte rowCount = 14;                     // The number of rows
+in the matrix
+  const byte columnPins[] = { 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
+  const byte colCount = sizeof(columnPins);  // The number of columns
+in the matrix
+  const byte hexCount = colCount * rowCount;  // The number of
+elements in the matrix
+  const byte LEDPin = 22;
+  Adafruit_NeoPixel strip(hexCount, LEDPin, NEO_GRB + NEO_KHZ800);
+  enum { NoAnim, StarAnim, SplashAnim, OrbitAnim, OctaveAnim, NoteAnim };
+  byte animationType = 0;
+  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.
+// ====== initialize hex state object
+
+  enum { Right, UpRight, UpLeft, Left, DnLeft, DnRight };
+  typedef struct {
+    int8_t row;
+    int8_t col;
+  } coordinates;
+  typedef struct {
+    byte keyState = 0;          // binary 00 = off, 01 = just pressed,
+10 = just released, 11 = held
+    coordinates coords = {0,0};
+    uint32_t timePressed = 0;   // timecode of last press
+    uint32_t LEDcolorAnim = 0;      //
+    uint32_t LEDcolorPlay = 0;      //
+    uint32_t LEDcolorOn = 0;   //
+    uint32_t LEDcolorOff = 0;  //
+    bool animate = 0;           // hex is flagged as part of the
+animation in this frame
+    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 = 255;            // 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;
+  buttonDef h[hexCount]; // array of hex objects from 0 to 139
+  const byte assignCmd[] = { 0, 20, 40, 60, 80, 100, 120 };
+  const byte cmdCount = 7;
+  const byte cmdCode = 192;
+
+// ====== initialize wheel emulation
+
+  const uint16_t ccMsgCoolDown = 20; // 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
   };
-GEMSelect selectScale(sizeof(selectScaleOptions) / sizeof(SelectOptionByte), selectScaleOptions);
-GEMItem menuItemScale("Scale:", scale, selectScale, applySelectedScale);
-
-const bool (*selectedScale)[12];
-// Scale arrays of boolean (for O(1) access instead of O(12/2))
-//                                     0 1 2 3 4 5 6 7 8 9 X E
-const bool noneScale[12]            = {0,0,0,0,0,0,0,0,0,0,0,0};
-const bool chromaticScale[12]       = {1,1,1,1,1,1,1,1,1,1,1,1};
-const bool majorScale[12]           = {1,0,1,0,1,1,0,1,0,1,0,1};
-const bool harmonicMinorScale[12]   = {1,0,1,1,0,1,0,1,1,0,0,1};
-const bool melodicMinorScale[12]    = {1,0,1,1,0,1,0,1,0,1,0,1};
-const bool naturalMinorScale[12]    = {1,0,1,1,0,1,0,1,1,0,1,0};
-const bool pentatonicMajorScale[12] = {1,0,1,0,1,0,0,1,0,1,0,0};
-const bool pentatonicMinorScale[12] = {1,0,0,1,0,1,0,1,0,0,1,0};
-const bool bluesScale[12]           = {1,0,0,1,0,1,1,1,0,0,1,0};
-const bool doubleHarmonicScale[12]  = {1,1,0,0,1,1,0,1,1,0,0,1};
-const bool phrygianScale[12]        = {1,1,0,1,0,1,0,1,1,0,1,0};
-const bool phrygianDominantScale[12]= {1,1,0,0,1,1,0,1,1,0,1,0};
-const bool dorianScale[12]          = {1,0,1,1,0,1,0,1,0,1,1,0};
-const bool lydianScale[12]          = {1,0,1,0,1,0,1,1,0,1,0,1};
-const bool mixolydianScale[12]      = {1,0,1,0,1,1,0,1,0,1,1,0};
-const bool locrianScale[12]         = {1,1,0,1,0,1,1,0,1,0,1,0};
-
-// Function to apply the selected scale
-void applySelectedScale() {
-  switch (scale) {
-    case 0:  // All notes
-      selectedScale = &chromaticScale;
-      break;
-    case 1:  // Major scale
-      selectedScale = &majorScale;
-      break;
-    case 2:  // Harmonic minor scale
-      selectedScale = &harmonicMinorScale;
-      break;
-    case 3:  // Melodic minor scale
-      selectedScale = &melodicMinorScale;
-      break;
-    case 4:  // Natural minor scale
-      selectedScale = &naturalMinorScale;
-      break;
-    case 5:  // Pentatonic major scale
-      selectedScale = &pentatonicMajorScale;
-      break;
-    case 6:  // Pentatonic minor scale
-      selectedScale = &pentatonicMinorScale;
-      break;
-    case 7:  // Blues scale
-      selectedScale = &bluesScale;
-      break;
-    case 8:  // Double Harmonic scale
-      selectedScale = &doubleHarmonicScale;
-      break;
-    case 9:  // Phrygian scale
-      selectedScale = &phrygianScale;
-      break;
-    case 10:  // Phrygian Dominant scale
-      selectedScale = &phrygianDominantScale;
-      break;
-    case 11:  // Dorian scale
-      selectedScale = &dorianScale;
-      break;
-    case 12:  // Lydian scale
-      selectedScale = &lydianScale;
-      break;
-    case 13:  // Mixolydian scale
-      selectedScale = &mixolydianScale;
-      break;
-    case 14:  // Locrian scale
-      selectedScale = &locrianScale;
-      break;
-    default: // Dim all LEDs
-      selectedScale = &noneScale;
-      break;
-  }
-  setLayoutLEDs();
-}
-
-bool scaleLock = false;  // For disabling all keys not in the selected scale
-GEMItem menuItemScaleLock("Scale Lock:", scaleLock, setLayoutLEDs);
-
-int transpose = 0;
-SelectOptionInt selectTransposeOptions[] = {
-  { "-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 }
-};
-GEMSelect selectTranspose(sizeof(selectTransposeOptions) / sizeof(SelectOptionByte), selectTransposeOptions);
-void validateTranspose();  // Forward declaration
-GEMItem menuItemTranspose("Transpose:", transpose, selectTranspose, validateTranspose);
-
-SelectOptionInt selectBendSpeedOptions[] = { { "too slo", 128 }, { "Turtle", 256 }, { "Slow", 512 }, { "Medium", 1024 }, { "Fast", 2048 }, { "Cheetah", 4096 }, { "Instant", 16384 } };
-GEMSelect selectBendSpeed(sizeof(selectBendSpeedOptions) / sizeof(SelectOptionInt), selectBendSpeedOptions);
-GEMItem menuItemBendSpeed("Pitch Bend:", pitchBendSpeed, selectBendSpeed);
-
-SelectOptionByte selectModSpeedOptions[] = { { "too slo", 1 }, { "Turtle", 2 }, { "Slow", 3 }, { "Medium", 6 }, { "Fast", 12 }, { "Cheetah", 32 }, { "Instant", 127 } };
-GEMSelect selectModSpeed(sizeof(selectModSpeedOptions) / sizeof(SelectOptionByte), selectModSpeedOptions);
-GEMItem menuItemModSpeed("Mod Wheel:", modWheelSpeed, selectModSpeed);
-
-void setBrightness();  //Forward declaration
-#if ModelNumber == 1
-SelectOptionByte selectBrightnessOptions[] = { { "Night", 10 }, { "Dim", 30 }, { "Low", 70 }, { "Medium", 110 }, { "High", 160 }, { "Higher", 210 }, { "MAX(!!)", 255 } };
-#elif ModelNumber == 2  // Reducing options to simplify and because the lights aren't as bright.
-SelectOptionByte selectBrightnessOptions[] = { { "Dim", 20 }, { "Low", 70 }, { "Medium", 130 }, { "High", 190 }, { "Max", 255 } };
-#endif
-GEMSelect selectBrightness(sizeof(selectBrightnessOptions) / sizeof(SelectOptionByte), selectBrightnessOptions);
-GEMItem menuItemBrightness("Brightness:", stripBrightness, selectBrightness, setBrightness);
-
-byte lightMode = 0;
-SelectOptionByte selectLightingOptions[] = { { "Button", 0 }, { "Note", 1 }, { "Octave", 2 }, { "Splash", 3 }, { "Star", 4 }, { "Orbit", 5} };
-GEMSelect selectLighting(sizeof(selectLightingOptions) / sizeof(SelectOptionByte), selectLightingOptions);
-GEMItem menuItemLighting("Lighting:", lightMode, selectLighting);
-
-byte colorMode = 0;
-SelectOptionByte selectColorOptions[] = { { "Scale", 0 }, { "Tone", 1 }};
-GEMSelect selectColor(sizeof(selectColorOptions) / sizeof(SelectOptionByte), selectColorOptions);
-GEMItem menuItemColor("Color:", colorMode, selectColor);
-
-int buzzer = 0;  // For enabling built-in buzzer for sound generation without a computer
-#define BUZZER_ARP_UP 2
-#define BUZZER_ARP_DOWN 3
-SelectOptionInt selectBuzzerOptions[] = {{"Off", 0}, {"Mono", 1}, {"Arp Up", BUZZER_ARP_UP}, {"Arp Dwn", BUZZER_ARP_DOWN}};
-GEMSelect selectBuzzer(sizeof(selectBuzzerOptions)/sizeof(SelectOptionInt), selectBuzzerOptions);
-GEMItem menuItemBuzzer("Buzzer:", buzzer, selectBuzzer);
-
-// For use when testing out unfinished features
-GEMItem menuItemTesting("Testing", menuPageTesting);
-boolean release = false;  // Whether this is a release or not
-GEMItem menuItemVersion("V0.6.y ", release, GEM_READONLY);
-void sequencerSetup();  //Forward declaration
-// For enabling basic sequencer mode - not complete
-GEMItem menuItemSequencer("Sequencer:", sequencerMode, sequencerSetup);
-
-int tones = 12; // Experimental microtonal support
-// TODO: consider adding 17, 22, 53
-SelectOptionInt selectTonesOptions[] = {{"12", 12}, {"19", 19}, {"24", 24}, {"31", 31}, {"41", 41}, {"72", 72}};
-GEMSelect selectTones(sizeof(selectTonesOptions)/sizeof(SelectOptionInt), selectTonesOptions);
-GEMItem menuItemTones("Tones:", tones, selectTones);
-
-SelectOptionInt selectArpOptions[] = {{"Fast", 20}, {"Medium", 40}, {"Slow", 80}, {"sloooow", 120}};
-GEMSelect selectArp(sizeof(selectArpOptions)/sizeof(SelectOptionInt), selectArpOptions);
-GEMItem menuItemArp("Arp Speed:", arpThreshold, selectArp);
-
-// Create menu object of class GEM_u8g2. Supply its constructor with reference to u8g2 object we created earlier
-byte menuItemHeight = 10;
-byte menuPageScreenTopOffset = 10;
-byte menuValuesLeftOffset = 78;
-GEM_u8g2 menu(u8g2, GEM_POINTER_ROW, GEM_ITEMS_COUNT_AUTO, menuItemHeight, menuPageScreenTopOffset, menuValuesLeftOffset);
-
-
-// MIDI channel assignment
-byte midiChannel = 1;  // Current MIDI channel (changed via user input)
-
-// Velocity levels
-byte midiVelocity = 100;  // Default velocity
-
-// END SETUP SECTION
-// ------------------------------------------------------------------------------------------------------------------------------------------------------------
-
-void setup() {
-  // sequencer mode midi instruments for each "lane".
-  lanes[0].instrument = 36;  // Bass Drum 1
-  lanes[1].instrument = 40;  // Electric Snare
-  lanes[2].instrument = 46;  // Open Hi-Hat
-  lanes[3].instrument = 42;  // Closed Hi-Hat
-  lanes[4].instrument = 49;  // Crash Cymbal 1
-  lanes[5].instrument = 45;  // Low Tom
-  lanes[6].instrument = 50;  // Hi Tom
-
-#if defined(ARDUINO_ARCH_MBED) && defined(ARDUINO_ARCH_RP2040)
-  // Manual begin() is required on core without built-in support for TinyUSB such as mbed rp2040
-  TinyUSB_Device_Init(0);
-#endif
-  usb_midi.setStringDescriptor("HexBoard MIDI");
-  // Initialize MIDI, and listen to all MIDI channels
-  // This will also call usb_midi's begin()
-  MIDI.begin(MIDI_CHANNEL_OMNI);
-  // Callback will be run by MIDI.read()
-  MIDI.setHandleNoteOn(handleNoteOn);
-  //MIDI.setHandleTimeCodeQuarterFrame(handleTimeCodeQuarterFrame);
-
-  Wire.setSDA(16);
-  Wire.setSCL(17);
-
-  pinMode(encoderClick, INPUT_PULLUP);
-
-  Serial.begin(115200);  // Set serial to make uploads work without bootsel button
-
-  LittleFSConfig cfg;       // Configure file system defaults
-  cfg.setAutoFormat(true);  // Formats file system if it cannot be mounted.
-  LittleFS.setConfig(cfg);
-  LittleFS.begin();  // Mounts file system.
-  if (!LittleFS.begin()) {
-    Serial.println("An Error has occurred while mounting LittleFS");
-  }
+  wheelDef pbWheel =  {
+    &h[assignCmd[4]].keyState,
+    &h[assignCmd[5]].keyState,
+    &h[assignCmd[6]].keyState,
+    -8192, 8192, 1024,
+    0, 0, 0
+  };
+  wheelDef velWheel = {
+    &h[assignCmd[0]].keyState,
+    &h[assignCmd[1]].keyState,
+    &h[assignCmd[2]].keyState,
+    0, 127, 8,
+    96, 96, 96
+  };
+  bool toggleWheel = 0; // 0 for mod, 1 for pb
+
+// ====== initialize rotary knob
+
+  #include <Rotary.h>
+  const byte rotaryPinA = 20;
+  const byte rotaryPinB = 21;
+  const byte rotaryPinC = 24;
+  Rotary rotary = Rotary(rotaryPinA, rotaryPinB);
+  bool rotaryIsClicked = HIGH;          //
+  bool rotaryWasClicked = HIGH;         //
+  int8_t rotaryKnobTurns = 0;           //
+
+// ====== initialize GFX display
+
+  #include <GEM_u8g2.h>
+  #define GEM_DISABLE_GLCD
+  U8G2_SH1107_SEEED_128X128_F_HW_I2C u8g2(U8G2_R2, U8X8_PIN_NONE);
+  GEM_u8g2 menu(u8g2, GEM_POINTER_ROW, GEM_ITEMS_COUNT_AUTO, 10, 10,
+78); // menu item height; 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 = 10000; //
+
+// ====== initialize piezo buzzer
+
+  //#include "RP2040_Volume.h"    // simulated volume control on buzzer
+  const byte tonePin = 23;
+  //RP2040_Volume piezoBuzzer(tonePin, tonePin);
+  byte buzzer = 0;                      // buzzer state
+  byte currentBuzzNote = 255;           // need to work on this
+  uint32_t currentBuzzTime = 0;         // Used to keep track of when
+this note started buzzin
+  uint32_t arpeggiateLength = 10;       //
+
+// ====== initialize tuning (microtonal) presets
+
+  typedef struct {
+    char* name;
+    byte cycleLength; // steps before repeat
+    float stepSize;   // in cents, 100 = "normal" semitone.
+  } tuningDef;
+  enum {
+    Twelve, Seventeen, Nineteen, TwentyTwo,
+    TwentyFour, ThirtyOne, FortyOne, FiftyThree,
+    SeventyTwo, BohlenPierce,
+    CarlosA, CarlosB, CarlosG
+  };
+  tuningDef tuningOptions[] = {
+    // replaces the idea of const byte EDO[] = { 12, 17, 19, 22, 24,
+31, 41, 53, 72 };
+    { (char*)"12 EDO",           12,  100.0 },
+    { (char*)"17 EDO",           17, 1200.0 / 17 },
+    { (char*)"19 EDO",           19, 1200.0 / 19 },
+    { (char*)"22 EDO",           22, 1200.0 / 22 },
+    { (char*)"24 EDO",           24,   50.0 },
+    { (char*)"31 EDO",           31, 1200.0 / 31 },
+    { (char*)"41 EDO",           41, 1200.0 / 41 },
+    { (char*)"53 EDO",           53, 1200.0 / 53 },
+    { (char*)"72 EDO",           72,  100.0 / 6 },
+    { (char*)"Bohlen-Pierce",    13, 1901.955 / 13 }, //
+    { (char*)"Carlos Alpha",      9, 77.965 }, //
+    { (char*)"Carlos Beta",      11, 63.833 }, //
+    { (char*)"Carlos Gamma",     20, 35.099 }
+  };
+  const byte tuningCount = sizeof(tuningOptions) / sizeof(tuningDef);
+
+// ====== initialize layout patterns
+
+  typedef struct {
+    char* name;
+    bool isPortrait;
+    byte rootHex;
+    int8_t acrossSteps;
+    int8_t dnLeftSteps;
+    byte tuning;
+  } layoutDef;
+  layoutDef layoutOptions[] = {
+    { (char*)"Wicki-Hayden",      1, 64,   2,  -7, Twelve     },
+    { (char*)"Harmonic Table",    0, 75,  -7,   3, Twelve     },
+    { (char*)"Janko",             0, 65,  -1,  -1, Twelve     },
+    { (char*)"Gerhard",           0, 65,  -1,  -3, Twelve     },
+    { (char*)"Accordion C-sys.",  1, 75,   2,  -3, Twelve     },
+    { (char*)"Accordion B-sys.",  1, 64,   1,  -3, Twelve     },
+    { (char*)"Full Layout",       1, 65,  -1,  -9, Twelve     },
+    { (char*)"Bosanquet, 17",     0, 65,  -2,  -1, Seventeen  },
+    { (char*)"Full Layout",       1, 65,  -1,  -9, Seventeen  },
+    { (char*)"Bosanquet, 19",     0, 65,  -1,  -2, Nineteen   },
+    { (char*)"Full Layout",       1, 65,  -1,  -9, Nineteen   },
+    { (char*)"Bosanquet, 22",     0, 65,  -3,  -1, TwentyTwo  },
+    { (char*)"Full Layout",       1, 65,  -1,  -9, TwentyTwo  },
+    { (char*)"Bosanquet, 24",     0, 65,  -1,  -3, TwentyFour },
+    { (char*)"Full Layout",       1, 65,  -1,  -9, TwentyFour },
+    { (char*)"Bosanquet, 31",     0, 65,  -2,  -3, ThirtyOne  },
+    { (char*)"Full Layout",       1, 65,  -1,  -9, ThirtyOne  },
+    { (char*)"Bosanquet, 41",     0, 65,  -4,  -3, FortyOne   },  //
+forty-one #1
+    { (char*)"Gerhard, 41",       0, 65,   3, -10, FortyOne   },  //
+forty-one #2
+    { (char*)"Full Layout, 41",   0, 65,  -1,  -8, FortyOne   },  //
+forty-one #3
+    { (char*)"Wicki-Hayden, 53",  1, 64,   9, -31, FiftyThree },
+    { (char*)"Harmonic Tbl, 53",  0, 75, -31,  14, FiftyThree },
+    { (char*)"Bosanquet, 53",     0, 65,  -5,  -4, FiftyThree },
+    { (char*)"Full Layout, 53",   0, 65,  -1,  -9, FiftyThree },
+    { (char*)"Full Layout, 72",   0, 65,  -1,  -9, SeventyTwo },
+    { (char*)"Full Layout",       1, 65,  -1,  -9, BohlenPierce },
+    { (char*)"Full Layout",       1, 65,  -1,  -9, CarlosA    },
+    { (char*)"Full Layout",       1, 65,  -1,  -9, CarlosB    },
+    { (char*)"Full Layout",       1, 65,  -1,  -9, CarlosG    }
+  };
+  const byte layoutCount = sizeof(layoutOptions) / sizeof(layoutDef);
+
+// ====== initialize list of supported scales / modes / raga / maqam
+
+  typedef struct {
+    char* name;
+    byte tuning;
+    byte step[16]; // 16 bytes = 128 bits, 1 = in scale; 0 = not
+  } scaleDef;
+  scaleDef scaleOptions[] = {
+    { (char*)"None",              255,        { 255,        255,
+  255,255,255,255,255,255,255,255,255,255,255,255,255,255} },
+    { (char*)"Major",             Twelve,     { 0b10101101,
+0b0101'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { (char*)"Minor, natural",    Twelve,     { 0b10110101,
+0b1010'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { (char*)"Minor, melodic",    Twelve,     { 0b10110101,
+0b0101'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { (char*)"Minor, harmonic",   Twelve,     { 0b10110101,
+0b1001'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { (char*)"Pentatonic, major", Twelve,     { 0b10101001,
+0b0100'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { (char*)"Pentatonic, minor", Twelve,     { 0b10010101,
+0b0010'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { (char*)"Blues",             Twelve,     { 0b10010111,
+0b0010'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { (char*)"Double Harmonic",   Twelve,     { 0b11001101,
+0b1001'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { (char*)"Phrygian",          Twelve,     { 0b11010101,
+0b1010'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { (char*)"Phrygian Dominant", Twelve,     { 0b11001101,
+0b1010'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { (char*)"Dorian",            Twelve,     { 0b10110101,
+0b0110'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { (char*)"Lydian",            Twelve,     { 0b10101011,
+0b0101'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { (char*)"Lydian Dominant",   Twelve,     { 0b10101011,
+0b0110'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { (char*)"Mixolydian",        Twelve,     { 0b10101101,
+0b0110'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { (char*)"Locrian",           Twelve,     { 0b11010110,
+0b1010'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { (char*)"Whole tone",        Twelve,     { 0b10101010,
+0b1010'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { (char*)"Octatonic",         Twelve,     { 0b10110110,
+0b1101'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { (char*)"Rast maqam",        TwentyFour, { 0b10001001,
+0b00100010, 0b00101100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
+    { (char*)"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);
+  byte scaleLock = 0;    // menu wants this to be an int, not a bool
+
+// ====== initialize key coloring routines
+
+  enum colors      { W,    R,    O,    Y,    L,    G,    C,    B,
+I,    P,    M,
+                          r,    o,    y,    l,    g,    c,    b,    i,
+   p,    m     };
+  enum { DARK = 0, VeryDIM = 1, DIM = 32, BRIGHT = 127, VeryBRIGHT = 255 };
+  enum { GRAY = 0, DULL = 127, VIVID = 255 };
+  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  };
+  byte colorMode = 0;
+  byte perceptual = 1;
+  enum {assignDefault = -1}; // auto-determine this component of color
+
+// ====== initialize note labels in each tuning, also used for key signature
+
+  typedef struct {
+    char* 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
+      { (char*)" C        (B#)", Twelve, -9, W },
+      { (char*)" C# / Db",       Twelve, -8, I },
+      { (char*)"      D",        Twelve, -7, W },
+      { (char*)"      D# / Eb",  Twelve, -6, I },
+      { (char*)"     (Fb)  E",   Twelve, -5, W },
+      { (char*)"      F   (E#)", Twelve, -4, W },
+      { (char*)" Gb / F#",       Twelve, -3, I },
+      { (char*)" G",             Twelve, -2, W },
+      { (char*)" G# / Ab",       Twelve, -1, I },
+      { (char*)"      A",        Twelve,  0, W },
+      { (char*)"      A# / Bb",  Twelve,  1, I },
+      { (char*)"(Cb)       B",   Twelve,  2, W },
+    // 17 EDO, whole tone = 3, #/b = 2, +/d = 1
+      { (char*)" C        (B+)", Seventeen,  -13, W },
+      { (char*)" C+ / Db / B#",  Seventeen,  -12, R },
+      { (char*)" C# / Dd",       Seventeen,  -11, I },
+      { (char*)"      D",        Seventeen,  -10, W },
+      { (char*)"      D+ / Eb",  Seventeen,   -9, R },
+      { (char*)" Fb / D# / Ed",  Seventeen,   -8, I },
+      { (char*)"(Fd)       E",   Seventeen,   -7, W },
+      { (char*)" F        (E+)", Seventeen,   -6, W },
+      { (char*)" F+ / Gb / E#",  Seventeen,   -5, R },
+      { (char*)" F# / Gd",       Seventeen,   -4, I },
+      { (char*)"      G",        Seventeen,   -3, W },
+      { (char*)"      G+ / Ab",  Seventeen,   -2, R },
+      { (char*)"      G# / Ad",  Seventeen,   -1, I },
+      { (char*)"           A",   Seventeen,    0, W },
+      { (char*)"      Bb / A+",  Seventeen,    1, R },
+      { (char*)" Cb / Bd / A#",  Seventeen,    2, I },
+      { (char*)"(Cd)  B"      ,  Seventeen,    3, W },
+    // 19 EDO, whole tone = 3, #/b = 1
+      { (char*)" C",       Nineteen, -14, W },
+      { (char*)" C#",      Nineteen, -13, R },
+      { (char*)" Db",      Nineteen, -12, I },
+      { (char*)" D",       Nineteen, -11, W },
+      { (char*)" D#",      Nineteen, -10, R },
+      { (char*)" Eb",      Nineteen,  -9, I },
+      { (char*)" E",       Nineteen,  -8, W },
+      { (char*)" E# / Fb", Nineteen,  -7, m },
+      { (char*)"      F",  Nineteen,  -6, W },
+      { (char*)"      F#", Nineteen,  -5, R },
+      { (char*)"      Gb", Nineteen,  -4, I },
+      { (char*)"      G",  Nineteen,  -3, W },
+      { (char*)"      G#", Nineteen,  -2, R },
+      { (char*)"      Ab", Nineteen,  -1, I },
+      { (char*)"      A",  Nineteen,   0, W },
+      { (char*)"      A#", Nineteen,   1, R },
+      { (char*)"      Bb", Nineteen,   2, I },
+      { (char*)"      B",  Nineteen,   3, W },
+      { (char*)" Cb / B#", Nineteen,   4, m },
+    // 22 EDO, whole tone = 4, #/b = 3, ^/v = 1
+      { (char*)"  C         (^B)", TwentyTwo, -17, W },
+      { (char*)" ^C  /  Db / vB#", TwentyTwo, -16, l },
+      { (char*)" vC# / ^Db /  B#", TwentyTwo, -15, C },
+      { (char*)"  C# / vD",        TwentyTwo, -14, i },
+      { (char*)"        D",        TwentyTwo, -13, W },
+      { (char*)"       ^D  /  Eb", TwentyTwo, -12, l },
+      { (char*)"  Fb / vD# / ^Eb", TwentyTwo, -11, C },
+      { (char*)" ^Fb /  D# / vE",  TwentyTwo, -10, i },
+      { (char*)"(vF)          E",  TwentyTwo,  -9, W },
+      { (char*)"  F         (^E)", TwentyTwo,  -8, W },
+      { (char*)" ^F  /  Gb / vE#", TwentyTwo,  -7, l },
+      { (char*)" vF# / ^Gb /  E#", TwentyTwo,  -6, C },
+      { (char*)"  F# / vG",        TwentyTwo,  -5, i },
+      { (char*)"        G",        TwentyTwo,  -4, W },
+      { (char*)"       ^G  /  Ab", TwentyTwo,  -3, l },
+      { (char*)"       vG# / ^Ab", TwentyTwo,  -2, C },
+      { (char*)"        G# / vA",  TwentyTwo,  -1, i },
+      { (char*)"              A",  TwentyTwo,   0, W },
+      { (char*)"        Bb / ^A",  TwentyTwo,   1, l },
+      { (char*)"  Cb / ^Bb / vA#", TwentyTwo,   2, C },
+      { (char*)" ^Cb / vB  /  A#", TwentyTwo,   3, i },
+      { (char*)"(vC)    B",        TwentyTwo,   4, W },
+    // 24 EDO, whole tone = 4, #/b = 2, +/d = 1
+      { (char*)" C  / B#", TwentyFour, -18, W },
+      { (char*)" C+",      TwentyFour, -17, r },
+      { (char*)" C# / Db", TwentyFour, -16, I },
+      { (char*)"      Dd", TwentyFour, -15, g },
+      { (char*)"      D",  TwentyFour, -14, W },
+      { (char*)"      D+", TwentyFour, -13, r },
+      { (char*)" Eb / D#", TwentyFour, -12, I },
+      { (char*)" Ed",      TwentyFour, -11, g },
+      { (char*)" E  / Fb", TwentyFour, -10, W },
+      { (char*)" E+ / Fd", TwentyFour,  -9, y },
+      { (char*)" E# / F",  TwentyFour,  -8, W },
+      { (char*)"      F+", TwentyFour,  -7, r },
+      { (char*)" Gb / F#", TwentyFour,  -6, I },
+      { (char*)" Gd",      TwentyFour,  -5, g },
+      { (char*)" G",       TwentyFour,  -4, W },
+      { (char*)" G+",      TwentyFour,  -3, r },
+      { (char*)" G# / Ab", TwentyFour,  -2, I },
+      { (char*)"      Ad", TwentyFour,  -1, g },
+      { (char*)"      A",  TwentyFour,   0, W },
+      { (char*)"      A+", TwentyFour,   1, r },
+      { (char*)" Bb / A#", TwentyFour,   2, I },
+      { (char*)" Bd",      TwentyFour,   3, g },
+      { (char*)" B  / Cb", TwentyFour,   4, W },
+      { (char*)" B+ / Cd", TwentyFour,   5, y },
+    // 31 EDO, whole tone = 5, #/b = 2, +/d = 1
+      { (char*)" C",       ThirtyOne, -23, W },
+      { (char*)" C+",      ThirtyOne, -22, R },
+      { (char*)" C#",      ThirtyOne, -21, Y },
+      { (char*)" Db",      ThirtyOne, -20, C },
+      { (char*)" Dd",      ThirtyOne, -19, I },
+      { (char*)" D",       ThirtyOne, -18, W },
+      { (char*)" D+",      ThirtyOne, -17, R },
+      { (char*)" D#",      ThirtyOne, -16, Y },
+      { (char*)" Eb",      ThirtyOne, -15, C },
+      { (char*)" Ed",      ThirtyOne, -14, I },
+      { (char*)" E",       ThirtyOne, -13, W },
+      { (char*)" E+ / Fb", ThirtyOne, -12, L },
+      { (char*)" E# / Fd", ThirtyOne, -11, M },
+      { (char*)"      F",  ThirtyOne, -10, W },
+      { (char*)"      F+", ThirtyOne,  -9, R },
+      { (char*)"      F#", ThirtyOne,  -8, Y },
+      { (char*)"      Gb", ThirtyOne,  -7, C },
+      { (char*)"      Gd", ThirtyOne,  -6, I },
+      { (char*)"      G",  ThirtyOne,  -5, W },
+      { (char*)"      G+", ThirtyOne,  -4, R },
+      { (char*)"      G#", ThirtyOne,  -3, Y },
+      { (char*)"      Ab", ThirtyOne,  -2, C },
+      { (char*)"      Ad", ThirtyOne,  -1, I },
+      { (char*)"      A",  ThirtyOne,   0, W },
+      { (char*)"      A+", ThirtyOne,   1, R },
+      { (char*)"      A#", ThirtyOne,   2, Y },
+      { (char*)"      Bb", ThirtyOne,   3, C },
+      { (char*)"      Bd", ThirtyOne,   4, I },
+      { (char*)"      B",  ThirtyOne,   5, W },
+      { (char*)" Cb / B+", ThirtyOne,   6, L },
+      { (char*)" Cd / B#", ThirtyOne,   7, M },
+    // 41 EDO, whole tone = 7, #/b = 4, +/d = 2, ^/v = 1
+      { (char*)"  C         (vB#)", FortyOne, -31, W },
+      { (char*)" ^C        /  B#",  FortyOne, -30, c },
+      { (char*)"  C+ ",             FortyOne, -29, O },
+      { (char*)" vC# /  Db",        FortyOne, -28, I },
+      { (char*)"  C# / ^Db",        FortyOne, -27, R },
+      { (char*)"        Dd",        FortyOne, -26, B },
+      { (char*)"       vD",         FortyOne, -25, y },
+      { (char*)"        D",         FortyOne, -24, W },
+      { (char*)"       ^D",         FortyOne, -23, c },
+      { (char*)"        D+",        FortyOne, -22, O },
+      { (char*)"       vD# /  Eb",  FortyOne, -21, I },
+      { (char*)"        D# / ^Eb",  FortyOne, -20, R },
+      { (char*)"              Ed",  FortyOne, -19, B },
+      { (char*)"             vE",   FortyOne, -18, y },
+      { (char*)"      (^Fb)   E",   FortyOne, -17, W },
+      { (char*)"        Fd / ^E",   FortyOne, -16, c },
+      { (char*)"       vF  /  E+",  FortyOne, -15, y },
+      { (char*)"        F   (vE#)", FortyOne, -14, W },
+      { (char*)"       ^F  /  E#",  FortyOne, -13, c },
+      { (char*)"        F+",        FortyOne, -12, O },
+      { (char*)"  Gb / vF#",        FortyOne, -11, I },
+      { (char*)" ^Gb /  F#",        FortyOne, -10, R },
+      { (char*)"  Gd",              FortyOne,  -9, B },
+      { (char*)" vG",               FortyOne,  -8, y },
+      { (char*)"  G",               FortyOne,  -7, W },
+      { (char*)" ^G",               FortyOne,  -6, c },
+      { (char*)"  G+",              FortyOne,  -5, O },
+      { (char*)" vG# /  Ab",        FortyOne,  -4, I },
+      { (char*)"  G# / ^Ab",        FortyOne,  -3, R },
+      { (char*)"        Ad",        FortyOne,  -2, B },
+      { (char*)"       vA",         FortyOne,  -1, y },
+      { (char*)"        A",         FortyOne,   0, W },
+      { (char*)"       ^A",         FortyOne,   1, c },
+      { (char*)"        A+",        FortyOne,   2, O },
+      { (char*)"       vA# /  Bb",  FortyOne,   3, I },
+      { (char*)"        A# / ^Bb",  FortyOne,   4, R },
+      { (char*)"              Bd",  FortyOne,   5, B },
+      { (char*)"             vB",   FortyOne,   6, y },
+      { (char*)"      (^Cb)   B",   FortyOne,   7, W },
+      { (char*)"        Cd / ^B",   FortyOne,   8, c },
+      { (char*)"       vC  /  B+",  FortyOne,   9, y },
+    // 53 EDO, whole tone = 9, #/b = 5, >/< = 2, ^/v = 1
+      { (char*)"  C         (vB#)", FiftyThree, -40, W },
+      { (char*)" ^C     /     B#",  FiftyThree, -39, c },
+      { (char*)" >C  / <Db",        FiftyThree, -38, l },
+      { (char*)" <C# / vDb",        FiftyThree, -37, O },
+      { (char*)" vC# /  Db",        FiftyThree, -36, I },
+      { (char*)"  C# / ^Db",        FiftyThree, -35, R },
+      { (char*)" ^C# / >Db",        FiftyThree, -34, B },
+      { (char*)" >C# / <D",         FiftyThree, -33, g },
+      { (char*)"       vD",         FiftyThree, -32, y },
+      { (char*)"        D",         FiftyThree, -31, W },
+      { (char*)"       ^D",         FiftyThree, -30, c },
+      { (char*)"       >D  / <Eb",  FiftyThree, -29, l },
+      { (char*)"       <D# / vEb",  FiftyThree, -28, O },
+      { (char*)"       vD# /  Eb",  FiftyThree, -27, I },
+      { (char*)"        D# / ^Eb",  FiftyThree, -26, R },
+      { (char*)"       ^D# / >Eb",  FiftyThree, -25, B },
+      { (char*)"       >D# / <E",   FiftyThree, -24, g },
+      { (char*)"  Fb    /    vE",   FiftyThree, -23, y },
+      { (char*)"(^Fb)         E",   FiftyThree, -22, W },
+      { (char*)"(>Fb)        ^E",   FiftyThree, -21, c },
+      { (char*)" <F     /    >E",   FiftyThree, -20, G },
+      { (char*)" vF         (<E#)", FiftyThree, -19, y },
+      { (char*)"  F         (vE#)", FiftyThree, -18, W },
+      { (char*)" ^F     /     E#",  FiftyThree, -17, c },
+      { (char*)" >F  / <Gb",        FiftyThree, -16, l },
+      { (char*)" <F# / vGb",        FiftyThree, -15, O },
+      { (char*)" vF# /  Gb",        FiftyThree, -14, I },
+      { (char*)"  F# / ^Gb",        FiftyThree, -13, R },
+      { (char*)" ^F# / >Gb",        FiftyThree, -12, B },
+      { (char*)" >F# / <G",         FiftyThree, -11, g },
+      { (char*)"       vG",         FiftyThree, -10, y },
+      { (char*)"        G",         FiftyThree,  -9, W },
+      { (char*)"       ^G",         FiftyThree,  -8, c },
+      { (char*)"       >G  / <Ab",  FiftyThree,  -7, l },
+      { (char*)"       <G# / vAb",  FiftyThree,  -6, O },
+      { (char*)"       vG# /  Ab",  FiftyThree,  -5, I },
+      { (char*)"        G# / ^Ab",  FiftyThree,  -4, R },
+      { (char*)"       ^G# / >Ab",  FiftyThree,  -3, B },
+      { (char*)"       >G# / <A",   FiftyThree,  -2, g },
+      { (char*)"             vA",   FiftyThree,  -1, y },
+      { (char*)"              A",   FiftyThree,   0, W },
+      { (char*)"             ^A",   FiftyThree,   1, c },
+      { (char*)"       <Bb / >A",   FiftyThree,   2, l },
+      { (char*)"       vBb / <A#",  FiftyThree,   3, O },
+      { (char*)"        Bb / vA#",  FiftyThree,   4, I },
+      { (char*)"       ^Bb /  A#",  FiftyThree,   5, R },
+      { (char*)"       >Bb / ^A#",  FiftyThree,   6, B },
+      { (char*)"       <B  / >A#",  FiftyThree,   7, g },
+      { (char*)"  Cb / vB",         FiftyThree,   8, y },
+      { (char*)"(^Cb)   B",         FiftyThree,   9, W },
+      { (char*)"(>Cb)  ^B",         FiftyThree,  10, c },
+      { (char*)" <C  / >B",         FiftyThree,  11, G },
+      { (char*)" vC   (<B#)",       FiftyThree,  12, y },
+    // 72 EDO, whole tone = 12, #/b = 6, +/d = 3, ^/v = 1
+      { (char*)"  C    (B#)", SeventyTwo, -54, W },
+      { (char*)" ^C",         SeventyTwo, -53, g },
+      { (char*)" vC+",        SeventyTwo, -52, r },
+      { (char*)"  C+",        SeventyTwo, -51, p },
+      { (char*)" ^C+",        SeventyTwo, -50, b },
+      { (char*)" vC#",        SeventyTwo, -49, y },
+      { (char*)"  C# /  Db",  SeventyTwo, -48, I },
+      { (char*)" ^C# / ^Db",  SeventyTwo, -47, g },
+      { (char*)"       vDd",  SeventyTwo, -46, r },
+      { (char*)"        Dd",  SeventyTwo, -45, p },
+      { (char*)"       ^Dd",  SeventyTwo, -44, b },
+      { (char*)"       vD",   SeventyTwo, -43, y },
+      { (char*)"        D",   SeventyTwo, -42, W },
+      { (char*)"       ^D",   SeventyTwo, -41, g },
+      { (char*)"       vD+",  SeventyTwo, -40, r },
+      { (char*)"        D+",  SeventyTwo, -39, p },
+      { (char*)"       ^D+",  SeventyTwo, -38, b },
+      { (char*)" vEb / vD#",  SeventyTwo, -37, y },
+      { (char*)"  Eb /  D#",  SeventyTwo, -36, I },
+      { (char*)" ^Eb / ^D#",  SeventyTwo, -35, g },
+      { (char*)" vEd",        SeventyTwo, -34, r },
+      { (char*)"  Ed",        SeventyTwo, -33, p },
+      { (char*)" ^Ed",        SeventyTwo, -32, b },
+      { (char*)" vE   (vFb)", SeventyTwo, -31, y },
+      { (char*)"  E    (Fb)", SeventyTwo, -30, W },
+      { (char*)" ^E   (^Fb)", SeventyTwo, -29, g },
+      { (char*)" vE+ / vFd",  SeventyTwo, -28, r },
+      { (char*)"  E+ /  Fd",  SeventyTwo, -27, p },
+      { (char*)" ^E+ / ^Fd",  SeventyTwo, -26, b },
+      { (char*)"(vE#)  vF",   SeventyTwo, -25, y },
+      { (char*)" (E#)   F",   SeventyTwo, -24, W },
+      { (char*)"(^E#)  ^F",   SeventyTwo, -23, g },
+      { (char*)"       vF+",  SeventyTwo, -22, r },
+      { (char*)"        F+",  SeventyTwo, -21, p },
+      { (char*)"       ^F+",  SeventyTwo, -20, b },
+      { (char*)" vGb / vF#",  SeventyTwo, -19, y },
+      { (char*)"  Gb /  F#",  SeventyTwo, -18, I },
+      { (char*)" ^Gb / ^F#",  SeventyTwo, -17, g },
+      { (char*)" vGd",        SeventyTwo, -16, r },
+      { (char*)"  Gd",        SeventyTwo, -15, p },
+      { (char*)" ^Gd",        SeventyTwo, -14, b },
+      { (char*)" vG",         SeventyTwo, -13, y },
+      { (char*)"  G",         SeventyTwo, -12, W },
+      { (char*)" ^G",         SeventyTwo, -11, g },
+      { (char*)" vG+",        SeventyTwo, -10, r },
+      { (char*)"  G+",        SeventyTwo,  -9, p },
+      { (char*)" ^G+",        SeventyTwo,  -8, b },
+      { (char*)" vG# / vAb",  SeventyTwo,  -7, y },
+      { (char*)"  G# /  Ab",  SeventyTwo,  -6, I },
+      { (char*)" ^G# / ^Ab",  SeventyTwo,  -5, g },
+      { (char*)"       vAd",  SeventyTwo,  -4, r },
+      { (char*)"        Ad",  SeventyTwo,  -3, p },
+      { (char*)"       ^Ad",  SeventyTwo,  -2, b },
+      { (char*)"       vA",   SeventyTwo,  -1, y },
+      { (char*)"        A",   SeventyTwo,   0, W },
+      { (char*)"       ^A",   SeventyTwo,   1, g },
+      { (char*)"       vA+",  SeventyTwo,   2, r },
+      { (char*)"        A+",  SeventyTwo,   3, p },
+      { (char*)"       ^A+",  SeventyTwo,   4, b },
+      { (char*)" vBb / vA#",  SeventyTwo,   5, y },
+      { (char*)"  Bb /  A#",  SeventyTwo,   6, I },
+      { (char*)" ^Bb / ^A#",  SeventyTwo,   7, g },
+      { (char*)" vBd",        SeventyTwo,   8, r },
+      { (char*)"  Bd",        SeventyTwo,   9, p },
+      { (char*)" ^Bd",        SeventyTwo,  10, b },
+      { (char*)" vB   (vCb)", SeventyTwo,  11, y },
+      { (char*)"  B    (Cb)", SeventyTwo,  12, W },
+      { (char*)" ^B   (^Cb)", SeventyTwo,  13, g },
+      { (char*)" vB+ / vCd",  SeventyTwo,  14, r },
+      { (char*)"  B+ /  Cd",  SeventyTwo,  15, p },
+      { (char*)" ^B+ / ^Cd",  SeventyTwo,  16, b },
+      { (char*)"(vB#)  vC",   SeventyTwo,  17, y },
+    //
+      { (char*)"n/a",BohlenPierce,0,W},
+      { (char*)"n/a",BohlenPierce,0,W},
+      { (char*)"n/a",BohlenPierce,0,W},
+      { (char*)"n/a",BohlenPierce,0,W},
+      { (char*)"n/a",BohlenPierce,0,W},
+      { (char*)"n/a",BohlenPierce,0,W},
+      { (char*)"n/a",BohlenPierce,0,W},
+      { (char*)"n/a",BohlenPierce,0,W},
+      { (char*)"n/a",BohlenPierce,0,W},
+      { (char*)"n/a",BohlenPierce,0,W},
+      { (char*)"n/a",BohlenPierce,0,W},
+      { (char*)"n/a",BohlenPierce,0,W},
+      { (char*)"n/a",BohlenPierce,0,W},
+    //
+      { (char*)"n/a",CarlosA,0,W},
+      { (char*)"n/a",CarlosA,0,W},
+      { (char*)"n/a",CarlosA,0,W},
+      { (char*)"n/a",CarlosA,0,W},
+      { (char*)"n/a",CarlosA,0,W},
+      { (char*)"n/a",CarlosA,0,W},
+      { (char*)"n/a",CarlosA,0,W},
+      { (char*)"n/a",CarlosA,0,W},
+      { (char*)"n/a",CarlosA,0,W},
+    //
+      { (char*)"n/a",CarlosB,0,W},
+      { (char*)"n/a",CarlosB,0,W},
+      { (char*)"n/a",CarlosB,0,W},
+      { (char*)"n/a",CarlosB,0,W},
+      { (char*)"n/a",CarlosB,0,W},
+      { (char*)"n/a",CarlosB,0,W},
+      { (char*)"n/a",CarlosB,0,W},
+      { (char*)"n/a",CarlosB,0,W},
+      { (char*)"n/a",CarlosB,0,W},
+      { (char*)"n/a",CarlosB,0,W},
+      { (char*)"n/a",CarlosB,0,W},
+    //
+      { (char*)"n/a",CarlosG,0,W},
+      { (char*)"n/a",CarlosG,0,W},
+      { (char*)"n/a",CarlosG,0,W},
+      { (char*)"n/a",CarlosG,0,W},
+      { (char*)"n/a",CarlosG,0,W},
+      { (char*)"n/a",CarlosG,0,W},
+      { (char*)"n/a",CarlosG,0,W},
+      { (char*)"n/a",CarlosG,0,W},
+      { (char*)"n/a",CarlosG,0,W},
+      { (char*)"n/a",CarlosG,0,W},
+      { (char*)"n/a",CarlosG,0,W},
+      { (char*)"n/a",CarlosG,0,W},
+      { (char*)"n/a",CarlosG,0,W},
+      { (char*)"n/a",CarlosG,0,W},
+      { (char*)"n/a",CarlosG,0,W},
+      { (char*)"n/a",CarlosG,0,W},
+      { (char*)"n/a",CarlosG,0,W},
+      { (char*)"n/a",CarlosG,0,W},
+      { (char*)"n/a",CarlosG,0,W},
+      { (char*)"n/a",CarlosG,0,W},
 
+  };
+  const int keyCount = sizeof(keyOptions) / sizeof(keyDef);
+// ====== initialize structure to store and recall user preferences
+
+  typedef struct { // put all user-selectable options into a class so
+that down the line these can be saved and loaded.
+    char* 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 transpose;
+    // define simple recall functions
+    tuningDef tuning() {
+      return tuningOptions[tuningIndex];
+    };
+    layoutDef layout() {
+      return layoutOptions[layoutIndex];
+    };
+    scaleDef scale() {
+      return scaleOptions[scaleIndex];
+    };
+    keyDef key() {
+      return keyOptions[keyIndex];
+    };
+    int layoutsBegin() {
+      if (tuningIndex == Twelve) {
+        return 0;
+      } else {
+        int temp = 0;
+        while (layoutOptions[temp].tuning < tuningIndex) {
+          temp++;
+        };
+        return temp;
+      };
+    };
+    int keysBegin() {
+      if (tuningIndex == Twelve) {
+        return 0;
+      } else {
+        int temp = 0;
+        while (keyOptions[temp].tuning < tuningIndex) {
+          temp++;
+        };
+        return temp;
+      };
+    };
+    int findC() {
+      return keyOptions[keysBegin()].offset;
+    };
+  } presetDef;
+  presetDef current = {
+    (char*)"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
 
-  // Set pinModes for the digital button matrix.
-  for (int pinNumber = 0; pinNumber < columnCount; pinNumber++)  // For each column pin...
-  {
-    pinMode(columns[pinNumber], INPUT_PULLUP);  // set the pinMode to INPUT_PULLUP (+3.3V / HIGH).
+  int positiveMod(int n, int d) {
+    return (((n % d) + d) % d);
   }
-  pinMode(m1p, OUTPUT);  // Setting the row multiplexer pins to output.
-  pinMode(m2p, OUTPUT);
-  pinMode(m4p, OUTPUT);
-  pinMode(m8p, OUTPUT);
-
-  strip.begin();                         // INITIALIZE NeoPixel strip object
-  strip.show();                          // Turn OFF all pixels ASAP
-  strip.setBrightness(stripBrightness);  // Set BRIGHTNESS (max = 255)
-  setCMD_LEDs();
-  strip.setPixelColor(cmdBtn1, strip.ColorHSV(65536 / 12, 255, pressedBrightness));
-  selectedScale = &chromaticScale;  // Set default scale
-  setLayoutLEDs();
-
-  u8g2.begin();               //Menu and graphics setup
-  u8g2.setBusClock(1000000);  // Speed up display
-  u8g2.setContrast(stripBrightness / 2);
-  menu.setSplashDelay(0);
-  menu.init();
-  setupMenu();
-  menu.drawMenu();
-
-  // wait until device mounted, maybe
-  for (int i = 0; i < 5 && !TinyUSBDevice.mounted(); i++) delay(1);
-
-  // Print diagnostic troubleshooting information to serial monitor
-  diagnosticTest();
-}
-
-void setup1() {  //Second core exclusively runs encoder
-  //pinMode(ROTA, INPUT_PULLUP);
-  //pinMode(ROTB, INPUT_PULLUP);
-  //encoder_init();
-}
-
-// ------------------------------------------------------------------------------------------------------------------------------------------------------------
-// START LOOP SECTION
-void loop() {
-
-  // Time tracking function
-  timeTracker();
-
-  // Reduces wear-and-tear on OLED panel
-  screenSaver();
-
-  // Read and store the digital button states of the scanning matrix
-  readDigitalButtons();
-
-  if (sequencerMode) {
-    // Cause newpresses to change stuff
-    sequencerToggleThingies();
-
-    // If it's time to play notes, play them
-    sequencerMaybePlayNotes();
-  } else {
-    // Act on those buttons
-    playNotes();
-    if (buzzer == BUZZER_ARP_UP) {
-      arp(1);
-    } else if (buzzer == BUZZER_ARP_DOWN) {
-      arp(-1);
-    }
-
-    if (pitchModToggle) {
-      // Pitch bend stuff
-      pitchBend();
-    } else {
-      // mod wheel stuff
-      modWheel();
-    }
-
-    // Held buttons
-    heldButtons();
+  coordinates indexToCoord(byte x) {
+    coordinates temp;
+    temp.row = (x / 10);
+    temp.col = (2 * (x % 10)) + (temp.row & 1);
+    return temp;
   }
-
-  // Animations
-  reactiveLighting();
-
-  // Do the LEDS
-  strip.show();
-
-  // Read any new MIDI messages
-  MIDI.read();
-
-  // Read menu navigation functions
-  menuNavigation();
-}
-
-void loop1() {
-  rotate();  // Reads the encoder on the second core to avoid missed steps
-  //readEncoder();
-}
-// END LOOP SECTION
-// ------------------------------------------------------------------------------------------------------------------------------------------------------------
-
-
-// ------------------------------------------------------------------------------------------------------------------------------------------------------------
-// START FUNCTIONS SECTION
-
-void timeTracker() {
-  loopTime = currentTime - previousTime;
-  if (diagnostics == 4) {  //Print out the time it takes to run each loop
-    Serial.println(loopTime);
+  bool hexOOB(coordinates c) {
+    return (c.row < 0)
+        || (c.row >= rowCount)
+        || (c.col < 0)
+        || (c.col >= (2 * colCount))
+        || ((c.col + c.row) & 1);
   }
-  // Update previouTime variable to give us a reference point for next loop
-  previousTime = currentTime;
-  // Store the current time in a uniform variable for this program loop
-  currentTime = millis();
-}
-
-void diagnosticTest() {
-  if (diagnostics > 0) {
-    Serial.println("Zach was here");
+  byte coordToIndex(coordinates c) {
+    if (hexOOB(c)) {
+      return 255;
+    } else {
+      return (10 * c.row) + (c.col / 2);
+    };
   }
-}
-
-void commandPress(byte command) {
-  // 14,15 undefined
-  // 20-31 undefined
-  // 35, 41 undefined
-  // 46-63 undefined
-  //84-90 undefined
-  // 102-119 undefined
-  if (command == EXTR_1) {
-    MIDI.sendControlChange(85, 127, midiChannel);
-  } else if (command == EXTR_2) {
-    MIDI.sendControlChange(86, 127, midiChannel);
-  } else if (command == EXTR_3) {
-    MIDI.sendControlChange(87, 127, midiChannel);
-  } else if (command == EXTR_4) {
-    MIDI.sendControlChange(88, 127, midiChannel);
-  } else if (command == EXTR_5) {
-    MIDI.sendControlChange(89, 127, midiChannel);
-  } else if (command == CMDB_1) {
-    midiVelocity = 100;
-    setCMD_LEDs();
-    strip.setPixelColor(cmdBtn1, strip.ColorHSV(65536 / 12, 255, pressedBrightness));
-  } else if (command == CMDB_2) {
-    midiVelocity = 60;
-    setCMD_LEDs();
-    strip.setPixelColor(cmdBtn2, strip.ColorHSV(65536 / 3, 255, pressedBrightness));
-  } else if (command == CMDB_3) {
-    midiVelocity = 20;
-    setCMD_LEDs();
-    strip.setPixelColor(cmdBtn3, strip.ColorHSV(65536 / 2, 255, pressedBrightness));
-  } else if (command == CMDB_4) {
-    pitchModToggle = !pitchModToggle;  // Toggles between pitch bend and mod wheel
-  } else if (command == CMDB_5) {
-  } else if (command == CMDB_6) {
-  } else if (command == CMDB_7) {
+  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;
   }
-}
-void commandRelease(byte command) {
-  switch(command) {
-    case EXTR_1:
-      MIDI.sendControlChange(85, 0, midiChannel);
-      break;
-    case EXTR_2:
-      MIDI.sendControlChange(86, 0, midiChannel);
-      break;
-    case EXTR_3:
-      MIDI.sendControlChange(87, 0, midiChannel);
-      break;
-    case EXTR_4:
-      MIDI.sendControlChange(88, 0, midiChannel);
-      break;
-    case EXTR_5:
-      MIDI.sendControlChange(89, 0, midiChannel);
-      break;
+  coordinates hexOffset(coordinates a, coordinates b) {
+    coordinates temp;
+    temp.row = a.row + b.row;
+    temp.col = a.col + b.col;
+    return temp;
   }
-}
-
-void pitchBend() {
-  // Default: no pitch change
-  int pitchBendTarget = 0;
-  // Otherwise set the targetted value based on the buttons pressed.
-  if (activeButtons[cmdBtn5] && !activeButtons[cmdBtn6] && !activeButtons[cmdBtn7]) {
-    pitchBendTarget = 8191;  // Whole pitch up
-  } else if (activeButtons[cmdBtn5] && activeButtons[cmdBtn6] && !activeButtons[cmdBtn7]) {
-    pitchBendTarget = 4096;  // Half pitch up
-  } else if (!activeButtons[cmdBtn5] && activeButtons[cmdBtn6] && activeButtons[cmdBtn7]) {
-    pitchBendTarget = -4096;  // Half pitch down
-  } else if (!activeButtons[cmdBtn5] && !activeButtons[cmdBtn6] && activeButtons[cmdBtn7]) {
-    pitchBendTarget = -8192;  // Whole pitch down
+  coordinates hexDistance(coordinates origin, coordinates destination) {
+    coordinates temp;
+    temp.row = destination.row - origin.row;
+    temp.col = destination.col - origin.col;
+    return temp;
   }
-
-  // Approach the target, sendPitchBend based on timing
-  if (pitchBendPosition != pitchBendTarget) {
-    modPitchTime = modPitchTime + loopTime;
-    if (modPitchTime >= 20) {  // Only run this loop every 20 ms to avoid overrunning MIDI
-      modPitchTime = 0;        // Reset clock.
-      // if distance between current value and target is less than the mod speed,
-      if (abs(pitchBendPosition - pitchBendTarget) < pitchBendSpeed) {
-        // don't go past the target.
-        pitchBendPosition = pitchBendTarget;
-      } else if (pitchBendPosition > pitchBendTarget) {
-        // otherwise, subtract (or add) the speed to approach the target.
-        pitchBendPosition = pitchBendPosition - pitchBendSpeed;
-      } else if (pitchBendPosition < pitchBendTarget) {
-        pitchBendPosition = pitchBendPosition + pitchBendSpeed;
-      }
-      // Always send the pitchBend because pitchBendPosition changed.
-      MIDI.sendPitchBend(pitchBendPosition, midiChannel);
-    }
+  float freqToMIDI(float Hz) {             // formula to convert from
+Hz to MIDI note
+    return 69.0 + 12.0 * log2f(Hz / 440.0);
   }
-
-  if (pitchBendPosition == pitchBendTarget && modPitchTime != 200) {  // When the pitchbend hits the target...
-    modPitchTime = 200;                                               // ...reset the clock for instant responsiveness upon button change
+  float MIDItoFreq(float MIDI) {           // formula to convert from
+MIDI note to Hz
+    return 440.0 * exp2((MIDI - 69.0) / 12.0);
   }
-  // Set mode indicator button red if in pitch bend mode
-  strip.setPixelColor(cmdBtn4, strip.ColorHSV(0, 255, defaultBrightness));
-  // Set the lights
-  if (pitchBendPosition > 0) {
-    strip.setPixelColor(cmdBtn5, strip.ColorHSV(0, 255, ((pitchBendPosition / 32) - 1)));
-    strip.setPixelColor(cmdBtn6, strip.ColorHSV(0, 255, (-pitchBendPosition / 32)));
-    strip.setPixelColor(cmdBtn7, strip.ColorHSV(0, 255, 0));
+  float stepsToMIDI(int16_t stepsFromA) {  // return the MIDI pitch associated
+    return freqToMIDI(concertA) + ((float)stepsFromA *
+(float)current.tuning().stepSize / 100.0);
   }
-  if (pitchBendPosition == 0) {
-    strip.setPixelColor(cmdBtn5, strip.ColorHSV(0, 255, 0));
-    strip.setPixelColor(cmdBtn6, strip.ColorHSV(0, 255, 255));
-    strip.setPixelColor(cmdBtn7, strip.ColorHSV(0, 255, 0));
+
+// ====== diagnostic wrapper
+
+  void sendToLog(String msg) {
+    if (diagnostics) {
+      Serial.println(msg);
+    };
   }
-  if (pitchBendPosition < 0) {
-    strip.setPixelColor(cmdBtn5, strip.ColorHSV(0, 255, 0));
-    strip.setPixelColor(cmdBtn6, strip.ColorHSV(0, 255, (pitchBendPosition / 32)));
-    strip.setPixelColor(cmdBtn7, strip.ColorHSV(0, 255, ((-pitchBendPosition / 32) - 1)));
+
+// ====== 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]));
+    };
   }
-}
-
-void modWheel() {
-  //Set targetted value based on what keys are being pressed.
-  byte modWheelTarget = 0;
-  if (activeButtons[cmdBtn5] && !activeButtons[cmdBtn6] && !activeButtons[cmdBtn7]) {
-    modWheelTarget = 127;
-  } else if (activeButtons[cmdBtn5] && activeButtons[cmdBtn6] && !activeButtons[cmdBtn7]) {
-    modWheelTarget = 100;
-  } else if (!activeButtons[cmdBtn5] && activeButtons[cmdBtn6] && !activeButtons[cmdBtn7]) {
-    modWheelTarget = 75;
-  } else if (activeButtons[cmdBtn5] && activeButtons[cmdBtn7]) {
-    modWheelTarget = 75;
-  } else if (!activeButtons[cmdBtn5] && activeButtons[cmdBtn6] && activeButtons[cmdBtn7]) {
-    modWheelTarget = 50;
-  } else if (!activeButtons[cmdBtn5] && !activeButtons[cmdBtn6] && activeButtons[cmdBtn7]) {
-    modWheelTarget = 25;
-  } else if (!activeButtons[cmdBtn5] && !activeButtons[cmdBtn6] && !activeButtons[cmdBtn7]) {
-    modWheelTarget = 0;
+  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 < hexCount; i++) {
+      if (!(h[i].isCmd)) {
+        byte scaleDegree = positiveMod(h[i].steps +
+current.key().offset - current.findC(),current.tuning().cycleLength);
+        switch (colorMode) {
+          case 1:
+            c = keyOptions[current.keysBegin() + scaleDegree].tierColor;
+            hueDegrees = hueCode[c];
+            sat = satCode[c];
+            break;
+          default:
+            hueDegrees = 360.0 * ((float)scaleDegree /
+(float)current.tuning().cycleLength);
+            sat = 255;
+            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 {
+        //
+      };
+    };
   }
 
-  if (modWheelPosition != modWheelTarget) {  // Only runs the clock when mod target differs from actual
-    modPitchTime = modPitchTime + loopTime;
-    if (modPitchTime >= 20) {  // If it has been over 20 ms from last run,
-      modPitchTime = 0;        // reset clock.
-      // if distance between current value and target is less than the mod speed,
-      if (abs(modWheelPosition - modWheelTarget) < modWheelSpeed) {
-        // don't go past the target.
-        modWheelPosition = modWheelTarget;
-      } else if (modWheelPosition > modWheelTarget) {
-        // otherwise, subtract (or add) the speed to approach the target.
-        modWheelPosition = modWheelPosition - modWheelSpeed;
-      } else if (modWheelPosition < modWheelTarget) {
-        modWheelPosition = modWheelPosition + modWheelSpeed;
-      }
-      // Always send the control change because modWheelPosition changed.
-      MIDI.sendControlChange(1, modWheelPosition, midiChannel);
-    }
+// ====== layout routines
+
+  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 < hexCount; i++) {
+      if (!(h[i].isCmd)) {
+        float N = stepsToMIDI(h[i].steps + current.key().offset +
+current.transpose);
+        if (N < 0 || N >= 128) {
+          h[i].note = 255;
+          h[i].bend = 0;
+          h[i].frequency = 0.0;
+        } else {
+          h[i].note = ((N >= 127) ? 127 : round(N));
+          h[i].bend = (ldexp(N - h[i].note, 13) / defaultPBRange);
+          h[i].frequency = MIDItoFreq(N);
+        };
+        sendToLog(String(
+          "hex #" + String(i) + ", " +
+          "steps=" + String(h[i].steps) + ", " +
+          "isCmd? " + String(h[i].isCmd) + ", " +
+          "note=" + String(h[i].note) + ", " +
+          "bend=" + String(h[i].bend) + ", " +
+          "freq=" + String(h[i].frequency) + ", " +
+          "inScale? " + String(h[i].inScale) + "."
+        ));
+      };
+    };
+    sendToLog("assignPitches complete.");
   }
-  if (modWheelPosition == modWheelTarget && modPitchTime != 200) {
-    modPitchTime = 200;  // Resets clock when modwheel hits target for instant responsiveness upon button change
+  void applyScale() {
+    sendToLog("applyScale was called:");
+    for (byte i = 0; i < hexCount; 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;
+        sendToLog(String(
+          "hex #" + String(i) + ", " +
+          "steps=" + String(h[i].steps) + ", " +
+          "isCmd? " + String(h[i].isCmd) + ", " +
+          "note=" + String(h[i].note) + ", " +
+          "inScale? " + String(h[i].inScale) + "."
+        ));
+      };
+    };
+    resetHexLEDs();
+    sendToLog("applyScale complete.");
   }
-  // Set mode indicator button green if in mod wheel mode
-  strip.setPixelColor(cmdBtn4, strip.ColorHSV(21854, 255, defaultBrightness));
-  // Set the pixel colors based on the (new) modWheelPosition.
-  if (modWheelPosition == 0) {
-    strip.setPixelColor(cmdBtn5, strip.ColorHSV(21854, 255, 0));
-    strip.setPixelColor(cmdBtn6, strip.ColorHSV(21854, 255, 0));
-    strip.setPixelColor(cmdBtn7, strip.ColorHSV(21854, 255, 0));
-  } else if (modWheelPosition > 0 && modWheelPosition < 25) {
-    strip.setPixelColor(cmdBtn5, strip.ColorHSV(21854, 255, 0));
-    strip.setPixelColor(cmdBtn6, strip.ColorHSV(21854, 255, 0));
-    strip.setPixelColor(cmdBtn7, strip.ColorHSV(21854, 255, (modWheelPosition * 10)));
-  } else if (modWheelPosition == 25) {
-    strip.setPixelColor(cmdBtn5, strip.ColorHSV(21854, 255, 0));
-    strip.setPixelColor(cmdBtn6, strip.ColorHSV(21854, 255, 0));
-    strip.setPixelColor(cmdBtn7, strip.ColorHSV(21854, 255, 255));
-  } else if (modWheelPosition > 25 && modWheelPosition < 75) {
-    strip.setPixelColor(cmdBtn5, strip.ColorHSV(21854, 255, 0));
-    strip.setPixelColor(cmdBtn6, strip.ColorHSV(21854, 255, ((modWheelPosition - 25) * 5)));
-    strip.setPixelColor(cmdBtn7, strip.ColorHSV(21854, 255, 255));
-  } else if (modWheelPosition == 75) {
-    strip.setPixelColor(cmdBtn5, strip.ColorHSV(21854, 255, 0));
-    strip.setPixelColor(cmdBtn6, strip.ColorHSV(21854, 255, 255));
-    strip.setPixelColor(cmdBtn7, strip.ColorHSV(21854, 255, 255));
-  } else if (modWheelPosition > 75 && modWheelPosition < 125) {
-    strip.setPixelColor(cmdBtn5, strip.ColorHSV(21854, 255, ((modWheelPosition - 75) * 5)));
-    strip.setPixelColor(cmdBtn6, strip.ColorHSV(21854, 255, 255));
-    strip.setPixelColor(cmdBtn7, strip.ColorHSV(21854, 255, 255));
-  } else if (modWheelPosition >= 125) {
-    strip.setPixelColor(cmdBtn5, strip.ColorHSV(21854, 255, 255));
-    strip.setPixelColor(cmdBtn6, strip.ColorHSV(21854, 255, 255));
-    strip.setPixelColor(cmdBtn7, strip.ColorHSV(21854, 255, 255));
+  void applyLayout() {       // call this function when the layout changes
+    sendToLog("buildLayout was called:");
+    for (byte i = 0; i < hexCount; 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 * (
+            current.layout().acrossSteps +
+            (2 * current.layout().dnLeftSteps)
+          ))
+        ) / 2;
+        sendToLog(String(
+          "hex #" + String(i) + ", " +
+          "steps=" + String(h[i].steps) + "."
+        ));
+      };
+    };
+    applyScale();        // when layout changes, have to re-apply
+scale and re-apply LEDs
+    assignPitches();     // same with pitches
+    u8g2.setDisplayRotation(current.layout().isPortrait ? U8G2_R2 :
+U8G2_R1);     // and landscape / portrait rotation
+    sendToLog("buildLayout complete.");
   }
-}
-
-// BUTTONS //
-void readDigitalButtons() {
-  if (diagnostics == 1) {
-    Serial.println();
+// ====== buzzer routines
+
+  byte nextHeldNote() {
+    byte n = 255;
+    for (byte i = 1; i < hexCount; i++) {
+      byte checkNote = positiveMod(currentBuzzNote + i, hexCount);
+      if ((h[checkNote].channel) && (!h[checkNote].isCmd)) {
+        n = checkNote;
+        break;
+      };
+    };
+    return n;
   }
-  // Button Deck
-  for (int rowIndex = 0; rowIndex < rowCount; rowIndex++)  // Iterate through each of the row pins on the multiplexing chip.
-  {
-    digitalWrite(m1p, rowIndex & 1);
-    digitalWrite(m2p, (rowIndex & 2) >> 1);
-    digitalWrite(m4p, (rowIndex & 4) >> 2);
-    digitalWrite(m8p, (rowIndex & 8) >> 3);
-    for (byte columnIndex = 0; columnIndex < columnCount; columnIndex++)  // Now iterate through each of the column pins that are connected to the current row pin.
-    {
-      byte columnPin = columns[columnIndex];                              // Hold the currently selected column pin in a variable.
-      pinMode(columnPin, INPUT_PULLUP);                                   // Set that row pin to INPUT_PULLUP mode (+3.3V / HIGH).
-      byte buttonNumber = columnIndex + (rowIndex * columnCount);         // Assign this location in the matrix a unique number.
-      delayMicroseconds(10);                                              // Delay to give the pin modes time to change state (false readings are caused otherwise).
-      previousActiveButtons[buttonNumber] = activeButtons[buttonNumber];  // Track the "previous" variable for comparison.
-      byte buttonState = digitalRead(columnPin);                          // (don't)Invert reading due to INPUT_PULLUP, and store the currently selected pin state.
-      if (buttonState == LOW) {
-        if (diagnostics == 1) {
-          Serial.print("1");
-        } else if (diagnostics == 2) {
-          Serial.println(buttonNumber);
-        }
-        if (!previousActiveButtons[buttonNumber]) {
-          // newpress time
-          activeButtonsTime[buttonNumber] = millis();
-        }
-        activeButtons[buttonNumber] = 1;
-      } else {
-        // Otherwise, the button is inactive, write a 0.
-        if (diagnostics == 1) {
-          Serial.print("0");
-        }
-        activeButtons[buttonNumber] = 0;
-      }
-      // Set the selected column pin back to INPUT mode (0V / LOW).
-      pinMode(columnPin, INPUT);
-    }
+  void buzz(byte x) {        // send 128 or larger to turn off tone
+    currentBuzzNote = x;
+    if ((!(h[x].isCmd)) && (h[x].note < 128) && (h[x].frequency < 32767)) {
+      //piezoBuzzer.tone(h[x].frequency, (float)velWheel.curValue *
+(100.0 / 128.0), 16384, TIME_MS);
+      tone(tonePin, h[x].frequency);     // stock TONE library, but
+frequency changed to float
+    } else {
+      //piezoBuzzer.stop_tone();
+      noTone(tonePin);                     // stock TONE library
+    };
   }
-}
-
-// Function to check if a note is within the selected scale
-bool isNotePlayable(byte note) {
-  if (!scaleLock) {
-    return true;  // Return true unconditionally if the toggle is disabled
+// ====== MIDI routines
+
+  void setPitchBendRange(byte Ch, byte semitones) {
+    MIDI.beginRpn(0, Ch);
+    MIDI.sendRpnValue(semitones << 7, Ch);
+    MIDI.endRpn(Ch);
+    sendToLog(String(
+      "set pitch bend range on ch " +
+      String(Ch) + " to be " + String(semitones) + " semitones"
+    ));
+  }
+  void setMPEzone(byte masterCh, byte sizeOfZone) {
+    MIDI.beginRpn(6, masterCh);
+    MIDI.sendRpnValue(sizeOfZone << 7, masterCh);
+    MIDI.endRpn(masterCh);
+    sendToLog(String(
+      "tried sending MIDI msg to set MPE zone, master ch " +
+      String(masterCh) + ", zone of this size: " + String(sizeOfZone)
+    ));
   }
-  note = (note - key + transpose) % 12;
-  if(tones != 12 || (*selectedScale)[note]) {
-    return true;
+  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);
+    for (byte i = 1; i <= 16; i++) {
+      setPitchBendRange(i, defaultPBRange);  // 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(String("pushed ch " + String(i) + " to the open
+channel queue"));
+      };
+      channelBend[i - 1] = 0;
+      channelPoly[i - 1] = 0;
+    };
   }
-  return false;
-}
-// Used by things not affected by scaleLock
-bool isNoteLit(byte note) {
-  note = (note - key + transpose) % 12;
-  if(tones != 12 || (*selectedScale)[note%12]){
-    return true;
+  void chgModulation() {
+  if (current.tuningIndex == Twelve) {
+      MIDI.sendControlChange(1, modWheel.curValue, 1);
+      sendToLog(String("sent mod value " + String(modWheel.curValue) +
+" to ch 1"));
+    } else if (MPE) {
+      MIDI.sendControlChange(1, modWheel.curValue, 1);
+      sendToLog(String("sent mod value " + String(modWheel.curValue) +
+" to ch 1"));
+      MIDI.sendControlChange(1, modWheel.curValue, 16);
+      sendToLog(String("sent mod value " + String(modWheel.curValue) +
+" to ch 16"));
+    } else {
+      for (byte i = 0; i < 16; i++) {
+        MIDI.sendControlChange(1, modWheel.curValue, i + 1);
+        sendToLog(String("sent mod value " + String(modWheel.curValue)
++ " to ch " + String(i+1)));
+      };
+    };
+  };
+  void chgUniversalPB() {
+    if (current.tuningIndex == Twelve) {
+      MIDI.sendPitchBend(pbWheel.curValue, 1);
+      sendToLog(String("sent pb value " + String(pbWheel.curValue) + "
+to ch 1"));
+    } else if (MPE) {
+      MIDI.sendPitchBend(pbWheel.curValue, 1);
+      sendToLog(String("sent pb value " + String(pbWheel.curValue) + "
+to ch 1"));
+      MIDI.sendPitchBend(pbWheel.curValue, 16);
+      sendToLog(String("sent pb value " + String(pbWheel.curValue) + "
+to ch 16"));
+    } else {
+      for (byte i = 0; i < 16; i++) {
+        MIDI.sendPitchBend(channelBend[i] + pbWheel.curValue, i + 1);
+        sendToLog(String("sent pb value " + String(channelBend[i] +
+pbWheel.curValue) + " to ch " + String(i+1)));
+      };
+    };
   }
-  return false;
-}
 
-void playNotes() {
-  for (int i = 0; i < elementCount; i++)  // For all buttons in the deck
-  {
-    if (activeButtons[i] != previousActiveButtons[i])  // If a change is detected
-    {
-      if (activeButtons[i] == 1)  // If the button is active (newpress)
-      {
-        if (currentLayout[i] < 128) {
-          if (isNotePlayable(currentLayout[i])) {  // If note is within the selected scale, light up and play
-            //strip.setPixelColor(i, keyColor(currentLayout[i], pressedBrightness));
-            noteOn(midiChannel, (currentLayout[i] + transpose) % 128, midiVelocity);
-          }
-        } else {
-          commandPress(currentLayout[i]);
-        }
-      } else {
-        // If the button is inactive (released)
-        if (currentLayout[i] < 128) {
-          if (isNotePlayable(currentLayout[i])) {
-            //setLayoutLED(i);
-            noteOff(midiChannel, (currentLayout[i] + transpose) % 128, 0);
-          }
+  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(String("found a matching channel: ch " +
+String(temp) + " has pitch bend " + String(channelBend[c])));
+          break;
+        };
+      };
+      if (temp = 17) {
+        if (openChannelQueue.empty()) {
+          sendToLog(String("channel queue was empty so we didn't send
+a note on"));
         } else {
-          commandRelease(currentLayout[i]);
-        }
-      }
-    }
+          temp = openChannelQueue.front();
+          openChannelQueue.pop();
+          sendToLog(String("popped " + String(temp) + " off the queue"));
+        };
+      };
+      return temp;
+    };
   }
-}
-
-void reactiveLighting() {
-  animationTime = animationTime + loopTime;
-  if (animationTime >= 33) {                                             // If it has been at least 33 ms (30fps) from last run,
-    animationTime = 0;                                                   // reset clock.
-    cycleNumber++;
-    if (cycleNumber > 11) {                                                    // Set position in 6-step cycle
-      cycleNumber = 0;
-    }
-    setLayoutLEDs();                                                     // Start by setting the lights to their defaults so we can "paint" on top of it.
-    for (int i = 0; i < elementCount; i++) {                             // Scanning through the buttons
-      if (isNotePlayable(currentLayout[i]) && currentLayout[i] < 128) {  // (if they are playable)
-        switch (lightMode) {                                             // and implementing the selected lighting pattern
-          case 0:
-            buttonPattern(i);  // Lights up the button pressed.
-            break;
+
+// ====== 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) {
+        MIDI.sendPitchBend(h[x].bend, c); // ch 1-16
+      };
+      MIDI.sendNoteOn(h[x].note, velWheel.curValue, c); // ch 1-16
+      sendToLog(String(
+        "sent note on: " + String(h[x].note) +
+        " pb " + String(h[x].bend) +
+        " vel " + String(velWheel.curValue) +
+        " ch " + String(c)
+      ));
+      if (current.tuningIndex != Twelve) {
+        channelPoly[c - 1]++;   // array is 0 - 15
+      };
+      if (buzzer) {
+        buzz(x);
+      };
+    };
+  }
+  void noteOff(byte x) {
+    byte c = h[x].channel;
+    if (c) {
+      h[x].channel = 0;
+      MIDI.sendNoteOff(h[x].note, velWheel.curValue, c);
+      sendToLog(String(
+        "sent note off: " + String(h[x].note) +
+        " pb " + String(h[x].bend) +
+        " vel " + String(velWheel.curValue) +
+        " ch " + String(c)
+      ));
+      if (current.tuningIndex != Twelve) {
+        switch (channelPoly[c - 1]) {
           case 1:
-            notePattern(i);  // Lights up the same exact notes as played across the array.
-            break;
-          case 2:
-            octavePattern(i);  // Lights up the same notes as played in all octaves across the array.
-            break;
-          case 3:
-            splashPattern(i);  // Creates an expanding ring around the pressed button.
-            break;
-          case 4:
-            starPattern(i);  // Creates a starburst around the pressed button.
+            channelPoly[c - 1]--;
+            openChannelQueue.push(c);
             break;
-          case 5:
-            orbitPattern(i);  // Lights orbit around the pressed button.
+          case 0:
             break;
-          default:  // Just in case something goes wrong?
-            buttonPattern(i);
+          default:
+            channelPoly[c - 1]--;
             break;
-        }
-      }
-    }
+        };
+      };
+      if (buzzer) {
+        buzz(nextHeldNote());
+      };
+    };
   }
-}
-uint16_t keyHue(byte note) {
-    // 60072
-    return ((note - key + transpose) % tones) * ((65536)/tones);
-}
-
-uint32_t keyColor(byte note, byte brightness) {
-    // The brightness being variable is a reason not to cache this value.
-    // TODO: generalize the brightness code.
-    if (colorMode == 0) { // "scale"
-        return strip.ColorHSV(keyHue(note), 255, brightness);
-    } else if (colorMode == 1) { // "tone"
-        if (tones == 12) {
-            switch (note%tones) {
-                // White keys
-                case 0: case 2: case 4: case 5: case 7: case 9: case 11:
-                    return strip.ColorHSV(0, 0, brightness);
-                // Black keys
-                case 1: case 3: case 6: case 8: case 10:
-                    return strip.ColorHSV(5461, 128, brightness);
-            }
-        } else if (tones == 24) {
-            return 3;
-        } else if (tones == 41) {
-            switch (note%tones) {
-                // TODO: Fix the brightness of these so contrast is better.
-                //   C        D        E        F        G        A        B
-                case 0: case  7: case 14: case 17: case 24: case 31: case 38:
-                    return strip.ColorHSV(0, 0, brightness);
-                //   C^       D^       E^       F^       G^       A^       B^
-                case 1: case  8: case 15: case 18: case 25: case 32: case 39:
-                    return strip.ColorHSV(39*256, 0.44*256, brightness);
-                //   C+       D+                F+       G+       A+
-                case 2: case  9:          case 19: case 26: case 33:
-                    return strip.ColorHSV(230*256, 0.35*256, brightness);
-                //   Db       Eb                Gb       Ab       Bb
-                case 3: case 10:          case 20: case 27: case 34:
-                    return strip.ColorHSV(166*256, 0.64*256, brightness);
-                //   C#       D#                F#       G#       A#
-                case 4: case 11:          case 21: case 28: case 35:
-                    return strip.ColorHSV(66*256, 0.83*256, brightness);
-                //   Dd       Ed                Gd       Ad       Bd
-                case 5: case 12:          case 22: case 29: case 36:
-                    return strip.ColorHSV(10*256, 0.52*256, brightness);
-                //   Dv       Ev       Fv       Gv       Av       Bv       Cv
-                case 6: case 13: case 16: case 23: case 30: case 37: case 40:
-                    return strip.ColorHSV(198*256, 0.43*256, brightness);
-
-            }
-        }
-    }
-    return 0;
-}
-
-void buttonPattern(int i) {
-  if (activeButtons[i] == 1) {  // If it's an active button...
-    // ...then we light it up!
-    strip.setPixelColor(i, keyColor(currentLayout[i],pressedBrightness));
+  void cmdOn(byte x) {   // volume and mod wheel read all current buttons
+    switch (h[x].note) {
+      case cmdCode + 3:
+        toggleWheel = !toggleWheel;
+        // recolorHex(x);
+        break;
+      default:
+        // the rest should all be taken care of within the wheelDef structure
+        break;
+    };
   }
-}
-
-void notePattern(int i) {
-  if (activeButtons[i] == 1) {                       // Check to see if the it's an active button.
-    for (int m = 0; m < elementCount; m++) {         // Scanning through all the lights
-      if (currentLayout[m] < 128) {                  // Only runs on lights in the playable area
-        if (currentLayout[m] == currentLayout[i]) {  // If it's the same note as the active button...
-          // ...then we light it up!
-          strip.setPixelColor(m, keyColor(currentLayout[m], pressedBrightness));
-        }
-      }
-    }
+  void cmdOff(byte x) {   // pitch bend wheel only if buttons held.
+    // nothing; should all be taken care of within the wheelDef structure
   }
-}
-
-void octavePattern(int i) {
-  if (activeButtons[i] == 1) {                                 // Check to see if the it's an active button.
-    for (int m = 0; m < elementCount; m++) {                   // Scanning through all the lights
-      if (currentLayout[m] < 128) {                            // Only runs on lights in the playable area
-        if (currentLayout[m] % tones == currentLayout[i] % tones) {  // If it's in different octaves as the active button...
-          // ...then we light it up!
-          strip.setPixelColor(m, keyColor(currentLayout[m], pressedBrightness));
-        }
-      }
-    }
+
+// ====== animations
+
+  void flagToAnimate(coordinates C) {
+    if (!hexOOB(C)) {
+      h[coordToIndex(C)].animate = 1;
+    };
   }
-}
-
-/* Kinda inefficient - adds 3ms to the loop timer for every 4 buttons held, but does not affect playability yet.
-If performance becomes an issue, this may be better handled by a lookup table or something like that.
-Another thing I'm considering is having an array for the lights that is populated as it runs the loops and only does setPixelColor at the
-end. This would allow me to have the brightness fade and only add to the array if the light is brighter than what's currently there.*/
-void splashPattern(int i) {
-  int x1 = i % 10;  // Calculate the coordinates of the pressed button
-  int y1 = i / 10;
-  if (animationStep[i] > 0) {                 // Oh boy, animation time! Might be overcomplicating this...
-    for (int m = 0; m < elementCount; m++) {  // Scanning through all the lights
-      if (currentLayout[m] < 128) {           // Only runs on lights in the playable area
-        int x2 = m % 10;                      // Coordinates of lights
-        int y2 = m / 10;
-        int dx = x1 - x2;  // Difference between light and button pressed
-        int dy = y1 - y2;
-// Penalty int shifts the lights over depending on if they are on odd or even rows to correct for the staggered rows
-#if ModelNumber == 1
-        int penalty = (((y1 % 2 == 0) && (y2 % 2 != 0) && (x1 > x2)) || ((y2 % 2 == 0) && (y1 % 2 != 0) && (x2 > x1))) ? 1 : 0;
-#elif ModelNumber == 2
-        int penalty = (((y1 % 2 == 0) && (y2 % 2 != 0) && (x1 < x2)) || ((y2 % 2 == 0) && (y1 % 2 != 0) && (x2 < x1))) ? 1 : 0;
-#endif
-        // If the light is the correct distance from the button...
-        if (max(abs(dy), abs(dx) + floor(abs(dy) / 2) + penalty) == animationStep[i]) {
-          // light it up!
-          strip.setPixelColor(m, keyColor(currentLayout[m], pressedBrightness));
-          // or we could have it fade as it moves
-          //strip.setPixelColor(m, keyColor(currentLayout[m], (pressedBrightness - animationStep[i]*12)));
-        }
-      }
-    }
+  void animateMirror() {
+    for (byte i = 0; i < hexCount; i++) {                   // check every hex
+      if ((!(h[i].isCmd)) && (h[i].channel)) {              // that is
+a held note
+        for (byte j = 0; j < hexCount; 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
+              temp = positiveMod(temp, current.tuning().cycleLength);
+            };
+            if (temp == 0) {                                //
+highlight if diff is zero
+              h[j].animate = 1;
+            };
+          };
+        };
+      };
+    };
   }
-  if (activeButtons[i] == 1) {  // Check to see if the it's an active button.
-    // Then we light up the pressed button
-    strip.setPixelColor(i, keyColor(currentLayout[i], pressedBrightness));
-    if (animationStep[i] < 16) {
-      animationStep[i]++;  // Increment the animation to the next step for next time.
-    }
-  } else {
-    animationStep[i] = 0;  // Stop the animation if the key is released
+  void animateOrbit() {
+    for (byte i = 0; i < hexCount; 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
+      };
+    };
   }
-}
-
-void starPattern(int i) { // This one is far more efficient with no noticeable performance hit when playing lots of notes at once.
-  int x1 = i % 10;  // Calculate the coordinates of the pressed button
-  int y1 = i / 10;
-  // Define the relative offsets of neighboring buttons in the pattern
-#if ModelNumber == 1
-  int offsets[][2] = {
-    { 0, 1 },                        // Left
-    { 0, -1 },                       // Right
-    { -1, (y1 % 2 == 0) ? 0 : -1 },  // Top Left (adjusted based on row parity)
-    { -1, (y1 % 2 == 0) ? 1 : 0 },   // Top Right (adjusted based on row parity)
-    { 1, (y1 % 2 == 0) ? 1 : 0 },    // Bottom Right (adjusted based on row parity)
-    { 1, (y1 % 2 == 0) ? 0 : -1 }    // Bottom Left (adjusted based on row parity)
-  };
-#elif ModelNumber == 2
-  int offsets[][2] = {
-    { 0, -1 },                       // Left
-    { 0, 1 },                        // Right
-    { -1, (y1 % 2 == 0) ? 0 : 1 },  // Top Left (adjusted based on row parity)
-    { -1, (y1 % 2 == 0) ? -1 : 0 },   // Top Right (adjusted based on row parity)
-    { 1, (y1 % 2 == 0) ? -1 : 0 },    // Bottom Right (adjusted based on row parity)
-    { 1, (y1 % 2 == 0) ? 0 : 1 }    // Bottom Left (adjusted based on row parity)
-  };
-#endif
-
-
-  if (animationStep[i] > 0) {  // Oh boy, animation time!
-    for (const auto& offset : offsets) {
-      // Calculate the neighboring button coordinates
-      int y2 = y1 + offset[0] * animationStep[i];
-#if ModelNumber == 1
-      int x2 = x1 + offset[1] * animationStep[i] + ((y1 % 2 == 0) ? -1 : 1) * ((y1 == y2) ? 0 : 1) * (animationStep[i] / 2);
-#elif ModelNumber == 2
-      int x2 = x1 + offset[1] * animationStep[i] + ((y1 % 2 == 0) ? 1 : -1) * ((y1 == y2) ? 0 : 1) * (animationStep[i] / 2);
-#endif
-      // Check if the neighboring button is within the layout boundaries
-      if (y2 >= 0 && y2 < 14 && x2 >= 0 && x2 < 10) {
-        // Calculate the index of the neighboring button
-        int neighborIndex = y2 * 10 + x2;
-        if (currentLayout[neighborIndex] < 128) {  // If it's in the playable area...
-          // ...set the color for the neighboring button
-          strip.setPixelColor(neighborIndex, keyColor(currentLayout[neighborIndex], pressedBrightness));
-        }
-      }
-    }
+  void animateRadial() {
+    for (byte i = 0; i < hexCount; 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 < steps; i++) {
+ // # of steps to the next corner
+              flagToAnimate(temp);
+  // flag for animation
+              temp = hexOffset(temp, hexVector(dir,radius / steps));
+ // then next step
+            };
+          };
+        };
+      };
+    };
   }
 
-
-  if (activeButtons[i] == 1) {  // Check to see if the it's an active button.
-    // Then we light up the pressed button
-    strip.setPixelColor(i, keyColor(currentLayout[i], pressedBrightness));
-    if (animationStep[i] < 16) {
-      animationStep[i]++;  // Increment the animation to the next step for next time.
-    }
-  } else {
-    animationStep[i] = 0;  // Stop the animation if the key is released
+// ====== menu variables and routines
+
+  // must declare these variables globally for some reason
+  // doing so down here so we don't have to forward declare callback functions
+  //
+  SelectOptionByte optionByteYesOrNo[] = { { "No"     , 0 },
+                                           { "Yes"    , 1 } };
+  SelectOptionByte optionByteBuzzer[] =  { { "Off"    , 0 },
+                                           { "Mono"   , 1 },
+                                           { "Arp'gio", 2 } };
+  SelectOptionByte optionByteColor[] =   { { "Rainbow", 0 },
+                                           { "Tiered" , 1 } };
+  SelectOptionByte optionByteAnimate[] = { { "None"   , NoAnim },
+                                           { "Octave" , OctaveAnim},
+                                           { "By Note", NoteAnim},
+                                           { "Star"   , StarAnim},
+                                           { "Splash" , SplashAnim},
+                                           { "Orbit"  , OrbitAnim} };
+
+  GEMSelect selectYesOrNo(sizeof(optionByteYesOrNo) /
+sizeof(SelectOptionByte), optionByteYesOrNo);
+  GEMSelect selectBuzzer( sizeof(optionByteBuzzer)  /
+sizeof(SelectOptionByte), optionByteBuzzer);
+  GEMSelect selectColor(  sizeof(optionByteColor)   /
+sizeof(SelectOptionByte), optionByteColor);
+  GEMSelect selectAnimate(sizeof(optionByteAnimate) /
+sizeof(SelectOptionByte), optionByteAnimate);
+
+  GEMPage  menuPageMain("HexBoard MIDI Controller");
+
+  GEMPage  menuPageTuning("Tuning");
+  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
+
+  GEMPage  menuPageScales("Scales");
+  GEMItem  menuGotoScales("Scales", menuPageScales);
+  GEMItem* menuItemScales[scaleCount];  // dynamically generate item
+based on presets and if allowed in given EDO tuning
+
+  GEMPage  menuPageKeys("Keys");
+  GEMItem  menuGotoKeys("Keys",     menuPageKeys);
+  GEMItem* menuItemKeys[keyCount];   // dynamically generate item
+based on presets
+
+  GEMItem  menuItemScaleLock( "Scale lock?",   scaleLock,     selectYesOrNo);
+  GEMItem  menuItemMPE(       "MPE Mode:",     MPE,
+selectYesOrNo, prepMIDIforMicrotones);
+  GEMItem  menuItemBuzzer(    "Buzzer:",       buzzer,        selectBuzzer);
+  GEMItem  menuItemColor(     "Color mode:",   colorMode,
+selectColor,   resetHexLEDs);
+  GEMItem  menuItemPercep(    "Adjust color:", perceptual,
+selectYesOrNo, resetHexLEDs);
+  GEMItem  menuItemAnimate(   "Animation:",    animationType, selectAnimate);
+
+  void menuHome() {
+    menu.setMenuPageCurrent(menuPageMain);
+    menu.drawMenu();
   }
-}
-
-void orbitPattern(int i) { // Lights orbiting around the held note.
-  int x1 = i % 10;  // Calculate the coordinates of the pressed button
-  int y1 = i / 10;
-  // Define the relative offsets of neighboring buttons in the pattern
-#if ModelNumber == 1
-  int offsets[][2] = {
-    { 0, 1 },                        // Left
-    { -1, (y1 % 2 == 0) ? 1 : 0 },  // Top Left (adjusted based on row parity)
-    { -1, (y1 % 2 == 0) ? 0 : -1 },   // Top Right (adjusted based on row parity)
-    { 0, -1 },                       // Right
-    { 1, (y1 % 2 == 0) ? 0 : -1 },    // Bottom Right (adjusted based on row parity)
-    { 1, (y1 % 2 == 0) ? 1 : 0 }    // Bottom Left (adjusted based on row parity)
-  };
-#elif ModelNumber == 2
-  int offsets[][2] = {
-    { 0, -1 },                       // Left
-    { -1, (y1 % 2 == 0) ? -1 : 0 },  // Top Left (adjusted based on row parity)
-    { -1, (y1 % 2 == 0) ? 0 : 1 },   // Top Right (adjusted based on row parity)
-    { 0, 1 },                        // Right
-    { 1, (y1 % 2 == 0) ? 0 : 1 },    // Bottom Right (adjusted based on row parity)
-    { 1, (y1 % 2 == 0) ? -1 : 0 }    // Bottom Left (adjusted based on row parity)
-  };
-#endif
-
-  if (activeButtons[i] == 1) {  // Check to see if the it's an active button.
-    // Then we light up the pressed button
-    strip.setPixelColor(i, keyColor(currentLayout[i], pressedBrightness));
-      // Calculate the neighboring button coordinates
-  int y2 = y1 + offsets[cycleNumber/2][0];
-  int x2 = x1 + offsets[cycleNumber/2][1];
-    // Check if the neighboring button is within the layout boundaries
-    if (y2 >= 0 && y2 < 14 && x2 >= 0 && x2 < 10) {
-      // Calculate the index of the neighboring button
-      int neighborIndex = y2 * 10 + x2;
-      if (currentLayout[neighborIndex] < 128) {  // If it's in the playable area...
-       // ...set the color for the neighboring button
-        strip.setPixelColor(neighborIndex, keyColor(currentLayout[neighborIndex], pressedBrightness));
-      }
-    }
+  void showOnlyValidLayoutChoices() { // re-run at setup and whenever
+tuning changes
+    for (byte L = 0; L < layoutCount; L++) {
+      menuItemLayout[L]->hide((layoutOptions[L].tuning != current.tuningIndex));
+    };
+    sendToLog(String("menu: Layout choices were updated."));
   }
-}
-
-void heldButtons() {
-  for (int i = 0; i < elementCount; i++) {
-    if (activeButtons[i]) {
-      //if (
-    }
+  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));
+    };
+    sendToLog(String("menu: Scale choices were updated."));
   }
-}
-
-void sequencerToggleThingies() {
-  // For all buttons in the deck
-  for (int i = 0; i < elementCount; i++) {
-    // Some change was made
-    if (activeButtons[i] != previousActiveButtons[i]) {
-      // newpress
-      if (activeButtons[i]) {
-        int stripN = i / 20;
-        if (stripN >= NLANES) continue;  // avoid an error.
-        int step = map2step(i % 20);
-        if (step >= 0) {
-          int offset = lanes[stripN].bank * 16;
-          lanes[stripN].steps[step + offset] = !lanes[stripN].steps[step + offset];
-          int color = 0;
-          if (lanes[stripN].steps[step + offset]) color = 255;
-          strip.setPixelColor(i, color, color, color);
-        } else if (step == -1) {  // switching banks
-          lanes[stripN].bank = !lanes[stripN].bank;
-          int offset = lanes[stripN].bank * 16;
-          for (int j = 0; j < 16; j++) {
-            int color = 0;
-            if (lanes[stripN].steps[j + offset]) color = 255;
-            strip.setPixelColor((stripN * 20) + step2map(j), color, color, color);
-          }
-        }
-      }
-    }
+  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));
+    };
+    sendToLog(String("menu: Key choices were updated."));
   }
-}
-// TODO: Redefine these for hexboard v2
-int map2step(int i) {
-  if (i >= 10) {
-    return (19 - i) * 2;
+  void changeLayout(GEMCallbackData callbackData) {  // when you
+change the layout via the menu
+    byte selection = callbackData.valByte;
+    if (selection != current.layoutIndex) {
+      current.layoutIndex = selection;
+      applyLayout();
+    };
+    menuHome();
   }
-  if (i <= 8) {
-    return ((8 - i) * 2) + 1;
+  void changeScale(GEMCallbackData callbackData) {   // when you
+change the scale via the menu
+    int selection = callbackData.valInt;
+    if (selection != current.scaleIndex) {
+      current.scaleIndex = selection;
+      applyScale();
+    };
+    menuHome();
   }
-  return -1;
-}
-int step2map(int step) {
-  if (step % 2) {
-    return 8 - ((step - 1) / 2);
+  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();
   }
-  return 19 - (step / 2);
-}
-
-void sequencerMaybePlayNotes() {
-  // TODO: sometimes call sequencerPlayNextNote();
-}
-
-// Do the next note, and increment the sequencer counter
-void sequencerPlayNextNote() {
-  bool anySolo = false;
-  for (int i = 0; i < NLANES; i++) {
-    // bitwise check if it's soloed
-    if (lanes[i].state & STATE_SOLO) {
-      anySolo = true;
-    }
+  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();
+    };
+    menuHome();
   }
+  void buildMenu() {
+    menuPageMain.addMenuItem(menuGotoTuning);
+    for (byte T = 0; T < tuningCount; T++) { // create pointers to all
+tuning choices
+      menuItemTuning[T] = new GEMItem(tuningOptions[T].name, changeTuning, T);
+      menuPageTuning.addMenuItem(*menuItemTuning[T]);
+    };
+
+    menuPageMain.addMenuItem(menuGotoLayout);
+    for (byte L = 0; L < layoutCount; L++) { // create pointers to all layouts
+      menuItemLayout[L] = new GEMItem(layoutOptions[L].name, changeLayout, L);
+      menuPageLayout.addMenuItem(*menuItemLayout[L]);
+    };
+    showOnlyValidLayoutChoices();
+
+    menuPageMain.addMenuItem(menuGotoScales);
+    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, changeScale, S);
+      menuPageScales.addMenuItem(*menuItemScales[S]);
+    };
+    showOnlyValidScaleChoices();
+
+    menuPageMain.addMenuItem(menuGotoKeys);
+    for (int K = 0; K < keyCount; K++) {
+      menuItemKeys[K] = new GEMItem(keyOptions[K].name, changeKey, K);
+      menuPageKeys.addMenuItem(*menuItemKeys[K]);
+    };
+
+    showOnlyValidKeyChoices();
+
+    menuPageMain.addMenuItem(menuItemScaleLock);
+    menuPageMain.addMenuItem(menuItemColor);
+    menuPageMain.addMenuItem(menuItemBuzzer);
+    menuPageMain.addMenuItem(menuItemAnimate);
+
+    menuPageMain.addMenuItem(menuItemMPE);
+    menuPageMain.addMenuItem(menuItemPercep);
 
-  for (int i = 0; i < NLANES; i++) {
-    // If something is soloed (not this one), then don't do the thing
-    if (anySolo && !(lanes[i].state & STATE_SOLO)) {
-      continue;
-    }
-    // If this one was muted, don't do the thing.
-    if (lanes[i].state & STATE_MUTE) {
-      continue;
-    }
-    int offset = lanes[i].bank * 16;
-    if (lanes[i].steps[sequencerStep + offset]) {
-      // do the thing.
-      noteOn(midiChannel, lanes[i].instrument % 128, midiVelocity);
-      // TODO: Change when the noteoff is played?
-      noteOff(midiChannel, lanes[i].instrument % 128, 0);
-    }
   }
 
-  // increment and confine to limit
-  sequencerStep++;
-  sequencerStep %= 16;
-}
-
-// Return the first note that is currently held.
-byte getHeldNote() {
-  for (int i = 0; i < elementCount; i++) {
-    if (activeButtons[i]) {
-      if (currentLayout[i] < 128 && isNotePlayable(currentLayout[i])) {
-        return (currentLayout[i] + transpose) % 128;
-      }
-    }
+// ====== setup routines
+
+  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()
   }
-  return 128;
-}
-byte getNextNote(int direction, byte note) {
-  // Find the notes after note in the direction which is held.
-  for (int i = 0; i < 127; i++) {
-    byte target = (128 + note + ((1 + i)*direction)) % 128;
-    if (pitchRef[target]) {
-      return target;
+  void setupFileSystem() {
+    Serial.begin(115200);     // Set serial to make uploads work
+without bootsel button
+    LittleFSConfig cfg;       // Configure file system defaults
+    cfg.setAutoFormat(true);  // Formats file system if it cannot be mounted.
+    LittleFS.setConfig(cfg);
+    LittleFS.begin();  // Mounts file system.
+    if (!LittleFS.begin()) {
+      Serial.println("An Error has occurred while mounting LittleFS");
     }
   }
-  return 128;
-}
-
-void arp(int direction) {
-    if (currentTime - arpTime > arpThreshold){
-        arpTime = millis();
-        byte target = 128;
-        target = getNextNote(direction, curr_pitch);
-        if (target != 128) {
-            do_tone(target);
-        }
-    }
-}
-
-void do_tone(byte pitch) {
-    curr_pitch = pitch;
-    if (pitch > 127 || pitch < 0) {
-        noTone(TONEPIN);
-        return;
+  void setupPins() {
+    for (byte p = 0; p < sizeof(columnPins); p++)  // For each column pin...
+    {
+      pinMode(columnPins[p], INPUT_PULLUP);  // set the pinMode to
+INPUT_PULLUP (+3.3V / HIGH).
     }
-    if (tones == 12) {
-        tone(TONEPIN, pitches[pitch]);
-    } else if (tones == 19) {
-        tone(TONEPIN, pitches19[pitch]);
-    } else if (tones == 24) {
-        tone(TONEPIN, pitches24[pitch]);
-    } else if (tones == 31) {
-        tone(TONEPIN, pitches31[pitch]);
-    } else if (tones == 41) {
-        tone(TONEPIN, pitches41[pitch]);
-    } else if (tones == 72) {
-        tone(TONEPIN, pitches72[pitch]);
+    for (byte p = 0; p < sizeof(multiplexPins); p++)  // For each column pin...
+    {
+      pinMode(multiplexPins[p], OUTPUT);  // Setting the row
+multiplexer pins to output.
     }
-}
-
-// MIDI AND OTHER OUTPUTS //
-// Send Note On
-void noteOn(byte channel, byte pitch, byte velocity) {
-  MIDI.sendNoteOn(pitch, velocity, channel);
-  pitchRef[pitch] = true;
-  if (diagnostics == 3) {
-    Serial.print(pitch);
-    Serial.print(", ");
-    Serial.print(velocity);
-    Serial.print(", ");
-    Serial.println(channel);
+    Wire.setSDA(lightPinSDA);
+    Wire.setSCL(lightPinSCL);
+    pinMode(rotaryPinC, INPUT_PULLUP);
   }
-  if (buzzer) {
-    do_tone(pitch);
+  void setupGrid() {
+    sendToLog(String("initializing hex grid..."));
+    for (byte i = 0; i < hexCount; i++) {
+      h[i].coords = indexToCoord(i);
+      h[i].isCmd = 0;
+      h[i].note = 255;
+      h[i].keyState = 0;
+    };
+    for (byte c = 0; c < cmdCount; c++) {
+      h[assignCmd[c]].isCmd = 1;
+      h[assignCmd[c]].note = cmdCode + c;
+    };
+    applyLayout();
   }
-}
-// Send Note Off
-void noteOff(byte channel, byte pitch, byte velocity) {
-  MIDI.sendNoteOff(pitch, velocity, channel);
-  pitchRef[pitch] = false;
-  noTone(TONEPIN);
-  if (buzzer) {
-    byte anotherPitch = getHeldNote();
-    if (anotherPitch < 128) {
-      do_tone(anotherPitch);
-    } else {
-    }
+  void setupLEDs() {  // need layout
+    strip.begin();    // INITIALIZE NeoPixel strip object
+    strip.show();     // Turn OFF all pixels ASAP
+    resetHexLEDs();
   }
-}
-
-// LEDS //
-void setCMD_LEDs() {
-  strip.setPixelColor(cmdBtn1, strip.ColorHSV(65536 / 12, 255, dimBrightness));
-  strip.setPixelColor(cmdBtn2, strip.ColorHSV(65536 / 3, 255, dimBrightness));
-  strip.setPixelColor(cmdBtn3, strip.ColorHSV(65536 / 2, 255, dimBrightness));
-  strip.setPixelColor(cmdBtn4, strip.ColorHSV(0, 255, defaultBrightness));
-}
-
-void setLayoutLEDs() {
-  for (int i = 0; i < elementCount; i++) {
-    if (currentLayout[i] <= 127) {
-      setLayoutLED(i);
-    }
-  }
-}
-void setLayoutLED(int i) {
-  if (scaleLock) {
-    strip.setPixelColor(i, keyColor(currentLayout[i], 0));
-  } else {
-    strip.setPixelColor(i, keyColor(currentLayout[i], dimBrightness));
+  void setupMenu() {  // need menu
+    menu.setSplashDelay(0);
+    menu.init();
+    buildMenu();
+    menuHome();
   }
-
-  // Scale highlighting
-  if (isNoteLit(currentLayout[i])) {
-    strip.setPixelColor(i, keyColor(currentLayout[i], defaultBrightness));
+  void setupGFX() {
+    u8g2.begin();                       // Menu and graphics setup
+    u8g2.setBusClock(1000000);          // Speed up display
+    u8g2.setContrast(defaultContrast);  // Set contrast
   }
-}
-
-// ENCODER //
-// rotary encoder pin change interrupt handler
-void readEncoder() {
-  encoder_state = (encoder_state << 4) | (digitalRead(ROTB) << 1) | digitalRead(ROTA);
-  Serial.println(encoder_val);
-  switch (encoder_state) {
-    case 0x23: encoder_val++; break;
-    case 0x32: encoder_val--; break;
-    default: break;
+  void testDiagnostics() {
+    sendToLog(String("theHDM was here"));
   }
-}
-void rotate() {
-  unsigned char result = rotary.process();
-  if (result == DIR_CW) {
-    encoder_val++;
-  } else if (result == DIR_CCW) {
-    encoder_val--;
+
+// ====== loop routines
+
+  void timeTracker() {
+    lapTime = runTime - loopTime;
+    // sendToLog(String(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
   }
-}
-// rotary encoder init
-void encoder_init() {
-  // enable pin change interrupts
-  attachInterrupt(digitalPinToInterrupt(ROTA), readEncoder, RISING);
-  attachInterrupt(digitalPinToInterrupt(ROTB), readEncoder, RISING);
-  encoder_state = (digitalRead(ROTB) << 1) | digitalRead(ROTA);
-  interrupts();
-}
-
-// MENU //
-void menuNavigation() {
-  if (menu.readyForKey()) {
-    encoderState = digitalRead(encoderClick);
-    if (encoderState > encoderLastState) {
-      menu.registerKeyPress(GEM_KEY_OK);
-      screenTime = 0;
-    }
-    encoderLastState = encoderState;
-    if (encoder_val < 0) {
-      menu.registerKeyPress(GEM_KEY_UP);
-      encoder_val = 0;
-      screenTime = 0;
-    }
-    if (encoder_val > 0) {
-      menu.registerKeyPress(GEM_KEY_DOWN);
-      encoder_val = 0;
-      screenTime = 0;
+  void screenSaver() {
+    if (screenTime <= screenSaverMillis) {
+      screenTime = screenTime + lapTime;
+      if (screenSaverOn) {
+        screenSaverOn = 0;
+        u8g2.setContrast(defaultContrast);
+      }
+    } else {
+      if (!screenSaverOn) {
+        screenSaverOn = 1;
+        u8g2.setContrast(1);
+      }
     }
   }
-}
-
-void setupMenu() {
-  // Add menu items to Main menu page
-  menuPageMain.addMenuItem(menuItemLayout);
-  menuPageMain.addMenuItem(menuItemKey);
-  menuPageMain.addMenuItem(menuItemScale);
-  menuPageMain.addMenuItem(menuItemScaleLock);
-  menuPageMain.addMenuItem(menuItemTranspose);
-  menuPageMain.addMenuItem(menuItemBendSpeed);
-  menuPageMain.addMenuItem(menuItemModSpeed);
-  menuPageMain.addMenuItem(menuItemBrightness);
-  menuPageMain.addMenuItem(menuItemLighting);
-  menuPageMain.addMenuItem(menuItemColor);
-  menuPageMain.addMenuItem(menuItemBuzzer);
-  menuPageMain.addMenuItem(menuItemTesting);
-  // Add menu items to Layout Select page
-  menuPageLayout.addMenuItem(menuItemWickiHayden);
-  menuPageLayout.addMenuItem(menuItemHarmonicTable);
-  menuPageLayout.addMenuItem(menuItemGerhard);
-  menuPageLayout.addMenuItem(menuItemBosanquetWilson);
-  // Add menu items to Testing page
-  menuPageTesting.addMenuItem(menuItemSequencer);
-  menuPageTesting.addMenuItem(menuItemEzMajor);
-  menuPageTesting.addMenuItem(menuItemFull);
-  menuPageTesting.addMenuItem(menuItem411);
-  menuPageTesting.addMenuItem(menuItem412);
-  menuPageTesting.addMenuItem(menuItem413);
-  menuPageTesting.addMenuItem(menuItemVersion);
-  menuPageTesting.addMenuItem(menuItemTones);
-  menuPageTesting.addMenuItem(menuItemArp);
-  // Specify parent menu page for the other menu pages
-  menuPageLayout.setParentMenuPage(menuPageMain);
-  menuPageTesting.setParentMenuPage(menuPageMain);
-  // Add menu page to menu and set it as current
-  menu.setMenuPageCurrent(menuPageMain);
-}
-
-void wickiHayden() {
-  currentLayout = wickiHaydenLayout;
-  setLayoutLEDs();
-  if (ModelNumber != 1) {
-    u8g2.setDisplayRotation(U8G2_R2);
-  }
-  menu.setMenuPageCurrent(menuPageMain);
-  menu.drawMenu();
-}
-void harmonicTable() {
-  currentLayout = harmonicTableLayout;
-  setLayoutLEDs();
-  if (ModelNumber != 1) {
-    u8g2.setDisplayRotation(U8G2_R1);
-  }
-  menu.setMenuPageCurrent(menuPageMain);
-  menu.drawMenu();
-}
-void gerhard() {
-  currentLayout = gerhardLayout;
-  setLayoutLEDs();
-  if (ModelNumber != 1) {
-    u8g2.setDisplayRotation(U8G2_R1);
+  void readHexes() {
+    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(multiplexPins[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 = columnPins[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).
+        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).
+       }
+    }
   }
-  menu.setMenuPageCurrent(menuPageMain);
-  menu.drawMenu();
-}
-void bosanquetWilson() {
-  currentLayout = bosanquetWilsonLayout;
-  setLayoutLEDs();
-  if (ModelNumber != 1) {
-    u8g2.setDisplayRotation(U8G2_R1);
+  void actionHexes() {
+    for (byte i = 0; i < hexCount; i++) {   // For all buttons in the deck
+      switch (h[i].keyState) {
+        case 1: // just pressed
+          if (h[i].isCmd) {
+            cmdOn(i);
+          } else if (h[i].inScale || (!scaleLock)) {
+            noteOn(i);
+          };
+          break;
+        case 2: // just released
+          if (h[i].isCmd) {
+            cmdOff(i);
+          } else if (h[i].inScale || (!scaleLock)) {
+            noteOff(i);
+          };
+          break;
+        case 3: // held
+          break;
+        default: // inactive
+          break;
+      };
+    };
   }
-  menu.setMenuPageCurrent(menuPageMain);
-  menu.drawMenu();
-}
-void full() {
-  currentLayout = fullLayout;
-  setLayoutLEDs();
-  if (ModelNumber != 1) {
-    u8g2.setDisplayRotation(U8G2_R1);
+  void arpeggiate() {
+    if (buzzer > 1) {
+      if (runTime - currentBuzzTime > arpeggiateLength) {
+        currentBuzzTime = millis();
+        byte nextNoteToBuzz = nextHeldNote();
+        if (nextNoteToBuzz < cmdCode) {
+          buzz(nextNoteToBuzz);
+        };
+      };
+    };
   }
-  menu.setMenuPageCurrent(menuPageMain);
-  menu.drawMenu();
-}
-void fortyone1() {
-  currentLayout = fourtyone1;
-  setLayoutLEDs();
-  if (ModelNumber != 1) {
-    u8g2.setDisplayRotation(U8G2_R1);
+  void updateWheels() {
+    velWheel.setTargetValue();
+    bool upd = velWheel.updateValue(); // this function returns a
+boolean, gotta put it somewhere even if it isn't being used
+    if (upd) {
+      sendToLog(String("vel became " + String(velWheel.curValue)));
+    }
+    if (toggleWheel) {
+      pbWheel.setTargetValue();
+      upd = pbWheel.updateValue();
+      if (upd) {
+        chgUniversalPB();
+      };
+    } else {
+      modWheel.setTargetValue();
+      upd = modWheel.updateValue();
+      if (upd) {
+        chgModulation();
+      };
+    };
   }
-  menu.setMenuPageCurrent(menuPageMain);
-  menu.drawMenu();
-}
-void fortyone2() {
-  currentLayout = fourtyone2;
-  setLayoutLEDs();
-  if (ModelNumber != 1) {
-    u8g2.setDisplayRotation(U8G2_R1);
+  void animateLEDs() {    // TBD
+    for (byte i = 0; i < hexCount; i++) {
+      h[i].animate = 0;
+    };
+    if (animationType) {
+      switch (animationType) {
+        case StarAnim: case SplashAnim:
+          animateRadial();
+          break;
+        case OrbitAnim:
+          animateOrbit();
+          break;
+        case OctaveAnim: case NoteAnim:
+          animateMirror();
+          break;
+        default:
+          break;
+      };
+    };
   }
-  menu.setMenuPageCurrent(menuPageMain);
-  menu.drawMenu();
-}
-void fortyone3() {
-  currentLayout = fourtyone3;
-  setLayoutLEDs();
-  if (ModelNumber != 1) {
-    u8g2.setDisplayRotation(U8G2_R1);
+  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;
   }
-  menu.setMenuPageCurrent(menuPageMain);
-  menu.drawMenu();
-}
-void ezMajor() {
-  currentLayout = ezMajorLayout;
-  setLayoutLEDs();
-  if (ModelNumber != 1) {
-    u8g2.setDisplayRotation(U8G2_R2);
+  void lightUpLEDs() {
+    for (byte i = 0; i < hexCount; 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.show();
   }
-  menu.setMenuPageCurrent(menuPageMain);
-  menu.drawMenu();
-}
-
-void setBrightness() {
-  strip.setBrightness(stripBrightness);
-  setLayoutLEDs();
-}
-
-// Validation routine of transpose variable
-void validateTranspose() {
-  //Need to add some code here to make sure transpose doesn't get out of hand
-  /*something like
-  if ((transpose + LOWEST NOTE IN ARRAY) < 0) {
-    transpose = 0;
-  } */
-  setLayoutLEDs();
-}
-
-void screenSaver() {
-  if (screenTime <= 10000) {
-    screenTime = screenTime + loopTime;
-    if (screenBrightness != stripBrightness / 2) {
-      screenBrightness = stripBrightness / 2;
-      u8g2.setContrast(screenBrightness);
+  void dealWithRotary() {
+    if (menu.readyForKey()) {
+      rotaryIsClicked = digitalRead(rotaryPinC);
+      if (rotaryIsClicked > rotaryWasClicked) {
+        menu.registerKeyPress(GEM_KEY_OK);
+        screenTime = 0;
+      }
+      rotaryWasClicked = rotaryIsClicked;
+      if (rotaryKnobTurns != 0) {
+        for (byte i = 0; i < abs(rotaryKnobTurns); i++) {
+          menu.registerKeyPress(rotaryKnobTurns < 0 ? GEM_KEY_UP :
+GEM_KEY_DOWN);
+        }
+        rotaryKnobTurns = 0;
+        screenTime = 0;
+      }
     }
   }
-  if (screenTime > 10000)
-    if (screenBrightness != 1) {
-      screenBrightness = 1;
-      u8g2.setContrast(screenBrightness);
+  void readMIDI() {
+    MIDI.read();
+  }
+  void keepTrackOfRotaryKnobTurns() {
+    switch (rotary.process()) {
+      case DIR_CW:
+        rotaryKnobTurns++;
+        break;
+      case DIR_CCW:
+        rotaryKnobTurns--;
+        break;
     }
-}
-void sequencerSetup() {
-  if (sequencerMode) {
-    strip.clear();
-    strip.show();
-  } else {
-    setLayoutLEDs();
-    setCMD_LEDs();
   }
-}
 
-// END FUNCTIONS SECTION
+// ====== setup() and loop()
+
+  void setup() {
+  #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
+    setupMIDI();
+    setupFileSystem();
+    setupPins();
+    testDiagnostics();  // Print diagnostic troubleshooting
+information to serial monitor
+    setupGrid();
+    setupLEDs();
+    setupGFX();
+    setupMenu();
+    for (byte i = 0; i < 5 && !TinyUSBDevice.mounted(); i++) {
+      delay(1);  // wait until device mounted, maybe
+    };
+  }
+  void setup1() {
+    //
+  };
+  void loop() {   // run on first core
+    timeTracker();  // Time tracking functions
+    screenSaver();  // Reduces wear-and-tear on OLED panel
+    readHexes();       // Read and store the digital button states of
+the scanning matrix
+    actionHexes();       // actions on hexes
+    arpeggiate();      // arpeggiate the buzzer
+    updateWheels();   // deal with the pitch/mod wheel
+    animateLEDs();     // deal with animations
+    lightUpLEDs();      // refresh LEDs
+    dealWithRotary();  // deal with menu
+    readMIDI();
+  }
+  void loop1() {  // run on second core
+    keepTrackOfRotaryKnobTurns();
+  }