Firmware for HexBoard MIDI controller
Diffstat (limited to 'Hexperiment.ino')
-rw-r--r--Hexperiment.ino1946
1 files changed, 1037 insertions, 909 deletions
diff --git a/Hexperiment.ino b/Hexperiment.ino
index 40f56b5..a5aab7a 100644
--- a/Hexperiment.ino
+++ b/Hexperiment.ino
@@ -1,786 +1,958 @@
-// ====== Hexperiment
- // Sketch to program the hexBoard to handle microtones
- // March 2024, theHDM / Nicholas Fox
- // major thanks to Zach and Jared!
+// ====== Hexperiment v1.2
+ // Copyright 2022-2023 Jared DeCook and Zach DeCook
+ // Licensed under the GNU GPL Version 3.
+ // Hardware Information:
+ // Generic RP2040 running at 133MHz with 16MB of flash
+ // https://github.com/earlephilhower/arduino-pico
+ // (Additional boards manager URL: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json)
+ // Tools > USB Stack > (Adafruit TinyUSB)
+ // Sketch > Export Compiled Binary
+ //
+ // Brilliant resource for dealing with hexagonal coordinates. https://www.redblobgames.com/grids/hexagons/
+ // Used this to get my hexagonal animations sorted. http://ondras.github.io/rot.js/manual/#hex/indexing
+ //
+ // Menu library documentation https://github.com/Spirik/GEM
+ //
// Arduino IDE setup:
// Board = Generic RP2040 (use the following additional board manager repo:
// https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json)
+ //
// Patches needed for U8G2, Rotary.h
// ==============================================================================
- // list of things remaining to do:
- // -- get MPE to work on pianoteq -- OK, for the most part
- // -- get MPE to work on logic pro
- // -- get MPE to work on garageband iOS
- // -- save and load presets
- // -- sequencer restore
- // ==============================================================================
#include <Arduino.h>
+ #include <Adafruit_TinyUSB.h>
+ #include "LittleFS.h"
+ #include <MIDI.h>
+ #include <Adafruit_NeoPixel.h>
+ #define GEM_DISABLE_GLCD
+ #include <GEM_u8g2.h>
#include <Wire.h>
- #include <LittleFS.h>
+ #include <Rotary.h>
+ #include "hardware/pwm.h"
+ #include "hardware/timer.h"
+ #include "hardware/irq.h"
#include <queue> // std::queue construct to store open channels in microtonal mode
- const byte diagnostics = 0;
-
-// ====== initialize timers
-
- uint32_t runTime = 0; // Program loop consistent variable for time in milliseconds since power on
- uint32_t lapTime = 0; // Used to keep track of how long each loop takes. Useful for rate-limiting.
- uint32_t loopTime = 0; // Used to check speed of the loop in diagnostics mode 4
- // since we're already using a hardware timer, might want to consider having these timers also rely on timer_hw too.
-
-// ====== initialize SDA and SCL pins for hardware I/O
+ #include <string>
- #define SDAPIN 16
- #define SCLPIN 17
-
-// ====== initialize MIDI
+ // hardware pins
+ #define SDAPIN 16
+ #define SCLPIN 17
+
+ // 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;
+
+ // 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; //
+
+ // 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;
+
+ // 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.
+
+ // 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
- #include <Adafruit_TinyUSB.h>
- #include <MIDI.h>
- Adafruit_USBD_MIDI usb_midi;
- MIDI_CREATE_INSTANCE(Adafruit_USBD_MIDI, usb_midi, MIDI);
- #define concertA 440.0
- 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.
- int16_t channelBend[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // what's the current note bend on this channel
- byte channelPoly[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // how many notes are playing on this channel
- std::queue<byte> openChannelQueue;
- #define defaultPBRange 2
-
-// ====== initialize LEDs
-
- #include <Adafruit_NeoPixel.h>
- const byte multiplexPins[] = { 4, 5, 2, 3 }; // m1p, m2p, m4p, m8p
- const byte columnPins[] = { 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
- #define ROWCOUNT 14
- #define COLCOUNT 10
- #define HEXCOUNT 140
-
- #define LEDPIN 22
- Adafruit_NeoPixel strip(HEXCOUNT, LEDPIN, NEO_GRB + NEO_KHZ800);
- enum { NoAnim, StarAnim, SplashAnim, OrbitAnim, OctaveAnim, NoteAnim };
- byte animationType = 0;
- byte animationFPS = 32; // actually frames per 2^10 seconds. close enough to 30fps
- int16_t rainbowDegreeTime = 64; // ms to go through 1/360 of rainbow.
-
-// ====== initialize hex state object
-
- enum { Right, UpRight, UpLeft, Left, DnLeft, DnRight };
- typedef struct {
- int8_t row;
- int8_t col;
- } coordinates;
- typedef struct {
- byte keyState = 0; // binary 00 = off, 01 = just pressed, 10 = just released, 11 = held
- coordinates coords = {0,0};
- uint32_t timePressed = 0; // timecode of last press
- uint32_t LEDcolorAnim = 0; //
- uint32_t LEDcolorPlay = 0; //
- uint32_t LEDcolorOn = 0; //
- uint32_t LEDcolorOff = 0; //
- bool animate = 0; // hex is flagged as part of the animation in this frame
- int16_t steps = 0; // number of steps from key center (semitones in 12EDO; microtones if >12EDO)
- bool isCmd = 0; // 0 if it's a MIDI note; 1 if it's a MIDI control cmd
- bool inScale = 0; // 0 if it's not in the selected scale; 1 if it is
- byte note = 255; // MIDI note or control parameter corresponding to this hex
- int16_t bend; // in microtonal mode, the pitch bend for this note needed to be tuned correctly
- byte channel; // what MIDI channel this note is playing on
- float frequency; // what frequency to ring on the buzzer
- void updateKeyState(bool keyPressed) {
- keyState = (((keyState << 1) + keyPressed) & 3);
- if (keyState == 1) {
- timePressed = millis(); // log the time
- };
+ // 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
};
- uint32_t animFrame() {
- if (timePressed) { // 2^10 milliseconds is close enough to 1 second
- return 1 + (((runTime - timePressed) * animationFPS) >> 10);
- } else {
- return 0;
+ #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
+ };
};
- };
- } buttonDef;
- buttonDef h[HEXCOUNT]; // array of hex objects from 0 to 139
- const byte assignCmd[] = { 0, 20, 40, 60, 80, 100, 120 };
- const byte cmdCount = 7;
- const byte cmdCode = 192;
-
-// ====== initialize wheel emulation
-
- const uint16_t ccMsgCoolDown = 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;
+ 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 }
+ };
+ const byte layoutCount = sizeof(layoutOptions) / sizeof(layoutDef);
+
+ // 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 }
};
- bool updateValue() {
- int16_t temp = targetValue - curValue;
- if (temp != 0) {
- if ((runTime - timeLastChanged) >= ccMsgCoolDown) {
- timeLastChanged = runTime;
- if (abs(temp) < stepValue) {
- curValue = targetValue;
+ const byte tuningCount = sizeof(tuningOptions) / sizeof(tuningDef);
+
+ #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
+
+ 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;
+
+ // 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; //
+
+ // 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 {
- curValue = curValue + (stepValue * (temp / abs(temp)));
+ return 0;
};
- 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 pbWheel = {
- &h[assignCmd[4]].keyState,
- &h[assignCmd[5]].keyState,
- &h[assignCmd[6]].keyState,
- -8192, 8192, 1024,
- 0, 0, 0
- };
- wheelDef velWheel = {
- &h[assignCmd[0]].keyState,
- &h[assignCmd[1]].keyState,
- &h[assignCmd[2]].keyState,
- 0, 127, 8,
- 96, 96, 96
- };
- bool toggleWheel = 0; // 0 for mod, 1 for pb
-
-// ====== initialize rotary knob
-
- #include <Rotary.h>
- #define rotaryPinA 20
- #define rotaryPinB 21
- #define rotaryPinC 24
- Rotary rotary = Rotary(rotaryPinA, rotaryPinB);
- bool rotaryIsClicked = HIGH; //
- bool rotaryWasClicked = HIGH; //
- int8_t rotaryKnobTurns = 0; //
- byte maxKnobTurns = 10;
-
-// ====== initialize GFX display
-
- #include <GEM_u8g2.h>
- #define GEM_DISABLE_GLCD
- U8G2_SH1107_SEEED_128X128_F_HW_I2C u8g2(U8G2_R2, U8X8_PIN_NONE);
- GEM_u8g2 menu(u8g2, GEM_POINTER_ROW, GEM_ITEMS_COUNT_AUTO, 10, 10, 78); // menu item height; page screen top offset; menu values left offset
- const byte defaultContrast = 63; // GFX default contrast
- bool screenSaverOn = 0; //
- uint32_t screenTime = 0; // GFX timer to count if screensaver should go on
- const uint32_t screenSaverMillis = 10'000; //
-
-// ====== initialize piezo buzzer
-
- #include "hardware/pwm.h"
- #include "hardware/timer.h"
- #include "hardware/irq.h"
-
- #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
-
- typedef struct {
- char* name;
- byte lvl[WAVE_RESOLUTION];
- } waveDef;
- waveDef wf[] = { // from [0..129]
- {(char*)"Square", {0,0,0,0,0,0,0,0,129,129,129,129,129,129,129,129}},
- {(char*)"Saw", {0,9,17,26,34,43,52,60,69,77,86,95,103,112,120,129}},
- {(char*)"3iangle", {0,16,32,48,65,81,97,113,129,113,97,81,65,48,32,16}},
- {(char*)"Sine", {0,5,19,40,65,89,110,124,129,124,110,89,65,40,19,5}}
- };
- byte currWave = 0;
- byte wfTick = 0;
- byte wfLvl = 0;
- uint32_t microSecondsPerCycle = 1000000;
- uint32_t microSecondsPerTick = microSecondsPerCycle / WAVE_RESOLUTION;
-
- byte playbackMode = 2; // buzzer
- 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; //
-
-// ====== initialize tuning (microtonal) presets
-
- typedef struct {
- char* name;
- byte cycleLength; // steps before repeat
- float stepSize; // in cents, 100 = "normal" semitone.
- } tuningDef;
- enum {
- Twelve, Seventeen, Nineteen, TwentyTwo,
- TwentyFour, ThirtyOne, FortyOne, FiftyThree,
- SeventyTwo, BohlenPierce,
- CarlosA, CarlosB, CarlosG
- };
- tuningDef tuningOptions[] = {
- // replaces the idea of const byte EDO[] = { 12, 17, 19, 22, 24, 31, 41, 53, 72 };
- { (char*)"12 EDO", 12, 100.0 },
- { (char*)"17 EDO", 17, 1200.0 / 17 },
- { (char*)"19 EDO", 19, 1200.0 / 19 },
- { (char*)"22 EDO", 22, 1200.0 / 22 },
- { (char*)"24 EDO", 24, 50.0 },
- { (char*)"31 EDO", 31, 1200.0 / 31 },
- { (char*)"41 EDO", 41, 1200.0 / 41 },
- { (char*)"53 EDO", 53, 1200.0 / 53 },
- { (char*)"72 EDO", 72, 100.0 / 6 },
- { (char*)"Bohlen-Pierce", 13, 1901.955 / 13 }, //
- { (char*)"Carlos Alpha", 9, 77.965 }, //
- { (char*)"Carlos Beta", 11, 63.833 }, //
- { (char*)"Carlos Gamma", 20, 35.099 }
- };
- const byte tuningCount = sizeof(tuningOptions) / sizeof(tuningDef);
-
-// ====== initialize layout patterns
-
- typedef struct {
- char* name;
- bool isPortrait;
- byte rootHex;
- int8_t acrossSteps;
- int8_t dnLeftSteps;
- byte tuning;
- } layoutDef;
- layoutDef layoutOptions[] = {
- { (char*)"Wicki-Hayden", 1, 64, 2, -7, Twelve },
- { (char*)"Harmonic Table", 0, 75, -7, 3, Twelve },
- { (char*)"Janko", 0, 65, -1, -1, Twelve },
- { (char*)"Gerhard", 0, 65, -1, -3, Twelve },
- { (char*)"Accordion C-sys.", 1, 75, 2, -3, Twelve },
- { (char*)"Accordion B-sys.", 1, 64, 1, -3, Twelve },
- { (char*)"Full Layout", 1, 65, -1, -9, Twelve },
- { (char*)"Bosanquet, 17", 0, 65, -2, -1, Seventeen },
- { (char*)"Full Layout", 1, 65, -1, -9, Seventeen },
- { (char*)"Bosanquet, 19", 0, 65, -1, -2, Nineteen },
- { (char*)"Full Layout", 1, 65, -1, -9, Nineteen },
- { (char*)"Bosanquet, 22", 0, 65, -3, -1, TwentyTwo },
- { (char*)"Full Layout", 1, 65, -1, -9, TwentyTwo },
- { (char*)"Bosanquet, 24", 0, 65, -1, -3, TwentyFour },
- { (char*)"Full Layout", 1, 65, -1, -9, TwentyFour },
- { (char*)"Bosanquet, 31", 0, 65, -2, -3, ThirtyOne },
- { (char*)"Full Layout", 1, 65, -1, -9, ThirtyOne },
- { (char*)"Bosanquet, 41", 0, 65, -4, -3, FortyOne }, // forty-one #1
- { (char*)"Gerhard, 41", 0, 65, 3, -10, FortyOne }, // forty-one #2
- { (char*)"Full Layout, 41", 0, 65, -1, -8, FortyOne }, // forty-one #3
- { (char*)"Wicki-Hayden, 53", 1, 64, 9, -31, FiftyThree },
- { (char*)"Harmonic Tbl, 53", 0, 75, -31, 14, FiftyThree },
- { (char*)"Bosanquet, 53", 0, 65, -5, -4, FiftyThree },
- { (char*)"Full Layout, 53", 0, 65, -1, -9, FiftyThree },
- { (char*)"Full Layout, 72", 0, 65, -1, -9, SeventyTwo },
- { (char*)"Full Layout", 1, 65, -1, -9, BohlenPierce },
- { (char*)"Full Layout", 1, 65, -1, -9, CarlosA },
- { (char*)"Full Layout", 1, 65, -1, -9, CarlosB },
- { (char*)"Full Layout", 1, 65, -1, -9, CarlosG }
- };
- const byte layoutCount = sizeof(layoutOptions) / sizeof(layoutDef);
-
-// ====== initialize list of supported scales / modes / raga / maqam
-
- typedef struct {
- char* name;
- byte tuning;
- byte step[16]; // 16 bytes = 128 bits, 1 = in scale; 0 = not
- } scaleDef;
- scaleDef scaleOptions[] = {
- { (char*)"None", 255, { 255, 255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255} },
- { (char*)"Major", Twelve, { 0b10101101, 0b0101'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Minor, natural", Twelve, { 0b10110101, 0b1010'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Minor, melodic", Twelve, { 0b10110101, 0b0101'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Minor, harmonic", Twelve, { 0b10110101, 0b1001'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Pentatonic, major", Twelve, { 0b10101001, 0b0100'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Pentatonic, minor", Twelve, { 0b10010101, 0b0010'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Blues", Twelve, { 0b10010111, 0b0010'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Double Harmonic", Twelve, { 0b11001101, 0b1001'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Phrygian", Twelve, { 0b11010101, 0b1010'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Phrygian Dominant", Twelve, { 0b11001101, 0b1010'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Dorian", Twelve, { 0b10110101, 0b0110'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Lydian", Twelve, { 0b10101011, 0b0101'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Lydian Dominant", Twelve, { 0b10101011, 0b0110'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Mixolydian", Twelve, { 0b10101101, 0b0110'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Locrian", Twelve, { 0b11010110, 0b1010'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Whole tone", Twelve, { 0b10101010, 0b1010'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Octatonic", Twelve, { 0b10110110, 0b1101'0000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Rast maqam", TwentyFour, { 0b10001001, 0b00100010, 0b00101100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Rast makam", FiftyThree, { 0b10000000, 0b01000000, 0b01000010, 0b00000001,
- 0b00000000, 0b10001000, 0b10000'000, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- };
- const byte scaleCount = sizeof(scaleOptions) / sizeof(scaleDef);
- byte scaleLock = 0; // menu wants this to be an int, not a bool
-
-// ====== initialize key coloring routines
-
- enum colors { W, R, O, Y, L, G, C, B, I, P, M,
- r, o, y, l, g, c, b, i, p, m };
- enum { DARK = 0, VeryDIM = 1, DIM = 32, BRIGHT = 127, VeryBRIGHT = 255 };
- enum { GRAY = 0, DULL = 127, VIVID = 255 };
- float hueCode[] = { 0.0, 0.0, 36.0, 72.0, 108.0, 144.0, 180.0, 216.0, 252.0, 288.0, 324.0,
- 0.0, 36.0, 72.0, 108.0, 144.0, 180.0, 216.0, 252.0, 288.0, 324.0 };
- byte satCode[] = { GRAY, VIVID,VIVID,VIVID,VIVID,VIVID,VIVID,VIVID,VIVID,VIVID,VIVID,
- DULL, DULL, DULL, DULL, DULL, DULL, DULL, DULL, DULL, DULL };
- byte colorMode = 1;
- byte perceptual = 1;
-
-// ====== initialize note labels in each tuning, also used for key signature
-
- typedef struct {
- char* name;
- byte tuning;
- int8_t offset; // steps from constant A4 to that key class
- colors tierColor;
- } keyDef;
- keyDef keyOptions[] = {
- // 12 EDO, whole tone = 2, #/b = 1
- { (char*)" C (B#)", Twelve, -9, W },
- { (char*)" C# / Db", Twelve, -8, I },
- { (char*)" D", Twelve, -7, W },
- { (char*)" D# / Eb", Twelve, -6, I },
- { (char*)" (Fb) E", Twelve, -5, W },
- { (char*)" F (E#)", Twelve, -4, W },
- { (char*)" Gb / F#", Twelve, -3, I },
- { (char*)" G", Twelve, -2, W },
- { (char*)" G# / Ab", Twelve, -1, I },
- { (char*)" A", Twelve, 0, W },
- { (char*)" A# / Bb", Twelve, 1, I },
- { (char*)"(Cb) B", Twelve, 2, W },
- // 17 EDO, whole tone = 3, #/b = 2, +/d = 1
- { (char*)" C (B+)", Seventeen, -13, W },
- { (char*)" C+ / Db / B#", Seventeen, -12, R },
- { (char*)" C# / Dd", Seventeen, -11, I },
- { (char*)" D", Seventeen, -10, W },
- { (char*)" D+ / Eb", Seventeen, -9, R },
- { (char*)" Fb / D# / Ed", Seventeen, -8, I },
- { (char*)"(Fd) E", Seventeen, -7, W },
- { (char*)" F (E+)", Seventeen, -6, W },
- { (char*)" F+ / Gb / E#", Seventeen, -5, R },
- { (char*)" F# / Gd", Seventeen, -4, I },
- { (char*)" G", Seventeen, -3, W },
- { (char*)" G+ / Ab", Seventeen, -2, R },
- { (char*)" G# / Ad", Seventeen, -1, I },
- { (char*)" A", Seventeen, 0, W },
- { (char*)" Bb / A+", Seventeen, 1, R },
- { (char*)" Cb / Bd / A#", Seventeen, 2, I },
- { (char*)"(Cd) B" , Seventeen, 3, W },
- // 19 EDO, whole tone = 3, #/b = 1
- { (char*)" C", Nineteen, -14, W },
- { (char*)" C#", Nineteen, -13, R },
- { (char*)" Db", Nineteen, -12, I },
- { (char*)" D", Nineteen, -11, W },
- { (char*)" D#", Nineteen, -10, R },
- { (char*)" Eb", Nineteen, -9, I },
- { (char*)" E", Nineteen, -8, W },
- { (char*)" E# / Fb", Nineteen, -7, m },
- { (char*)" F", Nineteen, -6, W },
- { (char*)" F#", Nineteen, -5, R },
- { (char*)" Gb", Nineteen, -4, I },
- { (char*)" G", Nineteen, -3, W },
- { (char*)" G#", Nineteen, -2, R },
- { (char*)" Ab", Nineteen, -1, I },
- { (char*)" A", Nineteen, 0, W },
- { (char*)" A#", Nineteen, 1, R },
- { (char*)" Bb", Nineteen, 2, I },
- { (char*)" B", Nineteen, 3, W },
- { (char*)" Cb / B#", Nineteen, 4, m },
- // 22 EDO, whole tone = 4, #/b = 3, ^/v = 1
- { (char*)" C (^B)", TwentyTwo, -17, W },
- { (char*)" ^C / Db / vB#", TwentyTwo, -16, l },
- { (char*)" vC# / ^Db / B#", TwentyTwo, -15, C },
- { (char*)" C# / vD", TwentyTwo, -14, i },
- { (char*)" D", TwentyTwo, -13, W },
- { (char*)" ^D / Eb", TwentyTwo, -12, l },
- { (char*)" Fb / vD# / ^Eb", TwentyTwo, -11, C },
- { (char*)" ^Fb / D# / vE", TwentyTwo, -10, i },
- { (char*)"(vF) E", TwentyTwo, -9, W },
- { (char*)" F (^E)", TwentyTwo, -8, W },
- { (char*)" ^F / Gb / vE#", TwentyTwo, -7, l },
- { (char*)" vF# / ^Gb / E#", TwentyTwo, -6, C },
- { (char*)" F# / vG", TwentyTwo, -5, i },
- { (char*)" G", TwentyTwo, -4, W },
- { (char*)" ^G / Ab", TwentyTwo, -3, l },
- { (char*)" vG# / ^Ab", TwentyTwo, -2, C },
- { (char*)" G# / vA", TwentyTwo, -1, i },
- { (char*)" A", TwentyTwo, 0, W },
- { (char*)" Bb / ^A", TwentyTwo, 1, l },
- { (char*)" Cb / ^Bb / vA#", TwentyTwo, 2, C },
- { (char*)" ^Cb / vB / A#", TwentyTwo, 3, i },
- { (char*)"(vC) B", TwentyTwo, 4, W },
- // 24 EDO, whole tone = 4, #/b = 2, +/d = 1
- { (char*)" C / B#", TwentyFour, -18, W },
- { (char*)" C+", TwentyFour, -17, r },
- { (char*)" C# / Db", TwentyFour, -16, I },
- { (char*)" Dd", TwentyFour, -15, g },
- { (char*)" D", TwentyFour, -14, W },
- { (char*)" D+", TwentyFour, -13, r },
- { (char*)" Eb / D#", TwentyFour, -12, I },
- { (char*)" Ed", TwentyFour, -11, g },
- { (char*)" E / Fb", TwentyFour, -10, W },
- { (char*)" E+ / Fd", TwentyFour, -9, y },
- { (char*)" E# / F", TwentyFour, -8, W },
- { (char*)" F+", TwentyFour, -7, r },
- { (char*)" Gb / F#", TwentyFour, -6, I },
- { (char*)" Gd", TwentyFour, -5, g },
- { (char*)" G", TwentyFour, -4, W },
- { (char*)" G+", TwentyFour, -3, r },
- { (char*)" G# / Ab", TwentyFour, -2, I },
- { (char*)" Ad", TwentyFour, -1, g },
- { (char*)" A", TwentyFour, 0, W },
- { (char*)" A+", TwentyFour, 1, r },
- { (char*)" Bb / A#", TwentyFour, 2, I },
- { (char*)" Bd", TwentyFour, 3, g },
- { (char*)" B / Cb", TwentyFour, 4, W },
- { (char*)" B+ / Cd", TwentyFour, 5, y },
- // 31 EDO, whole tone = 5, #/b = 2, +/d = 1
- { (char*)" C", ThirtyOne, -23, W },
- { (char*)" C+", ThirtyOne, -22, R },
- { (char*)" C#", ThirtyOne, -21, Y },
- { (char*)" Db", ThirtyOne, -20, C },
- { (char*)" Dd", ThirtyOne, -19, I },
- { (char*)" D", ThirtyOne, -18, W },
- { (char*)" D+", ThirtyOne, -17, R },
- { (char*)" D#", ThirtyOne, -16, Y },
- { (char*)" Eb", ThirtyOne, -15, C },
- { (char*)" Ed", ThirtyOne, -14, I },
- { (char*)" E", ThirtyOne, -13, W },
- { (char*)" E+ / Fb", ThirtyOne, -12, L },
- { (char*)" E# / Fd", ThirtyOne, -11, M },
- { (char*)" F", ThirtyOne, -10, W },
- { (char*)" F+", ThirtyOne, -9, R },
- { (char*)" F#", ThirtyOne, -8, Y },
- { (char*)" Gb", ThirtyOne, -7, C },
- { (char*)" Gd", ThirtyOne, -6, I },
- { (char*)" G", ThirtyOne, -5, W },
- { (char*)" G+", ThirtyOne, -4, R },
- { (char*)" G#", ThirtyOne, -3, Y },
- { (char*)" Ab", ThirtyOne, -2, C },
- { (char*)" Ad", ThirtyOne, -1, I },
- { (char*)" A", ThirtyOne, 0, W },
- { (char*)" A+", ThirtyOne, 1, R },
- { (char*)" A#", ThirtyOne, 2, Y },
- { (char*)" Bb", ThirtyOne, 3, C },
- { (char*)" Bd", ThirtyOne, 4, I },
- { (char*)" B", ThirtyOne, 5, W },
- { (char*)" Cb / B+", ThirtyOne, 6, L },
- { (char*)" Cd / B#", ThirtyOne, 7, M },
- // 41 EDO, whole tone = 7, #/b = 4, +/d = 2, ^/v = 1
- { (char*)" C (vB#)", FortyOne, -31, W },
- { (char*)" ^C / B#", FortyOne, -30, c },
- { (char*)" C+ ", FortyOne, -29, O },
- { (char*)" vC# / Db", FortyOne, -28, I },
- { (char*)" C# / ^Db", FortyOne, -27, R },
- { (char*)" Dd", FortyOne, -26, B },
- { (char*)" vD", FortyOne, -25, y },
- { (char*)" D", FortyOne, -24, W },
- { (char*)" ^D", FortyOne, -23, c },
- { (char*)" D+", FortyOne, -22, O },
- { (char*)" vD# / Eb", FortyOne, -21, I },
- { (char*)" D# / ^Eb", FortyOne, -20, R },
- { (char*)" Ed", FortyOne, -19, B },
- { (char*)" vE", FortyOne, -18, y },
- { (char*)" (^Fb) E", FortyOne, -17, W },
- { (char*)" Fd / ^E", FortyOne, -16, c },
- { (char*)" vF / E+", FortyOne, -15, y },
- { (char*)" F (vE#)", FortyOne, -14, W },
- { (char*)" ^F / E#", FortyOne, -13, c },
- { (char*)" F+", FortyOne, -12, O },
- { (char*)" Gb / vF#", FortyOne, -11, I },
- { (char*)" ^Gb / F#", FortyOne, -10, R },
- { (char*)" Gd", FortyOne, -9, B },
- { (char*)" vG", FortyOne, -8, y },
- { (char*)" G", FortyOne, -7, W },
- { (char*)" ^G", FortyOne, -6, c },
- { (char*)" G+", FortyOne, -5, O },
- { (char*)" vG# / Ab", FortyOne, -4, I },
- { (char*)" G# / ^Ab", FortyOne, -3, R },
- { (char*)" Ad", FortyOne, -2, B },
- { (char*)" vA", FortyOne, -1, y },
- { (char*)" A", FortyOne, 0, W },
- { (char*)" ^A", FortyOne, 1, c },
- { (char*)" A+", FortyOne, 2, O },
- { (char*)" vA# / Bb", FortyOne, 3, I },
- { (char*)" A# / ^Bb", FortyOne, 4, R },
- { (char*)" Bd", FortyOne, 5, B },
- { (char*)" vB", FortyOne, 6, y },
- { (char*)" (^Cb) B", FortyOne, 7, W },
- { (char*)" Cd / ^B", FortyOne, 8, c },
- { (char*)" vC / B+", FortyOne, 9, y },
- // 53 EDO, whole tone = 9, #/b = 5, >/< = 2, ^/v = 1
- { (char*)" C (vB#)", FiftyThree, -40, W },
- { (char*)" ^C / B#", FiftyThree, -39, c },
- { (char*)" >C / <Db", FiftyThree, -38, l },
- { (char*)" <C# / vDb", FiftyThree, -37, O },
- { (char*)" vC# / Db", FiftyThree, -36, I },
- { (char*)" C# / ^Db", FiftyThree, -35, R },
- { (char*)" ^C# / >Db", FiftyThree, -34, B },
- { (char*)" >C# / <D", FiftyThree, -33, g },
- { (char*)" vD", FiftyThree, -32, y },
- { (char*)" D", FiftyThree, -31, W },
- { (char*)" ^D", FiftyThree, -30, c },
- { (char*)" >D / <Eb", FiftyThree, -29, l },
- { (char*)" <D# / vEb", FiftyThree, -28, O },
- { (char*)" vD# / Eb", FiftyThree, -27, I },
- { (char*)" D# / ^Eb", FiftyThree, -26, R },
- { (char*)" ^D# / >Eb", FiftyThree, -25, B },
- { (char*)" >D# / <E", FiftyThree, -24, g },
- { (char*)" Fb / vE", FiftyThree, -23, y },
- { (char*)"(^Fb) E", FiftyThree, -22, W },
- { (char*)"(>Fb) ^E", FiftyThree, -21, c },
- { (char*)" <F / >E", FiftyThree, -20, G },
- { (char*)" vF (<E#)", FiftyThree, -19, y },
- { (char*)" F (vE#)", FiftyThree, -18, W },
- { (char*)" ^F / E#", FiftyThree, -17, c },
- { (char*)" >F / <Gb", FiftyThree, -16, l },
- { (char*)" <F# / vGb", FiftyThree, -15, O },
- { (char*)" vF# / Gb", FiftyThree, -14, I },
- { (char*)" F# / ^Gb", FiftyThree, -13, R },
- { (char*)" ^F# / >Gb", FiftyThree, -12, B },
- { (char*)" >F# / <G", FiftyThree, -11, g },
- { (char*)" vG", FiftyThree, -10, y },
- { (char*)" G", FiftyThree, -9, W },
- { (char*)" ^G", FiftyThree, -8, c },
- { (char*)" >G / <Ab", FiftyThree, -7, l },
- { (char*)" <G# / vAb", FiftyThree, -6, O },
- { (char*)" vG# / Ab", FiftyThree, -5, I },
- { (char*)" G# / ^Ab", FiftyThree, -4, R },
- { (char*)" ^G# / >Ab", FiftyThree, -3, B },
- { (char*)" >G# / <A", FiftyThree, -2, g },
- { (char*)" vA", FiftyThree, -1, y },
- { (char*)" A", FiftyThree, 0, W },
- { (char*)" ^A", FiftyThree, 1, c },
- { (char*)" <Bb / >A", FiftyThree, 2, l },
- { (char*)" vBb / <A#", FiftyThree, 3, O },
- { (char*)" Bb / vA#", FiftyThree, 4, I },
- { (char*)" ^Bb / A#", FiftyThree, 5, R },
- { (char*)" >Bb / ^A#", FiftyThree, 6, B },
- { (char*)" <B / >A#", FiftyThree, 7, g },
- { (char*)" Cb / vB", FiftyThree, 8, y },
- { (char*)"(^Cb) B", FiftyThree, 9, W },
- { (char*)"(>Cb) ^B", FiftyThree, 10, c },
- { (char*)" <C / >B", FiftyThree, 11, G },
- { (char*)" vC (<B#)", FiftyThree, 12, y },
- // 72 EDO, whole tone = 12, #/b = 6, +/d = 3, ^/v = 1
- { (char*)" C (B#)", SeventyTwo, -54, W },
- { (char*)" ^C", SeventyTwo, -53, g },
- { (char*)" vC+", SeventyTwo, -52, r },
- { (char*)" C+", SeventyTwo, -51, p },
- { (char*)" ^C+", SeventyTwo, -50, b },
- { (char*)" vC#", SeventyTwo, -49, y },
- { (char*)" C# / Db", SeventyTwo, -48, I },
- { (char*)" ^C# / ^Db", SeventyTwo, -47, g },
- { (char*)" vDd", SeventyTwo, -46, r },
- { (char*)" Dd", SeventyTwo, -45, p },
- { (char*)" ^Dd", SeventyTwo, -44, b },
- { (char*)" vD", SeventyTwo, -43, y },
- { (char*)" D", SeventyTwo, -42, W },
- { (char*)" ^D", SeventyTwo, -41, g },
- { (char*)" vD+", SeventyTwo, -40, r },
- { (char*)" D+", SeventyTwo, -39, p },
- { (char*)" ^D+", SeventyTwo, -38, b },
- { (char*)" vEb / vD#", SeventyTwo, -37, y },
- { (char*)" Eb / D#", SeventyTwo, -36, I },
- { (char*)" ^Eb / ^D#", SeventyTwo, -35, g },
- { (char*)" vEd", SeventyTwo, -34, r },
- { (char*)" Ed", SeventyTwo, -33, p },
- { (char*)" ^Ed", SeventyTwo, -32, b },
- { (char*)" vE (vFb)", SeventyTwo, -31, y },
- { (char*)" E (Fb)", SeventyTwo, -30, W },
- { (char*)" ^E (^Fb)", SeventyTwo, -29, g },
- { (char*)" vE+ / vFd", SeventyTwo, -28, r },
- { (char*)" E+ / Fd", SeventyTwo, -27, p },
- { (char*)" ^E+ / ^Fd", SeventyTwo, -26, b },
- { (char*)"(vE#) vF", SeventyTwo, -25, y },
- { (char*)" (E#) F", SeventyTwo, -24, W },
- { (char*)"(^E#) ^F", SeventyTwo, -23, g },
- { (char*)" vF+", SeventyTwo, -22, r },
- { (char*)" F+", SeventyTwo, -21, p },
- { (char*)" ^F+", SeventyTwo, -20, b },
- { (char*)" vGb / vF#", SeventyTwo, -19, y },
- { (char*)" Gb / F#", SeventyTwo, -18, I },
- { (char*)" ^Gb / ^F#", SeventyTwo, -17, g },
- { (char*)" vGd", SeventyTwo, -16, r },
- { (char*)" Gd", SeventyTwo, -15, p },
- { (char*)" ^Gd", SeventyTwo, -14, b },
- { (char*)" vG", SeventyTwo, -13, y },
- { (char*)" G", SeventyTwo, -12, W },
- { (char*)" ^G", SeventyTwo, -11, g },
- { (char*)" vG+", SeventyTwo, -10, r },
- { (char*)" G+", SeventyTwo, -9, p },
- { (char*)" ^G+", SeventyTwo, -8, b },
- { (char*)" vG# / vAb", SeventyTwo, -7, y },
- { (char*)" G# / Ab", SeventyTwo, -6, I },
- { (char*)" ^G# / ^Ab", SeventyTwo, -5, g },
- { (char*)" vAd", SeventyTwo, -4, r },
- { (char*)" Ad", SeventyTwo, -3, p },
- { (char*)" ^Ad", SeventyTwo, -2, b },
- { (char*)" vA", SeventyTwo, -1, y },
- { (char*)" A", SeventyTwo, 0, W },
- { (char*)" ^A", SeventyTwo, 1, g },
- { (char*)" vA+", SeventyTwo, 2, r },
- { (char*)" A+", SeventyTwo, 3, p },
- { (char*)" ^A+", SeventyTwo, 4, b },
- { (char*)" vBb / vA#", SeventyTwo, 5, y },
- { (char*)" Bb / A#", SeventyTwo, 6, I },
- { (char*)" ^Bb / ^A#", SeventyTwo, 7, g },
- { (char*)" vBd", SeventyTwo, 8, r },
- { (char*)" Bd", SeventyTwo, 9, p },
- { (char*)" ^Bd", SeventyTwo, 10, b },
- { (char*)" vB (vCb)", SeventyTwo, 11, y },
- { (char*)" B (Cb)", SeventyTwo, 12, W },
- { (char*)" ^B (^Cb)", SeventyTwo, 13, g },
- { (char*)" vB+ / vCd", SeventyTwo, 14, r },
- { (char*)" B+ / Cd", SeventyTwo, 15, p },
- { (char*)" ^B+ / ^Cd", SeventyTwo, 16, b },
- { (char*)"(vB#) vC", SeventyTwo, 17, y },
- //
- { (char*)"n/a",BohlenPierce,0,W},
- { (char*)"n/a",BohlenPierce,0,W},
- { (char*)"n/a",BohlenPierce,0,W},
- { (char*)"n/a",BohlenPierce,0,W},
- { (char*)"n/a",BohlenPierce,0,W},
- { (char*)"n/a",BohlenPierce,0,W},
- { (char*)"n/a",BohlenPierce,0,W},
- { (char*)"n/a",BohlenPierce,0,W},
- { (char*)"n/a",BohlenPierce,0,W},
- { (char*)"n/a",BohlenPierce,0,W},
- { (char*)"n/a",BohlenPierce,0,W},
- { (char*)"n/a",BohlenPierce,0,W},
- { (char*)"n/a",BohlenPierce,0,W},
- //
- { (char*)"n/a",CarlosA,0,W},
- { (char*)"n/a",CarlosA,0,W},
- { (char*)"n/a",CarlosA,0,W},
- { (char*)"n/a",CarlosA,0,W},
- { (char*)"n/a",CarlosA,0,W},
- { (char*)"n/a",CarlosA,0,W},
- { (char*)"n/a",CarlosA,0,W},
- { (char*)"n/a",CarlosA,0,W},
- { (char*)"n/a",CarlosA,0,W},
- //
- { (char*)"n/a",CarlosB,0,W},
- { (char*)"n/a",CarlosB,0,W},
- { (char*)"n/a",CarlosB,0,W},
- { (char*)"n/a",CarlosB,0,W},
- { (char*)"n/a",CarlosB,0,W},
- { (char*)"n/a",CarlosB,0,W},
- { (char*)"n/a",CarlosB,0,W},
- { (char*)"n/a",CarlosB,0,W},
- { (char*)"n/a",CarlosB,0,W},
- { (char*)"n/a",CarlosB,0,W},
- { (char*)"n/a",CarlosB,0,W},
- //
- { (char*)"n/a",CarlosG,0,W},
- { (char*)"n/a",CarlosG,0,W},
- { (char*)"n/a",CarlosG,0,W},
- { (char*)"n/a",CarlosG,0,W},
- { (char*)"n/a",CarlosG,0,W},
- { (char*)"n/a",CarlosG,0,W},
- { (char*)"n/a",CarlosG,0,W},
- { (char*)"n/a",CarlosG,0,W},
- { (char*)"n/a",CarlosG,0,W},
- { (char*)"n/a",CarlosG,0,W},
- { (char*)"n/a",CarlosG,0,W},
- { (char*)"n/a",CarlosG,0,W},
- { (char*)"n/a",CarlosG,0,W},
- { (char*)"n/a",CarlosG,0,W},
- { (char*)"n/a",CarlosG,0,W},
- { (char*)"n/a",CarlosG,0,W},
- { (char*)"n/a",CarlosG,0,W},
- { (char*)"n/a",CarlosG,0,W},
- { (char*)"n/a",CarlosG,0,W},
- { (char*)"n/a",CarlosG,0,W},
-
- };
- const int keyCount = sizeof(keyOptions) / sizeof(keyDef);
-// ====== initialize structure to store and recall user preferences
-
- typedef struct { // put all user-selectable options into a class so that down the line these can be saved and loaded.
- char* presetName;
- int tuningIndex; // instead of using pointers, i chose to store index value of each option, to be saved to a .pref or .ini or something
- int layoutIndex;
- int scaleIndex;
- int keyIndex;
- int transpose;
- // define simple recall functions
- tuningDef tuning() {
- return tuningOptions[tuningIndex];
+ };
+ } wheelDef;
+ wheelDef modWheel = {
+ &h[assignCmd[4]].keyState,
+ &h[assignCmd[5]].keyState,
+ &h[assignCmd[6]].keyState,
+ 0, 127, 8,
+ 0, 0, 0
+ };
+ wheelDef pbWheel = {
+ &h[assignCmd[4]].keyState,
+ &h[assignCmd[5]].keyState,
+ &h[assignCmd[6]].keyState,
+ -8192, 8192, 1024,
+ 0, 0, 0
};
- layoutDef layout() {
- return layoutOptions[layoutIndex];
+ wheelDef velWheel = {
+ &h[assignCmd[0]].keyState,
+ &h[assignCmd[1]].keyState,
+ &h[assignCmd[2]].keyState,
+ 0, 127, 8,
+ 96, 96, 96
};
- scaleDef scale() {
- return scaleOptions[scaleIndex];
+ 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 } },
};
- keyDef key() {
- return keyOptions[keyIndex];
+ 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},
+
};
- int layoutsBegin() {
- if (tuningIndex == Twelve) {
- return 0;
- } else {
- int temp = 0;
- while (layoutOptions[temp].tuning < tuningIndex) {
- temp++;
+ 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 menuGotoTuning("Tuning", menuPageTuning);
+ GEMItem* menuItemTuning[tuningCount]; // dynamically generate item based on tunings
+
+ GEMItem menuGotoLayout("Layout", menuPageLayout);
+ GEMItem* menuItemLayout[layoutCount]; // dynamically generate item based on presets
+
+ 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 } };
+ 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 } };
+ 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} };
+ GEMSelect selectAnimate( sizeof(optionByteAnimate) / sizeof(SelectOptionByte), optionByteAnimate);
+ 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 } };
+ GEMSelect selectWaveform(sizeof(optionByteWaveform) / sizeof(SelectOptionByte), optionByteWaveform);
+ GEMItem menuItemWaveform( "Waveform:", currWave, selectWaveform);
+
+ // put all user-selectable options into a class so that down the line these can be saved and loaded.
+ typedef struct {
+ 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 transpose;
+ // define simple recall functions
+ tuningDef tuning() {
+ return tuningOptions[tuningIndex];
+ };
+ layoutDef layout() {
+ return layoutOptions[layoutIndex];
+ };
+ scaleDef scale() {
+ return scaleOptions[scaleIndex];
+ };
+ keyDef key() {
+ return keyOptions[keyIndex];
+ };
+ int layoutsBegin() {
+ if (tuningIndex == Twelve) {
+ return 0;
+ } else {
+ int temp = 0;
+ while (layoutOptions[temp].tuning < tuningIndex) {
+ temp++;
+ };
+ return temp;
};
- return temp;
};
- };
- int keysBegin() {
- if (tuningIndex == Twelve) {
- return 0;
- } else {
- int temp = 0;
- while (keyOptions[temp].tuning < tuningIndex) {
- temp++;
+ int keysBegin() {
+ if (tuningIndex == Twelve) {
+ return 0;
+ } else {
+ int temp = 0;
+ while (keyOptions[temp].tuning < tuningIndex) {
+ temp++;
+ };
+ return temp;
};
- return temp;
};
+ int findC() {
+ return keyOptions[keysBegin()].offset;
+ };
+ } 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
};
- int findC() {
- return keyOptions[keysBegin()].offset;
- };
- } presetDef;
- presetDef current = {
- (char*)"Default",
- Twelve, // see the relevant enum{} statement
- 0, // default to the first layout, wicki hayden
- 0, // default to using no scale (chromatic)
- 0, // default to the key of C
- 0 // default to no transposition
- };
+
// ====== functions
byte byteLerp(byte xOne, byte xTwo, float yOne, float yTwo, float y) {
float weight = (y - yOne) / (yTwo - yOne);
@@ -839,14 +1011,14 @@
return 440.0 * exp2((MIDI - 69.0) / 12.0);
}
float stepsToMIDI(int16_t stepsFromA) { // return the MIDI pitch associated
- return freqToMIDI(concertA) + ((float)stepsFromA * (float)current.tuning().stepSize / 100.0);
+ return freqToMIDI(CONCERT_A_HZ) + ((float)stepsFromA * (float)current.tuning().stepSize / 100.0);
}
// ====== diagnostic wrapper
- void sendToLog(String msg) {
+ void sendToLog(std::string msg) {
if (diagnostics) {
- Serial.println(msg);
+ Serial.println(msg.c_str());
};
}
@@ -872,7 +1044,7 @@
float hueDegrees;
byte sat;
colors c;
- for (byte i = 0; i < HEXCOUNT; i++) {
+ 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);
switch (colorMode) {
@@ -901,7 +1073,7 @@
void assignPitches() { // run this if the layout, key, or transposition changes, but not if color or scale changes
sendToLog("assignPitch was called:");
- for (byte i = 0; i < HEXCOUNT; i++) {
+ for (byte i = 0; i < LED_COUNT; i++) {
if (!(h[i].isCmd)) {
float N = stepsToMIDI(h[i].steps + current.key().offset + current.transpose);
if (N < 0 || N >= 128) {
@@ -910,38 +1082,38 @@
h[i].frequency = 0.0;
} else {
h[i].note = ((N >= 127) ? 127 : round(N));
- h[i].bend = (ldexp(N - h[i].note, 13) / defaultPBRange);
+ h[i].bend = (ldexp(N - h[i].note, 13) / PITCH_BEND_SEMIS);
h[i].frequency = MIDItoFreq(N);
};
- sendToLog(String(
- "hex #" + String(i) + ", " +
- "steps=" + String(h[i].steps) + ", " +
- "isCmd? " + String(h[i].isCmd) + ", " +
- "note=" + String(h[i].note) + ", " +
- "bend=" + String(h[i].bend) + ", " +
- "freq=" + String(h[i].frequency) + ", " +
- "inScale? " + String(h[i].inScale) + "."
- ));
+ sendToLog(
+ "hex #" + std::to_string(i) + ", " +
+ "steps=" + std::to_string(h[i].steps) + ", " +
+ "isCmd? " + std::to_string(h[i].isCmd) + ", " +
+ "note=" + std::to_string(h[i].note) + ", " +
+ "bend=" + std::to_string(h[i].bend) + ", " +
+ "freq=" + std::to_string(h[i].frequency) + ", " +
+ "inScale? " + std::to_string(h[i].inScale) + "."
+ );
};
};
sendToLog("assignPitches complete.");
}
void applyScale() {
sendToLog("applyScale was called:");
- for (byte i = 0; i < HEXCOUNT; i++) {
+ 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;
- sendToLog(String(
- "hex #" + String(i) + ", " +
- "steps=" + String(h[i].steps) + ", " +
- "isCmd? " + String(h[i].isCmd) + ", " +
- "note=" + String(h[i].note) + ", " +
- "inScale? " + String(h[i].inScale) + "."
- ));
+ sendToLog(
+ "hex #" + std::to_string(i) + ", " +
+ "steps=" + std::to_string(h[i].steps) + ", " +
+ "isCmd? " + std::to_string(h[i].isCmd) + ", " +
+ "note=" + std::to_string(h[i].note) + ", " +
+ "inScale? " + std::to_string(h[i].inScale) + "."
+ );
};
};
resetHexLEDs();
@@ -949,7 +1121,7 @@
}
void applyLayout() { // call this function when the layout changes
sendToLog("buildLayout was called:");
- for (byte i = 0; i < HEXCOUNT; i++) {
+ 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 = (
@@ -959,10 +1131,10 @@
(2 * current.layout().dnLeftSteps)
))
) / 2;
- sendToLog(String(
- "hex #" + String(i) + ", " +
- "steps=" + String(h[i].steps) + "."
- ));
+ sendToLog(
+ "hex #" + std::to_string(i) + ", " +
+ "steps=" + std::to_string(h[i].steps) + "."
+ );
};
};
applyScale(); // when layout changes, have to re-apply scale and re-apply LEDs
@@ -1010,18 +1182,18 @@
+ ((microSecondsPerCycle % WAVE_RESOLUTION) < wfTick)
;
timer_hw->alarm[ALARM_NUM] = timer_hw->timerawl + microSecondsPerTick;
- wfLvl = ((playbackMode && (currentHexBuzzing <= HEXCOUNT)) ? (wf[currWave].lvl[wfTick] * velWheel.curValue) >> 6 : 0);
+ 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 <= HEXCOUNT)) {
- microSecondsPerCycle = round(1000000 / (exp2(pbWheel.curValue * defaultPBRange / 98304.0) * h[currentHexBuzzing].frequency));
+ 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 < HEXCOUNT; i++) {
- byte j = positiveMod(currentHexBuzzing + i, HEXCOUNT);
+ 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;
@@ -1043,10 +1215,10 @@
MIDI.beginRpn(0, Ch);
MIDI.sendRpnValue(semitones << 7, Ch);
MIDI.endRpn(Ch);
- sendToLog(String(
+ sendToLog(
"set pitch bend range on ch " +
- String(Ch) + " to be " + String(semitones) + " semitones"
- ));
+ std::to_string(Ch) + " to be " + std::to_string(semitones) + " semitones"
+ );
}
}
void setMPEzone(byte masterCh, byte sizeOfZone) {
@@ -1054,10 +1226,10 @@
MIDI.beginRpn(6, masterCh);
MIDI.sendRpnValue(sizeOfZone << 7, masterCh);
MIDI.endRpn(masterCh);
- sendToLog(String(
+ sendToLog(
"tried sending MIDI msg to set MPE zone, master ch " +
- String(masterCh) + ", zone of this size: " + String(sizeOfZone)
- ));
+ std::to_string(masterCh) + ", zone of this size: " + std::to_string(sizeOfZone)
+ );
}
}
void prepMIDIforMicrotones() {
@@ -1067,48 +1239,48 @@
setMPEzone(16, (5 * makeZone)); // MPE zone 16 = ch 11 thru 15 (or reset if not using MPE)
delay(ccMsgCoolDown);
for (byte i = 1; i <= 16; i++) {
- setPitchBendRange(i, defaultPBRange); // some synths try to set PB range to 48 semitones.
+ 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(String("pushed ch " + String(i) + " to the open channel queue"));
+ sendToLog("pushed ch " + std::to_string(i) + " to the open channel queue");
};
channelBend[i - 1] = 0;
channelPoly[i - 1] = 0;
};
}
void chgModulation() {
- if (enableMIDI) { // MIDI mode only
- if (current.tuningIndex == Twelve) {
+ if (enableMIDI) { // MIDI mode only
+ if (current.tuningIndex == Twelve) {
MIDI.sendControlChange(1, modWheel.curValue, 1);
- sendToLog(String("sent mod value " + String(modWheel.curValue) + " to ch 1"));
+ sendToLog("sent mod value " + std::to_string(modWheel.curValue) + " to ch 1");
} else if (MPE) {
MIDI.sendControlChange(1, modWheel.curValue, 1);
- sendToLog(String("sent mod value " + String(modWheel.curValue) + " to ch 1"));
+ sendToLog("sent mod value " + std::to_string(modWheel.curValue) + " to ch 1");
MIDI.sendControlChange(1, modWheel.curValue, 16);
- sendToLog(String("sent mod value " + String(modWheel.curValue) + " to ch 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(String("sent mod value " + String(modWheel.curValue) + " to ch " + String(i+1)));
+ sendToLog("sent mod value " + std::to_string(modWheel.curValue) + " to ch " + std::to_string(i+1));
};
};
- };
+ };
};
void chgUniversalPB() {
if (enableMIDI) { // MIDI mode only
if (current.tuningIndex == Twelve) {
MIDI.sendPitchBend(pbWheel.curValue, 1);
- sendToLog(String("sent pb value " + String(pbWheel.curValue) + " to ch 1"));
+ sendToLog("sent pb value " + std::to_string(pbWheel.curValue) + " to ch 1");
} else if (MPE) {
MIDI.sendPitchBend(pbWheel.curValue, 1);
- sendToLog(String("sent pb value " + String(pbWheel.curValue) + " to ch 1"));
+ sendToLog("sent pb value " + std::to_string(pbWheel.curValue) + " to ch 1");
MIDI.sendPitchBend(pbWheel.curValue, 16);
- sendToLog(String("sent pb value " + String(pbWheel.curValue) + " to ch 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(String("sent pb value " + String(channelBend[i] + pbWheel.curValue) + " to ch " + String(i+1)));
+ sendToLog("sent pb value " + std::to_string(channelBend[i] + pbWheel.curValue) + " to ch " + std::to_string(i+1));
};
};
};
@@ -1121,17 +1293,17 @@
for (byte c = MPE; c < (16 - MPE); c++) { // MPE - look at ch 2 thru 15 [c 1-14]; otherwise ch 1 thru 16 [c 0-15]
if ((c + 1 != 10) && (h[x].bend == channelBend[c])) { // not using drum channel ch 10 in either case
temp = c + 1;
- sendToLog(String("found a matching channel: ch " + String(temp) + " has pitch bend " + String(channelBend[c])));
+ 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(String("channel queue was empty so we didn't send a note on"));
+ sendToLog("channel queue was empty so we didn't send a note on");
} else {
temp = openChannelQueue.front();
openChannelQueue.pop();
- sendToLog(String("popped " + String(temp) + " off the queue"));
+ sendToLog("popped " + std::to_string(temp) + " off the queue");
};
};
return temp;
@@ -1154,12 +1326,12 @@
MIDI.sendPitchBend(h[x].bend, c); // ch 1-16
};
MIDI.sendNoteOn(h[x].note, velWheel.curValue, c); // ch 1-16
- sendToLog(String(
- "sent note on: " + String(h[x].note) +
- " pb " + String(h[x].bend) +
- " vel " + String(velWheel.curValue) +
- " ch " + String(c)
- ));
+ 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)
+ );
};
};
}
@@ -1184,18 +1356,18 @@
tryBuzzing(nextHeldNote());
} else {
MIDI.sendNoteOff(h[x].note, velWheel.curValue, c);
- sendToLog(String(
- "sent note off: " + String(h[x].note) +
- " pb " + String(h[x].bend) +
- " vel " + String(velWheel.curValue) +
- " ch " + String(c)
- ));
+ 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)
+ );
};
};
}
void cmdOn(byte x) { // volume and mod wheel read all current buttons
switch (h[x].note) {
- case cmdCode + 3:
+ case CMDB + 3:
toggleWheel = !toggleWheel;
// recolorHex(x);
break;
@@ -1216,9 +1388,9 @@
};
}
void animateMirror() {
- for (byte i = 0; i < HEXCOUNT; i++) { // check every hex
+ for (byte i = 0; i < LED_COUNT; i++) { // check every hex
if ((!(h[i].isCmd)) && (h[i].channel)) { // that is a held note
- for (byte j = 0; j < HEXCOUNT; j++) { // compare to every hex
+ 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
@@ -1233,14 +1405,14 @@
};
}
void animateOrbit() {
- for (byte i = 0; i < HEXCOUNT; i++) { // check every hex
+ 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
};
};
}
void animateRadial() {
- for (byte i = 0; i < HEXCOUNT; i++) { // check every hex
+ 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
@@ -1257,61 +1429,7 @@
};
}
-// ====== menu variables and routines
-
- // must declare these variables globally for some reason
- // doing so down here so we don't have to forward declare callback functions
- //
- SelectOptionByte optionByteYesOrNo[] = { { "No" , 0 },
- { "Yes" , 1 } };
- SelectOptionByte optionBytePlayback[] = { { "Off" , 0 },
- { "Mono" , 1 },
- { "Arp'gio", 2 } };
- SelectOptionByte optionByteColor[] = { { "Rainbow", 0 },
- { "Tiered" , 1 } };
- SelectOptionByte optionByteAnimate[] = { { "None" , NoAnim },
- { "Octave" , OctaveAnim},
- { "By Note", NoteAnim},
- { "Star" , StarAnim},
- { "Splash" , SplashAnim},
- { "Orbit" , OrbitAnim} };
- SelectOptionByte optionByteWaveform[] = { { wf[0].name, 0 },
- { wf[1].name, 1 },
- { wf[2].name, 2 },
- { wf[3].name, 3 } };
-
- GEMSelect selectYesOrNo( sizeof(optionByteYesOrNo) / sizeof(SelectOptionByte), optionByteYesOrNo);
- GEMSelect selectPlayback(sizeof(optionBytePlayback) / sizeof(SelectOptionByte), optionBytePlayback);
- GEMSelect selectColor( sizeof(optionByteColor) / sizeof(SelectOptionByte), optionByteColor);
- GEMSelect selectAnimate( sizeof(optionByteAnimate) / sizeof(SelectOptionByte), optionByteAnimate);
- GEMSelect selectWaveform(sizeof(optionByteWaveform) / sizeof(SelectOptionByte), optionByteWaveform);
-
- GEMPage menuPageMain("HexBoard MIDI Controller");
-
- GEMPage menuPageTuning("Tuning");
- GEMItem menuGotoTuning("Tuning", menuPageTuning);
- GEMItem* menuItemTuning[tuningCount]; // dynamically generate item based on tunings
-
- GEMPage menuPageLayout("Layout");
- GEMItem menuGotoLayout("Layout", menuPageLayout);
- GEMItem* menuItemLayout[layoutCount]; // dynamically generate item based on presets
-
- GEMPage menuPageScales("Scales");
- GEMItem menuGotoScales("Scales", menuPageScales);
- GEMItem* menuItemScales[scaleCount]; // dynamically generate item based on presets and if allowed in given EDO tuning
-
- GEMPage menuPageKeys("Keys");
- GEMItem menuGotoKeys("Keys", menuPageKeys);
- GEMItem* menuItemKeys[keyCount]; // dynamically generate item based on presets
-
- GEMItem menuItemScaleLock( "Scale lock?", scaleLock, selectYesOrNo);
- GEMItem menuItemPlayback( "Buzzer:", playbackMode, selectPlayback);
- GEMItem menuItemWaveform( "Waveform:", currWave, selectWaveform);
- GEMItem menuItemColor( "Color mode:", colorMode, selectColor, resetHexLEDs);
- GEMItem menuItemPercep( "Fix color:", perceptual, selectYesOrNo, resetHexLEDs);
- GEMItem menuItemAnimate( "Animation:", animationType, selectAnimate);
- GEMItem menuItemMIDI( "Enable MIDI:", enableMIDI, selectYesOrNo);
- GEMItem menuItemMPE( "MPE Mode:", MPE, selectYesOrNo, prepMIDIforMicrotones);
+// ====== menu routines
void menuHome() {
menu.setMenuPageCurrent(menuPageMain);
@@ -1321,19 +1439,19 @@
for (byte L = 0; L < layoutCount; L++) {
menuItemLayout[L]->hide((layoutOptions[L].tuning != current.tuningIndex));
};
- sendToLog(String("menu: Layout choices were updated."));
+ 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 != 255));
};
- sendToLog(String("menu: Scale choices were updated."));
+ 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));
};
- sendToLog(String("menu: Key choices were updated."));
+ sendToLog("menu: Key choices were updated.");
}
void changeLayout(GEMCallbackData callbackData) { // when you change the layout via the menu
byte selection = callbackData.valByte;
@@ -1377,30 +1495,29 @@
void buildMenu() {
menuPageMain.addMenuItem(menuGotoTuning);
for (byte T = 0; T < tuningCount; T++) { // create pointers to all tuning choices
- menuItemTuning[T] = new GEMItem(tuningOptions[T].name, changeTuning, T);
+ menuItemTuning[T] = new GEMItem(tuningOptions[T].name.c_str(), changeTuning, T);
menuPageTuning.addMenuItem(*menuItemTuning[T]);
};
menuPageMain.addMenuItem(menuGotoLayout);
for (byte L = 0; L < layoutCount; L++) { // create pointers to all layouts
- menuItemLayout[L] = new GEMItem(layoutOptions[L].name, changeLayout, L);
+ menuItemLayout[L] = new GEMItem(layoutOptions[L].name.c_str(), changeLayout, L);
menuPageLayout.addMenuItem(*menuItemLayout[L]);
};
showOnlyValidLayoutChoices();
menuPageMain.addMenuItem(menuGotoScales);
for (int S = 0; S < scaleCount; S++) { // create pointers to all scale items, filter them as you go
- menuItemScales[S] = new GEMItem(scaleOptions[S].name, changeScale, S);
+ 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, changeKey, K);
+ menuItemKeys[K] = new GEMItem(keyOptions[K].name.c_str(), changeKey, K);
menuPageKeys.addMenuItem(*menuItemKeys[K]);
};
-
showOnlyValidKeyChoices();
menuPageMain.addMenuItem(menuItemScaleLock);
@@ -1411,7 +1528,6 @@
menuPageMain.addMenuItem(menuItemAnimate);
menuPageMain.addMenuItem(menuItemPercep);
menuPageMain.addMenuItem(menuItemMPE);
-
}
// ====== setup routines
@@ -1427,33 +1543,35 @@
LittleFS.setConfig(cfg);
LittleFS.begin(); // Mounts file system.
if (!LittleFS.begin()) {
- Serial.println("An Error has occurred while mounting LittleFS");
+ sendToLog("An Error has occurred while mounting LittleFS");
}
}
void setupPins() {
- for (byte p = 0; p < sizeof(columnPins); p++) // For each column pin...
+ for (byte p = 0; p < sizeof(cPin); p++) // For each column pin...
{
- pinMode(columnPins[p], INPUT_PULLUP); // set the pinMode to INPUT_PULLUP (+3.3V / HIGH).
+ pinMode(cPin[p], INPUT_PULLUP); // set the pinMode to INPUT_PULLUP (+3.3V / HIGH).
}
- for (byte p = 0; p < sizeof(multiplexPins); p++) // For each column pin...
+ for (byte p = 0; p < sizeof(mPin); p++) // For each column pin...
{
- pinMode(multiplexPins[p], OUTPUT); // Setting the row multiplexer pins to output.
+ pinMode(mPin[p], OUTPUT); // Setting the row multiplexer pins to output.
}
Wire.setSDA(SDAPIN);
Wire.setSCL(SCLPIN);
- pinMode(rotaryPinC, INPUT_PULLUP);
+ pinMode(ROT_PIN_C, INPUT_PULLUP);
}
void setupGrid() {
- sendToLog(String("initializing hex grid..."));
- for (byte i = 0; i < HEXCOUNT; i++) {
+ /*
+ sendToLog("initializing hex grid..."));
+ */
+ for (byte i = 0; i < LED_COUNT; i++) {
h[i].coords = indexToCoord(i);
h[i].isCmd = 0;
h[i].note = 255;
h[i].keyState = 0;
};
- for (byte c = 0; c < cmdCount; c++) {
+ for (byte c = 0; c < CMDCOUNT; c++) {
h[assignCmd[c]].isCmd = 1;
- h[assignCmd[c]].note = cmdCode + c;
+ h[assignCmd[c]].note = CMDB + c;
};
applyLayout();
}
@@ -1474,7 +1592,9 @@
u8g2.setContrast(defaultContrast); // Set contrast
}
void testDiagnostics() {
- sendToLog(String("theHDM was here"));
+ /*
+ sendToLog("theHDM was here"));
+ */
}
void setupPiezo() {
gpio_set_function(TONEPIN, GPIO_FUNC_PWM);
@@ -1493,7 +1613,7 @@
void timeTracker() {
lapTime = runTime - loopTime;
- // sendToLog(String(lapTime)); // Print out the time it takes to run each loop
+ // 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
}
@@ -1514,10 +1634,10 @@
void readHexes() {
for (byte r = 0; r < ROWCOUNT; r++) { // Iterate through each of the row pins on the multiplexing chip.
for (byte d = 0; d < 4; d++) {
- digitalWrite(multiplexPins[d], (r >> d) & 1);
+ 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 = columnPins[c]; // Hold the currently selected column pin in a variable.
+ 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).
bool didYouPressHex = (digitalRead(p) == LOW); // hex is pressed if it returns LOW. else not pressed
@@ -1527,7 +1647,7 @@
}
}
void actionHexes() {
- for (byte i = 0; i < HEXCOUNT; i++) { // For all buttons in the deck
+ for (byte i = 0; i < LED_COUNT; i++) { // For all buttons in the deck
switch (h[i].keyState) {
case 1: // just pressed
if (h[i].isCmd) {
@@ -1566,7 +1686,7 @@
bool upd = velWheel.updateValue();
if (upd) {
buzz(); // update the volume live
- sendToLog(String("vel became " + String(velWheel.curValue)));
+ sendToLog("vel became " + std::to_string(velWheel.curValue));
}
if (toggleWheel) {
pbWheel.setTargetValue();
@@ -1584,7 +1704,7 @@
};
}
void animateLEDs() { // TBD
- for (byte i = 0; i < HEXCOUNT; i++) {
+ for (byte i = 0; i < LED_COUNT; i++) {
h[i].animate = 0;
};
if (animationType) {
@@ -1604,7 +1724,7 @@
};
}
void lightUpLEDs() {
- for (byte i = 0; i < HEXCOUNT; i++) {
+ for (byte i = 0; i < LED_COUNT; i++) {
if (!(h[i].isCmd)) {
if (h[i].animate) {
strip.setPixelColor(i,h[i].LEDcolorAnim);
@@ -1662,7 +1782,7 @@
}
void dealWithRotary() {
if (menu.readyForKey()) {
- rotaryIsClicked = digitalRead(rotaryPinC);
+ rotaryIsClicked = digitalRead(ROT_PIN_C);
if (rotaryIsClicked > rotaryWasClicked) {
menu.registerKeyPress(GEM_KEY_OK);
screenTime = 0;
@@ -1689,7 +1809,15 @@
rotaryKnobTurns--;
break;
}
- // rotaryKnobTurns = (rotaryKnobTurns > maxKnobTurns ? maxKnobTurns : (rotaryKnobTurns < -maxKnobTurns ? -maxKnobTurns : rotaryKnobTurns));
+ rotaryKnobTurns = (
+ rotaryKnobTurns > maxKnobTurns
+ ? maxKnobTurns
+ : (
+ rotaryKnobTurns < -maxKnobTurns
+ ? -maxKnobTurns
+ : rotaryKnobTurns
+ )
+ );
}
// ====== setup() and loop()