Firmware for HexBoard MIDI controller
Updates:
MENU
1) even more preset scales in the non-12 tunings -- some are super funky!
2) transpose should be working again
3) moved key signature change into the Scales page
4) wheel speed options are restored
FEATURES
1) POLYPHONIC BUZZER
2) wheel function back to "regular"
3) color palette improvements
STILL NOT QUITE WORKING
1) buzzer only works in polyphony mode for now -- haven't gotten mono
or arpeggio working right yet
I moved some stuff into .h headers, but no changes to the patches you
need for rotary / g8u2.
| -rw-r--r-- | Classes.h | 158 | ||||
| -rw-r--r-- | Constants.h | 140 | ||||
| -rw-r--r-- | Hexperiment.ino | 1820 | ||||
| -rw-r--r-- | Presets.h | 336 |
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
+};
|