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