Firmware for HexBoard MIDI controller
Diffstat (limited to 'Hexperiment.ino')
-rw-r--r--Hexperiment.ino1331
1 files changed, 1080 insertions, 251 deletions
diff --git a/Hexperiment.ino b/Hexperiment.ino
index 9b93007..255d91f 100644
--- a/Hexperiment.ino
+++ b/Hexperiment.ino
@@ -1,5 +1,6 @@
// ====== Hexperiment v1.2
// Copyright 2022-2023 Jared DeCook and Zach DeCook
+ // with help from Nicholas Fox
// Licensed under the GNU GPL Version 3.
// Hardware Information:
// Generic RP2040 running at 133MHz with 16MB of flash
@@ -10,14 +11,14 @@
//
// Brilliant resource for dealing with hexagonal coordinates. https://www.redblobgames.com/grids/hexagons/
// Used this to get my hexagonal animations sorted. http://ondras.github.io/rot.js/manual/#hex/indexing
- //
// Menu library documentation https://github.com/Spirik/GEM
//
- // Arduino IDE setup:
- // Board = Generic RP2040 (use the following additional board manager repo:
- // https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json)
- //
// Patches needed for U8G2, Rotary.h
+ //
+ // Wishlist:
+ // * multiple palettes per tuning
+ // * customize wheel placement and order
+ // *
// ==============================================================================
#include <Arduino.h>
@@ -35,9 +36,733 @@
#include <queue> // std::queue construct to store open channels in microtonal mode
#include <string>
- #include "Constants.h" // preprocessor constants / macros
- #include "Classes.h" // type definitions
- #include "Presets.h" // pre-load tuning, scale, palette, layout definitions
+ // 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_SINE 0
+ #define WAVEFORM_STRINGS 1
+ #define WAVEFORM_CLARINET 2
+ #define WAVEFORM_SQUARE 8
+ #define WAVEFORM_SAW 9
+ #define WAVEFORM_TRIANGLE 10
+ #define POLL_INTERVAL_IN_MICROSECONDS 24
+ // buzzer polyphony limit should be 8.
+ // MIDI should be independent and use MPE logic.
+ #define POLYPHONY_LIMIT 8
+ #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 BRIGHT_MAX 255
+ #define BRIGHT_HIGH 192
+ #define BRIGHT_MID 168
+ #define BRIGHT_LOW 144
+ #define BRIGHT_DIM 108
+
+
+ #define VALUE_BLACK 0
+ #define VALUE_LOW 127
+ #define VALUE_SHADE 170
+ #define VALUE_NORMAL 212
+ #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
+
+ // 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 tint() {
+ colorDef temp;
+ temp.hue = this->hue;
+ temp.sat = ((this->sat > SAT_TINT) ? SAT_TINT : this->sat);
+ temp.val = VALUE_FULL;
+ return temp;
+ }
+ colorDef shade() {
+ colorDef temp;
+ temp.hue = this->hue;
+ temp.sat = ((this->sat > SAT_TINT) ? SAT_TINT : this->sat);
+ temp.val = VALUE_LOW;
+ 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 LEDcodeAnim = 0; // calculate it once and store value, to make LED playback snappier
+ uint32_t LEDcodePlay = 0; // calculate it once and store value, to make LED playback snappier
+ uint32_t LEDcodeRest = 0; // calculate it once and store value, to make LED playback snappier
+ uint32_t LEDcodeOff = 0; // calculate it once and store value, to make LED playback snappier
+ uint32_t LEDcodeDim = 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 MIDIch = 0; // what MIDI channel this note is playing on
+ byte buzzCh = 0; // what buzzer ch this 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;
+ }
+ }
+ };
+
+ 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;
+ byte eq = 0;
+ };
+
+ // 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_BLUE, SAT_DULL, VALUE_SHADE }
+ , {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_BLUE, 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_BLUE, 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_LIME, SAT_DULL, VALUE_SHADE } // +
+ , {HUE_CYAN, SAT_VIVID, VALUE_NORMAL } // #/b
+ , {HUE_INDIGO, 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_DULL, VALUE_NORMAL } // ^
+ , {HUE_BLUE, SAT_VIVID, VALUE_NORMAL } // +
+ , {HUE_CYAN, SAT_DULL, VALUE_SHADE } // b
+ , {HUE_GREEN, SAT_DULL, VALUE_SHADE } // #
+ , {HUE_MAGENTA, SAT_DULL, 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_VIVID, VALUE_NORMAL } // ^
+ , {HUE_MAGENTA, SAT_DULL, VALUE_NORMAL } // L
+ , {HUE_INDIGO, SAT_VIVID, VALUE_NORMAL } // bv
+ , {HUE_GREEN, SAT_VIVID, VALUE_SHADE } // b
+ , {HUE_YELLOW, SAT_VIVID, VALUE_SHADE } // #
+ , {HUE_RED, SAT_VIVID, VALUE_NORMAL } // #^
+ , {HUE_PURPLE, SAT_DULL, VALUE_NORMAL } // 7
+ , {HUE_CYAN, SAT_VIVID, VALUE_SHADE } // 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_SHADE } // #/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
+ };
+
+ byte sine[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3,
+ 4, 5, 6, 7, 8, 9, 10, 12, 13, 15, 16, 18, 19, 21, 23, 25,
+ 27, 29, 31, 33, 35, 37, 39, 42, 44, 46, 49, 51, 54, 56, 59, 62,
+ 64, 67, 70, 73, 76, 79, 81, 84, 87, 90, 93, 96, 99, 103, 106, 109,
+ 112, 115, 118, 121, 124, 127, 131, 134, 137, 140, 143, 146, 149, 152, 156, 159,
+ 162, 165, 168, 171, 174, 176, 179, 182, 185, 188, 191, 193, 196, 199, 201, 204,
+ 206, 209, 211, 213, 216, 218, 220, 222, 224, 226, 228, 230, 232, 234, 236, 237,
+ 239, 240, 242, 243, 245, 246, 247, 248, 249, 250, 251, 252, 252, 253, 254, 254,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, 254, 253, 252, 252,
+ 251, 250, 249, 248, 247, 246, 245, 243, 242, 240, 239, 237, 236, 234, 232, 230,
+ 228, 226, 224, 222, 220, 218, 216, 213, 211, 209, 206, 204, 201, 199, 196, 193,
+ 191, 188, 185, 182, 179, 176, 174, 171, 168, 165, 162, 159, 156, 152, 149, 146,
+ 143, 140, 137, 134, 131, 127, 124, 121, 118, 115, 112, 109, 106, 103, 99, 96,
+ 93, 90, 87, 84, 81, 79, 76, 73, 70, 67, 64, 62, 59, 56, 54, 51,
+ 49, 46, 44, 42, 39, 37, 35, 33, 31, 29, 27, 25, 23, 21, 19, 18,
+ 16, 15, 13, 12, 10, 9, 8, 7, 6, 5, 4, 3, 3, 2, 1, 1
+ };
+
+ byte strings[] = {
+ 0, 0, 0, 1, 3, 6, 10, 14, 20, 26, 33, 41, 50, 59, 68, 77,
+ 87, 97, 106, 115, 124, 132, 140, 146, 152, 157, 161, 164, 166, 167, 167, 167,
+ 165, 163, 160, 157, 153, 149, 144, 140, 135, 130, 126, 122, 118, 114, 111, 109,
+ 106, 104, 103, 101, 101, 100, 100, 100, 100, 101, 101, 102, 103, 103, 104, 105,
+ 106, 107, 108, 109, 110, 111, 113, 114, 115, 116, 117, 119, 120, 121, 123, 124,
+ 126, 127, 129, 131, 132, 134, 135, 136, 138, 139, 140, 141, 142, 144, 145, 146,
+ 147, 148, 149, 150, 151, 152, 152, 153, 154, 154, 155, 155, 155, 155, 154, 154,
+ 152, 151, 149, 146, 144, 140, 137, 133, 129, 125, 120, 115, 111, 106, 102, 98,
+ 95, 92, 90, 88, 88, 88, 89, 91, 94, 98, 103, 109, 115, 123, 131, 140,
+ 149, 158, 168, 178, 187, 196, 205, 214, 222, 229, 235, 241, 245, 249, 252, 254,
+ 255, 255, 255, 254, 253, 250, 248, 245, 242, 239, 236, 233, 230, 227, 224, 222,
+ 220, 218, 216, 215, 214, 213, 212, 211, 210, 210, 209, 208, 207, 206, 205, 203,
+ 201, 199, 197, 194, 191, 188, 184, 180, 175, 171, 166, 161, 156, 150, 145, 139,
+ 133, 127, 122, 116, 110, 105, 99, 94, 89, 84, 80, 75, 71, 67, 64, 61,
+ 58, 56, 54, 52, 50, 49, 48, 47, 46, 45, 45, 44, 43, 42, 41, 40,
+ 39, 37, 35, 33, 31, 28, 25, 22, 19, 16, 13, 10, 7, 5, 2, 1
+ };
+
+ byte clarinet[] = {
+ 0, 0, 2, 7, 14, 21, 30, 38, 47, 54, 61, 66, 70, 72, 73, 74,
+ 73, 73, 72, 71, 70, 71, 72, 74, 76, 80, 84, 88, 93, 97, 101, 105,
+ 109, 111, 113, 114, 114, 114, 113, 112, 111, 110, 109, 109, 109, 110, 112, 114,
+ 116, 118, 121, 123, 126, 127, 128, 129, 128, 127, 126, 123, 121, 118, 116, 114,
+ 112, 110, 109, 109, 109, 110, 111, 112, 113, 114, 114, 114, 113, 111, 109, 105,
+ 101, 97, 93, 88, 84, 80, 76, 74, 72, 71, 70, 71, 72, 73, 73, 74,
+ 73, 72, 70, 66, 61, 54, 47, 38, 30, 21, 14, 7, 2, 0, 0, 2,
+ 9, 18, 31, 46, 64, 84, 105, 127, 150, 171, 191, 209, 224, 237, 246, 252,
+ 255, 255, 253, 248, 241, 234, 225, 217, 208, 201, 194, 189, 185, 183, 182, 181,
+ 182, 182, 183, 184, 185, 184, 183, 181, 179, 175, 171, 167, 162, 158, 154, 150,
+ 146, 144, 142, 141, 141, 141, 142, 143, 144, 145, 146, 146, 146, 145, 143, 141,
+ 139, 136, 134, 132, 129, 128, 127, 126, 127, 128, 129, 132, 134, 136, 139, 141,
+ 143, 145, 146, 146, 146, 145, 144, 143, 142, 141, 141, 141, 142, 144, 146, 150,
+ 154, 158, 162, 167, 171, 175, 179, 181, 183, 184, 185, 184, 183, 182, 182, 181,
+ 182, 183, 185, 189, 194, 201, 208, 217, 225, 234, 241, 248, 253, 255, 255, 252,
+ 246, 237, 224, 209, 191, 171, 150, 127, 105, 84, 64, 46, 31, 18, 9, 2,
+ };
// ====== useful math functions
int positiveMod(int n, int d) {
@@ -47,8 +772,8 @@
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;};
+ if (temp < xOne) {temp = xOne;}
+ if (temp > xTwo) {temp = xTwo;}
return temp;
}
@@ -110,15 +835,15 @@
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.
- byte enableMIDI = 1;
const byte layoutCount = sizeof(layoutOptions) / sizeof(layoutDef);
const byte scaleCount = sizeof(scaleOptions) / sizeof(scaleDef);
// 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};
+ std::queue<byte> MPEchQueue;
+ std::queue<byte> buzzChQueue;
+ const byte attenuation[] = {24,24,17,14,12,11,10,9,8}; // kind of a fast inverse square
+
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
@@ -131,10 +856,10 @@
int modWheelSpeed = 8;
int pbWheelSpeed = 1024;
- wheelDef modWheel = { false, false, // standard mode, not sticky
+ wheelDef modWheel = { false, true, // standard mode, sticky
&h[assignCmd[4]].btnState, &h[assignCmd[5]].btnState, &h[assignCmd[6]].btnState,
0, 127, &modWheelSpeed, 0, 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
@@ -145,6 +870,7 @@
};
bool toggleWheel = 0; // 0 for mod, 1 for pb
+
// 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
@@ -159,10 +885,21 @@
GEMPage menuPageScales("Scales");
GEMItem menuGotoScales("Scales", menuPageScales);
GEMItem menuScalesBack("<< Back", menuPageMain);
+ GEMPage menuPageColors("Color options");
+ GEMItem menuGotoColors("Color options", menuPageColors);
+ GEMItem menuColorsBack("<< Back", menuPageMain);
+ GEMPage menuPageSynth("Buzzer options");
+ GEMItem menuGotoSynth("Buzzer options", menuPageSynth);
+ GEMItem menuSynthBack("<< Back", menuPageMain);
GEMPage menuPageControl("Control wheel");
GEMItem menuGotoControl("Control wheel", menuPageControl);
GEMItem menuControlBack("<< Back", menuPageMain);
-
+ GEMPage menuPageTesting("Testing");
+ GEMItem menuGotoTesting("Testing", menuPageTesting);
+ GEMItem menuTestingBack("<< Back", menuPageMain);
+ char* blank = "";
+ GEMItem menuItemVersion("v0.6.0",blank,true);
+
// the following get initialized in the setup() routine.
GEMItem* menuItemTuning[TUNINGCOUNT];
GEMItem* menuItemLayout[layoutCount];
@@ -170,16 +907,20 @@
GEMSelect* selectKey[TUNINGCOUNT];
GEMItem* menuItemKeys[TUNINGCOUNT];
- void resetHexLEDs(); // forward-declaration
+ byte paletteBeginsAtKeyCenter = 1;
+
+ void setLEDcolorCodes(); // 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 menuItemPercep( "Fix color:", perceptual, selectYesOrNo, setLEDcolorCodes);
+ GEMItem menuItemShiftColor( "ColorByKey", paletteBeginsAtKeyCenter, selectYesOrNo, setLEDcolorCodes);
+
+ void resetBuzzers(); // forward declaration
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);
+ GEMItem menuItemPlayback( "Buzzer:", playbackMode, selectPlayback, resetBuzzers);
void changeTranspose(); // forward-declaration
int transposeSteps = 0;
@@ -209,7 +950,7 @@
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);
+ GEMItem menuItemColor( "Color mode:", colorMode, selectColor, setLEDcolorCodes);
byte animationType = ANIMATE_NONE;
SelectOptionByte optionByteAnimate[] = { { "None" , ANIMATE_NONE }, { "Octave", ANIMATE_OCTAVE },
@@ -217,8 +958,14 @@
GEMSelect selectAnimate( sizeof(optionByteAnimate) / sizeof(SelectOptionByte), optionByteAnimate);
GEMItem menuItemAnimate( "Animation:", animationType, selectAnimate);
+ byte globalBrightness = BRIGHT_MID;
+ SelectOptionByte optionByteBright[] = { { "Dim", BRIGHT_DIM}, {"Low", BRIGHT_LOW}, {"Normal", BRIGHT_MID}, {"High", BRIGHT_HIGH}, {"THE SUN", BRIGHT_MAX } };
+ GEMSelect selectBright( sizeof(optionByteBright) / sizeof(SelectOptionByte), optionByteBright);
+ GEMItem menuItemBright( "Brightness", globalBrightness, selectBright, setLEDcolorCodes);
+
byte currWave = WAVEFORM_SAW;
- SelectOptionByte optionByteWaveform[] = { { "Square", WAVEFORM_SQUARE }, { "Saw", WAVEFORM_SAW } };
+ SelectOptionByte optionByteWaveform[] = { { "Square", WAVEFORM_SQUARE }, { "Saw", WAVEFORM_SAW },
+ {"Triangl", WAVEFORM_TRIANGLE}, {"Sine", WAVEFORM_SINE}, {"Strings", WAVEFORM_STRINGS}, {"Clrinet", WAVEFORM_CLARINET} };
GEMSelect selectWaveform(sizeof(optionByteWaveform) / sizeof(SelectOptionByte), optionByteWaveform);
GEMItem menuItemWaveform( "Waveform:", currWave, selectWaveform);
@@ -233,6 +980,7 @@
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.
class presetDef {
public:
@@ -245,13 +993,13 @@
// define simple recall functions
tuningDef tuning() {
return tuningOptions[tuningIndex];
- };
+ }
layoutDef layout() {
return layoutOptions[layoutIndex];
- };
+ }
scaleDef scale() {
return scaleOptions[scaleIndex];
- };
+ }
int layoutsBegin() {
if (tuningIndex == TUNING_12EDO) {
return 0;
@@ -259,19 +1007,19 @@
int temp = 0;
while (layoutOptions[temp].tuning < tuningIndex) {
temp++;
- };
+ }
return temp;
- };
- };
+ }
+ }
int keyStepsFromC() {
- return -(tuning().spanCtoA() + keyStepsFromA);
- };
+ return tuning().spanCtoA() - keyStepsFromA;
+ }
int pitchRelToA4(int givenStepsFromC) {
- return givenStepsFromC - tuning().spanCtoA() + transpose;
- };
+ return givenStepsFromC + tuning().spanCtoA() + transpose;
+ }
int keyDegree(int givenStepsFromC) {
return positiveMod(givenStepsFromC + keyStepsFromC(), tuning().cycleLength);
- };
+ }
};
presetDef current = {
@@ -279,7 +1027,7 @@
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.
+ -9, // default to the key of C, which in 12EDO is -9 steps from A.
0 // default to no transposition
};
@@ -288,34 +1036,41 @@
void sendToLog(std::string msg) {
if (diagnostics) {
Serial.println(msg.c_str());
- };
+ }
}
// ====== LED routines
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);
+ if (!perceptual) {
+ return 65536 * D / 360;
+ } else {
+ // red yellow green cyan blue
+ int hueIn[] = { 0, 9, 18, 102, 117, 135, 142, 155, 203, 240, 252, 261, 306, 333, 360};
+ // #ff0000 #ffff00 #00ff00 #00ffff #0000ff #ff00ff
+ int hueOut[] = { 0, 3640, 5861,10922,12743,16384,21845,27306,32768,38229,43690,49152,54613,58254,65535};
+ 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));
+ return strip.gamma32(strip.ColorHSV(transformHue(c.hue),c.sat,c.val * globalBrightness / 255));
}
- void resetHexLEDs() { // calculate color codes for each hex, store for playback
+ void setLEDcolorCodes() { // calculate color codes for each hex, store for playback
for (byte i = 0; i < LED_COUNT; i++) {
if (!(h[i].isCmd)) {
colorDef setColor;
byte paletteIndex = positiveMod(h[i].stepsFromC,current.tuning().cycleLength);
+ if (paletteBeginsAtKeyCenter) {
+ paletteIndex = current.keyDegree(paletteIndex);
+ }
switch (colorMode) {
case TIERED_COLOR_MODE:
setColor = palette[current.tuningIndex].getColor(paletteIndex);
@@ -327,14 +1082,15 @@
, VALUE_NORMAL
};
break;
- };
- h[i].LEDcolorOn = getLEDcode(setColor);
- h[i].LEDcolorPlay = getLEDcode(setColor.mixWithWhite()); // "mix with white"
+ }
+ h[i].LEDcodeRest = getLEDcode(setColor);
+ h[i].LEDcodePlay = getLEDcode(setColor.tint());
+ h[i].LEDcodeDim = getLEDcode(setColor.shade());
setColor = {HUE_NONE,SAT_BW,VALUE_BLACK};
- h[i].LEDcolorOff = getLEDcode(setColor); // turn off entirely
- h[i].LEDcolorAnim = h[i].LEDcolorPlay;
- };
- };
+ h[i].LEDcodeOff = getLEDcode(setColor); // turn off entirely
+ h[i].LEDcodeAnim = h[i].LEDcodePlay;
+ }
+ }
sendToLog("LED codes re-calculated.");
}
@@ -381,14 +1137,15 @@
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;
- };
+ uint32_t applyNotePixelColor(byte x) {
+ if (h[x].animate) { return h[x].LEDcodeAnim;
+ } else if (h[x].MIDIch) { return h[x].LEDcodePlay;
+ } else if (h[x].inScale) { return h[x].LEDcodeRest;
+ } else if (scaleLock) { return h[x].LEDcodeOff;
+ } else { return h[x].LEDcodeDim;
+ }
}
// ====== layout routines
@@ -420,7 +1177,7 @@
h[i].note = ((N >= 127) ? 127 : round(N));
h[i].bend = (ldexp(N - h[i].note, 13) / PITCH_BEND_SEMIS);
h[i].frequency = MIDItoFreq(N);
- };
+ }
sendToLog(
"hex #" + std::to_string(i) + ", " +
"steps=" + std::to_string(h[i].stepsFromC) + ", " +
@@ -430,8 +1187,8 @@
"freq=" + std::to_string(h[i].frequency) + ", " +
"inScale? " + std::to_string(h[i].inScale) + "."
);
- };
- };
+ }
+ }
sendToLog("assignPitches complete.");
}
void applyScale() {
@@ -450,10 +1207,10 @@
while (degree > tempSum) {
tempSum += current.scale().pattern[iterator];
iterator++;
- }; // add the steps in the scale, and you're in scale
+ } // 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].stepsFromC) + ", " +
@@ -461,9 +1218,9 @@
"note=" + std::to_string(h[i].note) + ", " +
"inScale? " + std::to_string(h[i].inScale) + "."
);
- };
- };
- resetHexLEDs();
+ }
+ }
+ setLEDcolorCodes();
sendToLog("applyScale complete.");
}
@@ -484,8 +1241,8 @@
"hex #" + std::to_string(i) + ", " +
"steps from C4=" + std::to_string(h[i].stepsFromC) + "."
);
- };
- };
+ }
+ }
applyScale(); // when layout changes, have to re-apply scale and re-apply LEDs
assignPitches(); // same with pitches
u8g2.setDisplayRotation(current.layout().isPortrait ? U8G2_R2 : U8G2_R1); // and landscape / portrait rotation
@@ -537,101 +1294,135 @@
void poll() {
hw_clear_bits(&timer_hw->intr, 1u << ALARM_NUM);
timer_hw->alarm[ALARM_NUM] = timer_hw->timerawl + POLL_INTERVAL_IN_MICROSECONDS;
- uint16_t lvl = 0;
+ uint32_t mix = 0;
+ byte voices = POLYPHONY_LIMIT;
byte p;
+ byte level = 0;
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
- };
- lvl = (lvl * attenuation[poly]) >> 8; // cap = 3825 * 17 / 256 = 254
- lvl = (lvl * velWheel.curValue) >> 7;
- pwm_set_chan_level(TONE_SL, TONE_CH, lvl);
+ if (synth[i].increment) {
+ synth[i].counter += synth[i].increment; // should loop from 65536 -> 0
+ p = (synth[i].counter >> 8);
+ switch (currWave) {
+ case WAVEFORM_SAW: break;
+ case WAVEFORM_TRIANGLE: p = 2 * ((p < 128) ? p : (255 - p)); break;
+ case WAVEFORM_SQUARE: p = 0 - (p > (128 - modWheel.curValue)); break;
+ case WAVEFORM_SINE: p = sine[p]; break;
+ case WAVEFORM_STRINGS: p = strings[p]; break;
+ case WAVEFORM_CLARINET: p = clarinet[p]; break;
+ default: break;
+ }
+ mix += (p * synth[i].eq); // P[8bit] * EQ[8bit] =[16bit]
+ } else {
+ --voices;
+ }
+ }
+ mix = mix * attenuation[voices] * velWheel.curValue; // [16bit]*vel[7bit]=[23bit], poly+atten=[6bit] = [29bit]
+ level = mix >> 21; // [29bit] - [8bit] = [21bit]
+ pwm_set_chan_level(TONE_SL, TONE_CH, level);
}
// 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
+ byte isoTwoTwentySix(float f) {
+ // a very crude implementation of ISO 226
+ // equal loudness curves
+ // Hz dB Amp = sqrt(10^(dB/10))
+ // 200 0 255
+ // 800 -3 181
+ // 1500 0 255
+ // 3250 -6 127
+ // 5000 0 255
+ if ((f < 8.0) || (f > 12500.0)) { // really crude low- and high-pass
+ return 0;
} else {
- synth[ch].increment = 0; // zero effectively silences the channel
- };
- }
-
-// ====== MIDI routines
- void setAllNotesOff(byte Ch) {
- if (enableMIDI) {
- MIDI.sendControlChange(123, 0, Ch);
+ if ((f <= 200.0) || (f >= 5000.0)) {
+ return 255;
+ } else {
+ if (f < 1500.0) {
+ return 181 + 74 * (float)(abs(f-800) / 700);
+ } else {
+ return 127 + 128 * (float)(abs(f-3250) / 1750);
+ }
+ }
}
}
+ void setBuzzer(float f, byte c) {
+ synth[c - 1].counter = 0;
+ float FwithPB = f * exp2(pbWheel.curValue * PITCH_BEND_SEMIS / 98304.0);
+ synth[c - 1].increment = round(FwithPB * POLL_INTERVAL_IN_MICROSECONDS * 0.065536); // cycle 0-65535 at resultant frequency
+ synth[c - 1].eq = isoTwoTwentySix(FwithPB);
+ }
+ // USE THIS IN MONO OR ARPEG MODE ONLY
+ void replaceBuzzerWith(byte x) {
+ setBuzzer(0, 1);
+ arpeggiatingNow = x;
+ setBuzzer(h[arpeggiatingNow].frequency, 1);
+ }
+// ====== MIDI routines
void setPitchBendRange(byte Ch, byte semitones) {
- if (enableMIDI) { // MIDI mode only
- MIDI.beginRpn(0, Ch);
- MIDI.sendRpnValue(semitones << 7, Ch);
- MIDI.endRpn(Ch);
- sendToLog(
- "set pitch bend range on ch " +
- std::to_string(Ch) + " to be " +
- std::to_string(semitones) + " semitones"
- );
- }
+ MIDI.beginRpn(0, Ch);
+ MIDI.sendRpnValue(semitones << 7, Ch);
+ MIDI.endRpn(Ch);
+ sendToLog(
+ "set pitch bend range on ch " +
+ std::to_string(Ch) + " to be " +
+ std::to_string(semitones) + " semitones"
+ );
}
void setMPEzone(byte masterCh, byte sizeOfZone) {
- if (enableMIDI) { // MIDI mode only
- MIDI.beginRpn(6, masterCh);
- MIDI.sendRpnValue(sizeOfZone << 7, masterCh);
- MIDI.endRpn(masterCh);
- sendToLog(
- "tried sending MIDI msg to set MPE zone, master ch " +
- std::to_string(masterCh) + ", zone of this size: " + std::to_string(sizeOfZone)
- );
+ MIDI.beginRpn(6, masterCh);
+ MIDI.sendRpnValue(sizeOfZone << 7, masterCh);
+ MIDI.endRpn(masterCh);
+ sendToLog(
+ "tried sending MIDI msg to set MPE zone, master ch " +
+ std::to_string(masterCh) + ", zone of this size: " + std::to_string(sizeOfZone)
+ );
+ }
+ void resetBuzzers() {
+ while (!buzzChQueue.empty()) {
+ buzzChQueue.pop();
+ }
+ for (byte i = 0; i < POLYPHONY_LIMIT; i++) {
+ synth[i].increment = 0;
+ synth[i].counter = 0;
+ }
+ if (playbackMode == BUZZ_POLY) {
+ for (byte i = 0; i < POLYPHONY_LIMIT; i++) {
+ buzzChQueue.push(i + 1);
+ }
}
}
- void applyMPEmode() {
- while (!openChannelQueue.empty()) { // empty the channel queue
- openChannelQueue.pop();
- };
+ void resetTuningMIDI() {
+ while (!MPEchQueue.empty()) { // empty the channel queue
+ MPEchQueue.pop();
+ }
for (byte i = 1; i <= 16; i++) {
- setAllNotesOff(i); // turn off all notes
+ MIDI.sendControlChange(123, 0, i);
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");
- };
+ }
+ if (current.tuningIndex == TUNING_12EDO) {
+ setMPEzone(1, 0);
+ } else {
+ setMPEzone(1, 15); // MPE zone 1 = ch 2 thru 16
+ for (byte i = 0; i < 15; i++) {
+ MPEchQueue.push(i + 2);
+ sendToLog("pushed ch " + std::to_string(i + 2) + " to the open channel queue");
+ }
+ }
+ resetBuzzers();
}
void chgModulation() {
- if (enableMIDI) { // MIDI mode only
- MIDI.sendControlChange(1, modWheel.curValue, 1);
- sendToLog("sent mod value " + std::to_string(modWheel.curValue) + " to ch 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
- 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
- };
- };
- };
+ MIDI.sendPitchBend(pbWheel.curValue, 1);
+ for (byte i = 0; i < LED_COUNT; i++) {
+ if (!(h[i].isCmd)) {
+ if (h[i].buzzCh) {
+ setBuzzer(h[i].frequency,h[i].buzzCh); // rebuzz all notes if the pitch bend changes
+ }
+ }
sendToLog("sent pb wheel value " + std::to_string(pbWheel.curValue) + " to ch 1");
- };
+ }
}
// ====== hex press routines
@@ -639,52 +1430,79 @@
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");
+ if (!(h[x].MIDIch)) {
+ if (current.tuningIndex == TUNING_12EDO) {
+ h[x].MIDIch = 1;
} else {
- 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)
- );
- };
- };
- };
- }
+ if (MPEchQueue.empty()) { // if there aren't any open channels
+ sendToLog("MPE queue was empty so did not play a midi note");
+ } else {
+ h[x].MIDIch = MPEchQueue.front(); // value in MIDI terms (1-16)
+ MPEchQueue.pop();
+ sendToLog("popped " + std::to_string(h[x].MIDIch) + " off the MPE queue");
+ MIDI.sendPitchBend(h[x].bend, h[x].MIDIch); // ch 1-16
+ }
+ }
+ if (h[x].MIDIch) {
+ MIDI.sendNoteOn(h[x].note, velWheel.curValue, h[x].MIDIch); // 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].MIDIch)
+ );
+ }
+ }
+ if (playbackMode != BUZZ_OFF) {
+ if (playbackMode == BUZZ_POLY) {
+ // operate independently of MIDI
+ if (buzzChQueue.empty()) {
+ sendToLog("synths all firing, so did not buzz");
+ } else {
+ h[x].buzzCh = buzzChQueue.front();
+ buzzChQueue.pop();
+ sendToLog("popped " + std::to_string(h[x].buzzCh) + " off the synth queue");
+ setBuzzer(h[x].frequency, h[x].buzzCh);
+ }
+ } else {
+ // operate in lockstep with MIDI
+ if (h[x].MIDIch) {
+ replaceBuzzerWith(x);
+ }
+ }
+ }
+ }
+
+ // check - full death if over 8 poly.
+
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 == BUZZ_ARPEGGIO) {
+ if (h[x].MIDIch) { // but just in case, check
+ MIDI.sendNoteOff(h[x].note, velWheel.curValue, h[x].MIDIch);
+ 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(h[x].MIDIch)
+ );
+ if (current.tuningIndex != TUNING_12EDO) {
+ MPEchQueue.push(h[x].MIDIch);
+ sendToLog("pushed " + std::to_string(h[x].MIDIch) + " on the MPE queue");
+ }
+ h[x].MIDIch = 0;
+ if (!(playbackMode == BUZZ_OFF) && !(playbackMode == BUZZ_POLY)) {
+ setBuzzer(0, 1);
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(h[x].channel)
- );
- };
- h[x].channel = 0;
- };
+ }
+ }
+ if (playbackMode == BUZZ_POLY) {
+ if (h[x].buzzCh) {
+ setBuzzer(0, h[x].buzzCh);
+ buzzChQueue.push(h[x].buzzCh);
+ h[x].buzzCh = 0;
+ }
+ }
}
void cmdOn(byte x) { // volume and mod wheel read all current buttons
switch (h[x].note) {
@@ -694,13 +1512,13 @@
default:
// the rest should all be taken care of within the wheelDef structure
break;
- };
+ }
}
void cmdOff(byte x) { // pitch bend wheel only if buttons held.
switch (h[x].note) {
default:
break; // nothing; should all be taken care of within the wheelDef structure
- };
+ }
}
// ====== animations
@@ -709,7 +1527,7 @@
return 1 + (((runTime - h[x].timePressed) * animationFPS) >> 20);
} else {
return 0;
- };
+ }
}
void flagToAnimate(int8_t r, int8_t c) {
if (!
@@ -719,38 +1537,38 @@
)
) {
h[(10 * r) + (c / 2)].animate = 1;
- };
+ }
}
void animateMirror() {
for (byte i = 0; i < LED_COUNT; i++) { // check every hex
- if ((!(h[i].isCmd)) && (h[i].channel)) { // that is a held note
+ if ((!(h[i].isCmd)) && (h[i].MIDIch)) { // 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
+ if ((!(h[j].isCmd)) && (!(h[j].MIDIch))) { // that is a note not being played
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
h[j].animate = 1;
- };
- };
- };
- };
- };
+ }
+ }
+ }
+ }
+ }
}
void animateOrbit() {
for (byte i = 0; i < LED_COUNT; i++) { // check every hex
- if ((!(h[i].isCmd)) && (h[i].channel) && ((h[i].inScale) || (!scaleLock))) { // that is a held note
+ if ((!(h[i].isCmd)) && (h[i].MIDIch) && ((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) && ((h[i].inScale) || (!scaleLock))) { // that is a note
+ if (!(h[i].isCmd)) { // 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
@@ -761,11 +1579,11 @@
flagToAnimate(turtleRow,turtleCol); // flag for animation
turtleRow += (vertical[dir] * (radius / steps));
turtleCol += (horizontal[dir] * (radius / steps));
- };
- };
- };
- };
- };
+ }
+ }
+ }
+ }
+ }
}
// ====== menu routines
@@ -776,19 +1594,19 @@
void showOnlyValidLayoutChoices() { // re-run at setup and whenever tuning changes
for (byte L = 0; L < layoutCount; L++) {
menuItemLayout[L]->hide((layoutOptions[L].tuning != current.tuningIndex));
- };
+ }
sendToLog("menu: Layout choices were updated.");
}
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 != ALL_TUNINGS));
- };
+ }
sendToLog("menu: Scale choices were updated.");
}
void showOnlyValidKeyChoices() { // re-run at setup and whenever tuning changes
for (int T = 0; T < TUNINGCOUNT; T++) {
menuItemKeys[T]->hide((T != current.tuningIndex));
- };
+ }
sendToLog("menu: Key choices were updated.");
}
void changeLayout(GEMCallbackData callbackData) { // when you change the layout via the menu
@@ -796,7 +1614,7 @@
if (selection != current.layoutIndex) {
current.layoutIndex = selection;
applyLayout();
- };
+ }
menuHome();
}
void changeScale(GEMCallbackData callbackData) { // when you change the scale via the menu
@@ -804,7 +1622,7 @@
if (selection != current.scaleIndex) {
current.scaleIndex = selection;
applyScale();
- };
+ }
menuHome();
}
void changeKey() { // when you change the key via the menu
@@ -825,21 +1643,22 @@
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
- };
+ resetTuningMIDI
+ (); // clear out MIDI queue
+ }
menuHome();
}
void createTuningMenuItems() {
for (byte T = 0; T < TUNINGCOUNT; T++) {
menuItemTuning[T] = new GEMItem(tuningOptions[T].name.c_str(), changeTuning, T);
menuPageTuning.addMenuItem(*menuItemTuning[T]);
- };
+ }
}
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();
}
void createKeyMenuItems() {
@@ -847,14 +1666,14 @@
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();
}
@@ -865,7 +1684,8 @@
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();
+ resetTuningMIDI
+();
sendToLog("setupMIDI okay");
}
void setupFileSystem() {
@@ -899,11 +1719,11 @@
h[i].isCmd = 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();
}
@@ -911,7 +1731,7 @@
strip.begin(); // INITIALIZE NeoPixel strip object
strip.show(); // Turn OFF all pixels ASAP
sendToLog("LEDs started...");
- resetHexLEDs();
+ setLEDcolorCodes();
}
void setupMenu() {
menu.setSplashDelay(0);
@@ -932,11 +1752,21 @@
menuPageControl.addMenuItem(menuItemModSpeed);
menuPageControl.addMenuItem(menuItemVelSpeed);
menuPageControl.addMenuItem(menuControlBack);
+ menuPageMain.addMenuItem(menuGotoColors);
+ menuPageColors.addMenuItem(menuItemColor);
+ menuPageColors.addMenuItem(menuItemBright);
+ menuPageColors.addMenuItem(menuItemAnimate);
+ menuPageColors.addMenuItem(menuColorsBack);
+ menuPageMain.addMenuItem(menuGotoSynth);
+ menuPageSynth.addMenuItem(menuItemPlayback);
+ menuPageSynth.addMenuItem(menuItemWaveform);
+ menuPageSynth.addMenuItem(menuSynthBack);
menuPageMain.addMenuItem(menuItemTransposeSteps);
- menuPageMain.addMenuItem(menuItemColor);
- menuPageMain.addMenuItem(menuItemPlayback);
- menuPageMain.addMenuItem(menuItemWaveform);
- menuPageMain.addMenuItem(menuItemAnimate);
+ menuPageMain.addMenuItem(menuGotoTesting);
+ menuPageTesting.addMenuItem(menuItemVersion);
+ menuPageTesting.addMenuItem(menuItemPercep);
+ menuPageTesting.addMenuItem(menuItemShiftColor);
+ menuPageTesting.addMenuItem(menuTestingBack);
menuHome();
}
void setupGFX() {
@@ -994,7 +1824,7 @@
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).
}
}
@@ -1007,21 +1837,21 @@
cmdOn(i);
} else if (h[i].inScale || (!scaleLock)) {
playNote(i);
- };
+ }
break;
case 2: // just released
if (h[i].isCmd) {
cmdOff(i);
} else if (h[i].inScale || (!scaleLock)) {
stopNote(i);
- };
+ }
break;
case 3: // held
break;
default: // inactive
break;
- };
- };
+ }
+ }
}
void arpeggiate() {
@@ -1031,17 +1861,17 @@
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)) {
+ if ((h[j].MIDIch) && (!h[j].isCmd)) {
n = j;
break;
- };
- };
+ }
+ }
arpeggiatingNow = n;
if (n != UNUSED_NOTE) {
- buzz(n, true);
- };
- };
- };
+ replaceBuzzerWith(n);
+ }
+ }
+ }
}
void updateWheels() {
velWheel.setTargetValue();
@@ -1054,20 +1884,20 @@
upd = pbWheel.updateValue(runTime);
if (upd) {
chgUniversalPB();
- };
+ }
} else {
modWheel.setTargetValue();
upd = modWheel.updateValue(runTime);
if (upd) {
chgModulation();
- };
- };
+ }
+ }
}
void animateLEDs() {
for (byte i = 0; i < LED_COUNT; i++) {
h[i].animate = 0;
- };
+ }
if (animationType) {
switch (animationType) {
case ANIMATE_STAR: case ANIMATE_SPLASH:
@@ -1081,15 +1911,15 @@
break;
default:
break;
- };
- };
+ }
+ }
}
void lightUpLEDs() {
for (byte i = 0; i < LED_COUNT; i++) {
if (!(h[i].isCmd)) {
strip.setPixelColor(i,applyNotePixelColor(i));
}
- };
+ }
resetVelocityLEDs();
resetWheelLEDs();
strip.show();
@@ -1142,7 +1972,7 @@
setupMenu();
for (byte i = 0; i < 5 && !TinyUSBDevice.mounted(); i++) {
delay(1); // wait until device mounted, maybe
- };
+ }
}
void setup1() { // set up on second core
setupPiezo();
@@ -1157,7 +1987,6 @@
animateLEDs(); // deal with animations
lightUpLEDs(); // refresh LEDs
dealWithRotary(); // deal with menu
- readMIDI();
}
void loop1() { // run on second core
keepTrackOfRotaryKnobTurns();