Firmware for HexBoard MIDI controller
Hexperiment -- volume and waveform on buzzer
Nicholas Fox 2024-04-05
parent 763712d · commit 55206c9
-rw-r--r--Hexperiment.ino614
1 files changed, 348 insertions, 266 deletions
diff --git a/Hexperiment.ino b/Hexperiment.ino
index d75c2f7..40f56b5 100644
--- a/Hexperiment.ino
+++ b/Hexperiment.ino
@@ -8,73 +8,77 @@
// Patches needed for U8G2, Rotary.h
// ==============================================================================
// list of things remaining to do:
- // -- program the wheel -- OK!
- // -- put back the animations -- OK!
- // -- test MPE working on iPad garageband -- works on pianoteq without MPE; need to get powered connection to iOS.
- // -- volume control test on buzzer
+ // -- 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 <Wire.h>
#include <LittleFS.h>
#include <queue> // std::queue construct to store open channels in microtonal mode
- const byte diagnostics = 1;
+ 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
-
- const byte lightPinSDA = 16;
- const byte lightPinSCL = 17;
+
+ #define SDAPIN 16
+ #define SCLPIN 17
// ====== initialize MIDI
-
+
#include <Adafruit_TinyUSB.h>
#include <MIDI.h>
Adafruit_USBD_MIDI usb_midi;
MIDI_CREATE_INSTANCE(Adafruit_USBD_MIDI, usb_midi, MIDI);
- float concertA = 440.0; // tuning of A4 in Hz
- byte MPE = 0; // microtonal mode. if zero then attempt to self-manage multiple channels.
+ #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;
- const byte defaultPBRange = 2;
+ #define defaultPBRange 2
// ====== initialize LEDs
#include <Adafruit_NeoPixel.h>
const byte multiplexPins[] = { 4, 5, 2, 3 }; // m1p, m2p, m4p, m8p
- const byte rowCount = 14; // The number of rows in the matrix
const byte columnPins[] = { 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
- const byte colCount = sizeof(columnPins); // The number of columns in the matrix
- const byte hexCount = colCount * rowCount; // The number of elements in the matrix
- const byte LEDPin = 22;
- Adafruit_NeoPixel strip(hexCount, LEDPin, NEO_GRB + NEO_KHZ800);
+ #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;
+ } 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; //
+ 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
@@ -89,7 +93,7 @@
timePressed = millis(); // log the time
};
};
- uint32_t animFrame() {
+ uint32_t animFrame() {
if (timePressed) { // 2^10 milliseconds is close enough to 1 second
return 1 + (((runTime - timePressed) * animationFPS) >> 10);
} else {
@@ -97,14 +101,14 @@
};
};
} buttonDef;
- buttonDef h[hexCount]; // array of hex objects from 0 to 139
+ buttonDef h[HEXCOUNT]; // array of hex objects from 0 to 139
const byte assignCmd[] = { 0, 20, 40, 60, 80, 100, 120 };
const byte cmdCount = 7;
const byte cmdCode = 192;
// ====== initialize wheel emulation
- const uint16_t ccMsgCoolDown = 20; // milliseconds between steps
+ const uint16_t ccMsgCoolDown = 32; // milliseconds between steps
typedef struct {
byte* topBtn;
byte* midBtn;
@@ -121,7 +125,7 @@
int16_t temp = curValue;
if (*topBtn == 1) {temp += stepValue;};
if (*botBtn == 1) {temp -= stepValue;};
- if (temp > maxValue) {temp = maxValue;}
+ if (temp > maxValue) {temp = maxValue;}
else if (temp <= minValue) {temp = minValue;};
targetValue = temp;
} else {
@@ -150,7 +154,7 @@
} else {
return 0;
};
- };
+ };
} wheelDef;
wheelDef modWheel = {
&h[assignCmd[4]].keyState,
@@ -158,7 +162,7 @@
&h[assignCmd[6]].keyState,
0, 127, 8,
0, 0, 0
- };
+ };
wheelDef pbWheel = {
&h[assignCmd[4]].keyState,
&h[assignCmd[5]].keyState,
@@ -170,7 +174,7 @@
&h[assignCmd[0]].keyState,
&h[assignCmd[1]].keyState,
&h[assignCmd[2]].keyState,
- 0, 127, 8,
+ 0, 127, 8,
96, 96, 96
};
bool toggleWheel = 0; // 0 for mod, 1 for pb
@@ -178,35 +182,59 @@
// ====== initialize rotary knob
#include <Rotary.h>
- const byte rotaryPinA = 20;
- const byte rotaryPinB = 21;
- const byte rotaryPinC = 24;
+ #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
+ GEM_u8g2 menu(u8g2, GEM_POINTER_ROW, GEM_ITEMS_COUNT_AUTO, 10, 10, 78); // menu item height; page screen top offset; menu values left offset
const byte defaultContrast = 63; // GFX default contrast
bool screenSaverOn = 0; //
uint32_t screenTime = 0; // GFX timer to count if screensaver should go on
- const uint32_t screenSaverMillis = 10000; //
+ const uint32_t screenSaverMillis = 10'000; //
// ====== initialize piezo buzzer
- //#include "RP2040_Volume.h" // simulated volume control on buzzer
- const byte tonePin = 23;
- //RP2040_Volume piezoBuzzer(tonePin, tonePin);
- byte buzzer = 0; // buzzer state
- byte currentBuzzNote = 255; // need to work on this
+ #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 = 10; //
+ uint32_t arpeggiateLength = 60; //
// ====== initialize tuning (microtonal) presets
@@ -216,9 +244,9 @@
float stepSize; // in cents, 100 = "normal" semitone.
} tuningDef;
enum {
- Twelve, Seventeen, Nineteen, TwentyTwo,
- TwentyFour, ThirtyOne, FortyOne, FiftyThree,
- SeventyTwo, BohlenPierce,
+ Twelve, Seventeen, Nineteen, TwentyTwo,
+ TwentyFour, ThirtyOne, FortyOne, FiftyThree,
+ SeventyTwo, BohlenPierce,
CarlosA, CarlosB, CarlosG
};
tuningDef tuningOptions[] = {
@@ -290,28 +318,27 @@
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, 0b01010000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Minor, natural", Twelve, { 0b10110101, 0b10100000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Minor, melodic", Twelve, { 0b10110101, 0b01010000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Minor, harmonic", Twelve, { 0b10110101, 0b10010000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Pentatonic, major", Twelve, { 0b10101001, 0b01000000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Pentatonic, minor", Twelve, { 0b10010101, 0b00100000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Blues", Twelve, { 0b10010111, 0b00100000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Double Harmonic", Twelve, { 0b11001101, 0b10010000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Phrygian", Twelve, { 0b11010101, 0b10100000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Phrygian Dominant", Twelve, { 0b11001101, 0b10100000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Dorian", Twelve, { 0b10110101, 0b01100000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Lydian", Twelve, { 0b10101011, 0b01010000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Lydian Dominant", Twelve, { 0b10101011, 0b01100000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Mixolydian", Twelve, { 0b10101101, 0b01100000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Locrian", Twelve, { 0b11010110, 0b10100000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Whole tone", Twelve, { 0b10101010, 0b10100000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
- { (char*)"Octatonic", Twelve, { 0b10110110, 0b11010000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
+ { (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, 0b10000000, 0, 0, 0, 0, 0, 0, 0, 0, 0 } },
+ 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
@@ -319,23 +346,22 @@
// ====== 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 };
+ 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,
+ byte satCode[] = { GRAY, VIVID,VIVID,VIVID,VIVID,VIVID,VIVID,VIVID,VIVID,VIVID,VIVID,
DULL, DULL, DULL, DULL, DULL, DULL, DULL, DULL, DULL, DULL };
- byte colorMode = 0;
+ byte colorMode = 1;
byte perceptual = 1;
- enum {assignDefault = -1}; // auto-determine this component of color
// ====== initialize note labels in each tuning, also used for key signature
typedef struct {
char* name;
byte tuning;
- int8_t offset; // steps from constant A4 to that key class
+ int8_t offset; // steps from constant A4 to that key class
colors tierColor;
} keyDef;
keyDef keyOptions[] = {
@@ -702,7 +728,7 @@
// ====== 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;
+ 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;
@@ -748,7 +774,7 @@
};
} presetDef;
presetDef current = {
- (char*)"Default",
+ (char*)"Default",
Twelve, // see the relevant enum{} statement
0, // default to the first layout, wicki hayden
0, // default to using no scale (chromatic)
@@ -756,7 +782,13 @@
0 // default to no transposition
};
// ====== functions
-
+ byte byteLerp(byte xOne, byte xTwo, float yOne, float yTwo, float y) {
+ float weight = (y - yOne) / (yTwo - yOne);
+ int temp = xOne + ((xTwo - xOne) * weight);
+ if (temp < xOne) {temp = xOne;};
+ if (temp > xTwo) {temp = xTwo;};
+ return temp;
+ }
int positiveMod(int n, int d) {
return (((n % d) + d) % d);
}
@@ -767,10 +799,10 @@
return temp;
}
bool hexOOB(coordinates c) {
- return (c.row < 0)
- || (c.row >= rowCount)
- || (c.col < 0)
- || (c.col >= (2 * colCount))
+ return (c.row < 0)
+ || (c.row >= ROWCOUNT)
+ || (c.col < 0)
+ || (c.col >= (2 * COLCOUNT))
|| ((c.col + c.row) & 1);
}
byte coordToIndex(coordinates c) {
@@ -783,7 +815,7 @@
coordinates hexVector(byte direction, byte distance) {
coordinates temp;
int8_t vertical[] = {0,-1,-1, 0, 1,1};
- int8_t horizontal[] = {2, 1,-1,-2,-1,1};
+ int8_t horizontal[] = {2, 1,-1,-2,-1,1};
temp.row = vertical[direction] * distance;
temp.col = horizontal[direction] * distance;
return temp;
@@ -824,9 +856,9 @@
if ((!perceptual) || (D > 360.0)) {
return 65536 * (D / 360.0);
} else {
- // red yellow green blue
+ // 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
+ // #ff0000 #ffff00 #00ff00 #00ffff #0000ff #ff00ff
int hueOut[] = { 0, 3640, 5461,10922,12743,16384,21845,27306,32768,38229,43690,49152,54613,58254,65535};
byte B = 0;
while (D - hueIn[B] > 0) {
@@ -840,7 +872,7 @@
float hueDegrees;
byte sat;
colors c;
- for (byte i = 0; i < hexCount; i++) {
+ for (byte i = 0; i < HEXCOUNT; i++) {
if (!(h[i].isCmd)) {
byte scaleDegree = positiveMod(h[i].steps + current.key().offset - current.findC(),current.tuning().cycleLength);
switch (colorMode) {
@@ -869,7 +901,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 < HEXCOUNT; i++) {
if (!(h[i].isCmd)) {
float N = stepsToMIDI(h[i].steps + current.key().offset + current.transpose);
if (N < 0 || N >= 128) {
@@ -885,9 +917,9 @@
"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) + ", " +
+ "note=" + String(h[i].note) + ", " +
+ "bend=" + String(h[i].bend) + ", " +
+ "freq=" + String(h[i].frequency) + ", " +
"inScale? " + String(h[i].inScale) + "."
));
};
@@ -896,19 +928,18 @@
}
void applyScale() {
sendToLog("applyScale was called:");
- for (byte i = 0; i < hexCount; i++) {
+ for (byte i = 0; i < HEXCOUNT; i++) {
if (!(h[i].isCmd)) {
byte degree = positiveMod(h[i].steps, current.tuning().cycleLength);
byte whichByte = degree / 8;
byte bitShift = 7 - (degree - (whichByte << 3));
byte digitMask = 1 << bitShift;
- h[i].inScale = (current.scale().step[whichByte] & digitMask)
->> bitShift;
+ 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) + ", " +
+ "note=" + String(h[i].note) + ", " +
"inScale? " + String(h[i].inScale) + "."
));
};
@@ -918,13 +949,13 @@
}
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 < HEXCOUNT; i++) {
if (!(h[i].isCmd)) {
coordinates dist = hexDistance(h[current.layout().rootHex].coords, h[i].coords);
h[i].steps = (
- (dist.col * current.layout().acrossSteps) +
+ (dist.col * current.layout().acrossSteps) +
(dist.row * (
- current.layout().acrossSteps +
+ current.layout().acrossSteps +
(2 * current.layout().dnLeftSteps)
))
) / 2;
@@ -941,46 +972,93 @@
}
// ====== buzzer routines
+ // the piezo buzzer is an on/off switch that can buzz as fast as the processor clock (133MHz)
+ // the processor is fast enough to emulate analog signals.
+ // the RP2040 has pulse width modulation (PWM) built into the hardware.
+ // it can output a %-on / %-off pattern at any percentage desired.
+ // at high enough frequencies, it sounds the same as an analog signal at that % volume.
+ // to emulate an 8-bit (0-255) analog sample, with phase-correction, we need a 9 bit (512) cycle.
+ // we can safely sample up to 260kHz (133MHz / 512) this way.
+ // the highest frequency note in MIDI is about 12.5kHz.
+ // it is therefore possible to emulate waveforms with 4 bits resolution (260kHz / 12.5kHz, it's > 16 but < 32).
+ // 1) set a constant PWM signal at F_CPU/512 (260kHz) to play on pin 23
+ // the PWM signal can emulate an analog value from 0 to 255.
+ // this is done in setup1().
+ // 2) if a note is to be played on the buzzer, get the frequency, and express as a period in microseconds.
+ // this is done in buzz().
+ // 3) divide the period into 16 subperiods. fractions of microsecond are distributed across the 16 ticks.
+ // 4) every subperiod, change the level of the PWM output so that you emulate the next in the sequence of
+ // 16 analog sample values. those values are based on the waveform shape chosen (square, sine, etc)
+ // 5) this value is also scaled by the MIDI velocity wheel.
+ // 6) hardware timers are used because they will interrupt and run even if other code is active.
+ // otherwise, the subperiod is essentially floored at the length of the main loop() which is
+ // thousands of microseconds long!
+ // the implementation of 6) is to make a single timer that calls back an interrupt function called advanceTick().
+ // the callback function then resets the interrupt flag and resets the timer alarm.
+ // the timer is set to go off at the time of the last timer, plus the subperiod (stored based on the last frequency played).
+ // after the timer is reset, the function then changes the level of the PWM based on 4) and 5) above.
+ // example:
+ // to buzz note 69 (A=440Hz) at velocity 96:
+ // period = 2273 microseconds
+ // subperiod = 142 microseconds for 15 ticks, 143 microseconds for 1 tick
+ // for a square wave play 8 periods at zero level, 8 periods at 96 * 129 / 64 = 203 level
+
+ void advanceTick() {
+ hw_clear_bits(&timer_hw->intr, 1u << ALARM_NUM);
+ wfTick = ((wfTick + 1) & (WAVE_RESOLUTION - 1));
+ microSecondsPerTick = (microSecondsPerCycle / WAVE_RESOLUTION)
+ + ((microSecondsPerCycle % WAVE_RESOLUTION) < wfTick)
+ ;
+ timer_hw->alarm[ALARM_NUM] = timer_hw->timerawl + microSecondsPerTick;
+ wfLvl = ((playbackMode && (currentHexBuzzing <= HEXCOUNT)) ? (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));
+ };
+ }
byte nextHeldNote() {
byte n = 255;
- for (byte i = 1; i < hexCount; i++) {
- byte checkNote = positiveMod(currentBuzzNote + i, hexCount);
- if ((h[checkNote].channel) && (!h[checkNote].isCmd)) {
- n = checkNote;
+ for (byte i = 1; i < HEXCOUNT; i++) {
+ byte j = positiveMod(currentHexBuzzing + i, HEXCOUNT);
+ if ((h[j].channel) && (!h[j].isCmd)) {
+ n = j;
break;
};
};
return n;
}
- void buzz(byte x) { // send 128 or larger to turn off tone
- currentBuzzNote = x;
- if ((!(h[x].isCmd)) && (h[x].note < 128) && (h[x].frequency < 32767)) {
- //piezoBuzzer.tone(h[x].frequency, (float)velWheel.curValue * (100.0 / 128.0), 16384, TIME_MS);
- tone(tonePin, h[x].frequency); // stock TONE library, but frequency changed to float
- } else {
- //piezoBuzzer.stop_tone();
- noTone(tonePin); // stock TONE library
+ void tryBuzzing(byte x) {
+ currentHexBuzzing = x;
+ if ((h[x].isCmd) || (h[x].note >= 128)) {
+ currentHexBuzzing = 255; // send 128 or larger to turn off tone
};
+ buzz();
}
// ====== MIDI routines
void setPitchBendRange(byte Ch, byte semitones) {
- MIDI.beginRpn(0, Ch);
- MIDI.sendRpnValue(semitones << 7, Ch);
- MIDI.endRpn(Ch);
- sendToLog(String(
- "set pitch bend range on ch " +
- String(Ch) + " to be " + String(semitones) + " semitones"
- ));
+ if (enableMIDI) { // MIDI mode only
+ MIDI.beginRpn(0, Ch);
+ MIDI.sendRpnValue(semitones << 7, Ch);
+ MIDI.endRpn(Ch);
+ sendToLog(String(
+ "set pitch bend range on ch " +
+ String(Ch) + " to be " + String(semitones) + " semitones"
+ ));
+ }
}
void setMPEzone(byte masterCh, byte sizeOfZone) {
- MIDI.beginRpn(6, masterCh);
- MIDI.sendRpnValue(sizeOfZone << 7, masterCh);
- MIDI.endRpn(masterCh);
- sendToLog(String(
- "tried sending MIDI msg to set MPE zone, master ch " +
- String(masterCh) + ", zone of this size: " + String(sizeOfZone)
- ));
+ if (enableMIDI) { // MIDI mode only
+ MIDI.beginRpn(6, masterCh);
+ MIDI.sendRpnValue(sizeOfZone << 7, masterCh);
+ MIDI.endRpn(masterCh);
+ sendToLog(String(
+ "tried sending MIDI msg to set MPE zone, master ch " +
+ String(masterCh) + ", zone of this size: " + String(sizeOfZone)
+ ));
+ }
}
void prepMIDIforMicrotones() {
bool makeZone = (MPE && (current.tuningIndex != Twelve)); // if MPE flag is on and tuning <> 12EDO
@@ -1000,38 +1078,41 @@
};
}
void chgModulation() {
- if (current.tuningIndex == Twelve) {
- MIDI.sendControlChange(1, modWheel.curValue, 1);
- sendToLog(String("sent mod value " + String(modWheel.curValue) + " to ch 1"));
- } else if (MPE) {
- MIDI.sendControlChange(1, modWheel.curValue, 1);
- sendToLog(String("sent mod value " + String(modWheel.curValue) + " to ch 1"));
- MIDI.sendControlChange(1, modWheel.curValue, 16);
- sendToLog(String("sent mod value " + String(modWheel.curValue) + " to ch 16"));
- } else {
- for (byte i = 0; i < 16; i++) {
- MIDI.sendControlChange(1, modWheel.curValue, i + 1);
- sendToLog(String("sent mod value " + String(modWheel.curValue) + " to ch " + String(i+1)));
+ 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"));
+ } else if (MPE) {
+ MIDI.sendControlChange(1, modWheel.curValue, 1);
+ sendToLog(String("sent mod value " + String(modWheel.curValue) + " to ch 1"));
+ MIDI.sendControlChange(1, modWheel.curValue, 16);
+ sendToLog(String("sent mod value " + String(modWheel.curValue) + " to ch 16"));
+ } else {
+ for (byte i = 0; i < 16; i++) {
+ MIDI.sendControlChange(1, modWheel.curValue, i + 1);
+ sendToLog(String("sent mod value " + String(modWheel.curValue) + " to ch " + String(i+1)));
+ };
};
- };
+ };
};
void chgUniversalPB() {
- if (current.tuningIndex == Twelve) {
- MIDI.sendPitchBend(pbWheel.curValue, 1);
- sendToLog(String("sent pb value " + String(pbWheel.curValue) + " to ch 1"));
- } else if (MPE) {
- MIDI.sendPitchBend(pbWheel.curValue, 1);
- sendToLog(String("sent pb value " + String(pbWheel.curValue) + " to ch 1"));
- MIDI.sendPitchBend(pbWheel.curValue, 16);
- sendToLog(String("sent pb value " + String(pbWheel.curValue) + " to ch 16"));
- } else {
- for (byte i = 0; i < 16; i++) {
- MIDI.sendPitchBend(channelBend[i] + pbWheel.curValue, i + 1);
- sendToLog(String("sent pb value " + String(channelBend[i] + pbWheel.curValue) + " to ch " + String(i+1)));
+ 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"));
+ } else if (MPE) {
+ MIDI.sendPitchBend(pbWheel.curValue, 1);
+ sendToLog(String("sent pb value " + String(pbWheel.curValue) + " to ch 1"));
+ MIDI.sendPitchBend(pbWheel.curValue, 16);
+ sendToLog(String("sent pb value " + String(pbWheel.curValue) + " to ch 16"));
+ } else {
+ for (byte i = 0; i < 16; i++) {
+ MIDI.sendPitchBend(channelBend[i] + pbWheel.curValue, i + 1);
+ sendToLog(String("sent pb value " + String(channelBend[i] + pbWheel.curValue) + " to ch " + String(i+1)));
+ };
};
};
}
-
byte assignChannel(byte x) {
if (current.tuningIndex == Twelve) {
return 1;
@@ -1064,20 +1145,21 @@
if (c <= 16) {
h[x].channel = c; // value is 1 - 16
if (current.tuningIndex != Twelve) {
- MIDI.sendPitchBend(h[x].bend, c); // ch 1-16
- };
- MIDI.sendNoteOn(h[x].note, velWheel.curValue, c); // ch 1-16
- sendToLog(String(
- "sent note on: " + String(h[x].note) +
- " pb " + String(h[x].bend) +
- " vel " + String(velWheel.curValue) +
- " ch " + String(c)
- ));
- if (current.tuningIndex != Twelve) {
channelPoly[c - 1]++; // array is 0 - 15
- };
- if (buzzer) {
- buzz(x);
+ };
+ if (playbackMode) {
+ tryBuzzing(x);
+ } else {
+ if (current.tuningIndex != Twelve) {
+ MIDI.sendPitchBend(h[x].bend, c); // ch 1-16
+ };
+ MIDI.sendNoteOn(h[x].note, velWheel.curValue, c); // ch 1-16
+ sendToLog(String(
+ "sent note on: " + String(h[x].note) +
+ " pb " + String(h[x].bend) +
+ " vel " + String(velWheel.curValue) +
+ " ch " + String(c)
+ ));
};
};
}
@@ -1085,13 +1167,6 @@
byte c = h[x].channel;
if (c) {
h[x].channel = 0;
- MIDI.sendNoteOff(h[x].note, velWheel.curValue, c);
- sendToLog(String(
- "sent note off: " + String(h[x].note) +
- " pb " + String(h[x].bend) +
- " vel " + String(velWheel.curValue) +
- " ch " + String(c)
- ));
if (current.tuningIndex != Twelve) {
switch (channelPoly[c - 1]) {
case 1:
@@ -1105,8 +1180,16 @@
break;
};
};
- if (buzzer) {
- buzz(nextHeldNote());
+ if (playbackMode) {
+ 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)
+ ));
};
};
}
@@ -1133,9 +1216,9 @@
};
}
void animateMirror() {
- for (byte i = 0; i < hexCount; i++) { // check every hex
- if ((!(h[i].isCmd)) && (h[i].channel)) { // that is a held note
- for (byte j = 0; j < hexCount; j++) { // compare to every hex
+ for (byte i = 0; i < HEXCOUNT; i++) { // check every hex
+ if ((!(h[i].isCmd)) && (h[i].channel)) { // that is a held note
+ for (byte j = 0; j < HEXCOUNT; j++) { // compare to every hex
if ((!(h[j].isCmd)) && (!(h[j].channel))) { // that is a note not being played
int16_t temp = h[i].steps - h[j].steps; // look at difference between notes
if (animationType == OctaveAnim) { // set octave diff to zero if need be
@@ -1145,42 +1228,33 @@
h[j].animate = 1;
};
};
- };
+ };
};
};
}
void animateOrbit() {
- for (byte i = 0; i < hexCount; i++) { // check every hex
- if ((!(h[i].isCmd)) && (h[i].channel)) { // that is a held note
- flagToAnimate(hexOffset(h[i].coords,hexVector((h[i].animFrame() % 6),1))); // different neighbor each frame
+ for (byte i = 0; i < HEXCOUNT; i++) { // check every hex
+ if ((!(h[i].isCmd)) && (h[i].channel)) { // that is a held note
+ flagToAnimate(hexOffset(h[i].coords,hexVector((h[i].animFrame() % 6),1))); // different neighbor each frame
};
};
}
void animateRadial() {
- for (byte i = 0; i < hexCount; i++) {
- // check every hex
- if (!(h[i].isCmd)) {
- // that is a note
+ for (byte i = 0; i < HEXCOUNT; i++) { // check every hex
+ if (!(h[i].isCmd)) { // that is a note
uint32_t radius = h[i].animFrame();
- if ((radius > 0) && (radius < 16)) {
- // played in the last 16 frames
- byte steps = ((animationType == SplashAnim) ? radius : 1);
- // star = 1 step to next corner; ring = 1 step per hex
- coordinates temp =
-hexOffset(h[i].coords,hexVector(DnLeft,radius)); // start at one corner of the ring
- for (byte dir = 0; dir < 6; dir++) {
- // walk along the ring in each of the 6 hex directions
- for (byte i = 0; i < steps; i++) {
- // # of steps to the next corner
- flagToAnimate(temp);
- // flag for animation
- temp = hexOffset(temp, hexVector(dir,radius / steps));
- // then next step
+ if ((radius > 0) && (radius < 16)) { // played in the last 16 frames
+ byte steps = ((animationType == SplashAnim) ? radius : 1); // star = 1 step to next corner; ring = 1 step per hex
+ coordinates temp = hexOffset(h[i].coords,hexVector(DnLeft,radius)); // start at one corner of the ring
+ for (byte dir = 0; dir < 6; dir++) { // walk along the ring in each of the 6 hex directions
+ for (byte i = 0; i < steps; i++) { // # of steps to the next corner
+ flagToAnimate(temp); // flag for animation
+ temp = hexOffset(temp, hexVector(dir,radius / steps)); // then next step
};
};
};
- };
- };
+ };
+ };
}
// ====== menu variables and routines
@@ -1188,24 +1262,29 @@ hexOffset(h[i].coords,hexVector(DnLeft,radius)); // start at one corner of the
// must declare these variables globally for some reason
// doing so down here so we don't have to forward declare callback functions
//
- SelectOptionByte optionByteYesOrNo[] = { { "No" , 0 },
- { "Yes" , 1 } };
- SelectOptionByte optionByteBuzzer[] = { { "Off" , 0 },
- { "Mono" , 1 },
- { "Arp'gio", 2 } };
- SelectOptionByte optionByteColor[] = { { "Rainbow", 0 },
- { "Tiered" , 1 } };
- SelectOptionByte optionByteAnimate[] = { { "None" , NoAnim },
- { "Octave" , OctaveAnim},
- { "By Note", NoteAnim},
- { "Star" , StarAnim},
- { "Splash" , SplashAnim},
- { "Orbit" , OrbitAnim} };
-
- GEMSelect selectYesOrNo(sizeof(optionByteYesOrNo) / sizeof(SelectOptionByte), optionByteYesOrNo);
- GEMSelect selectBuzzer( sizeof(optionByteBuzzer) / sizeof(SelectOptionByte), optionByteBuzzer);
- GEMSelect selectColor( sizeof(optionByteColor) / sizeof(SelectOptionByte), optionByteColor);
- GEMSelect selectAnimate(sizeof(optionByteAnimate) / sizeof(SelectOptionByte), optionByteAnimate);
+ 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");
@@ -1214,11 +1293,11 @@ hexOffset(h[i].coords,hexVector(DnLeft,radius)); // start at one corner of the
GEMItem* menuItemTuning[tuningCount]; // dynamically generate item based on tunings
GEMPage menuPageLayout("Layout");
- GEMItem menuGotoLayout("Layout", menuPageLayout);
+ GEMItem menuGotoLayout("Layout", menuPageLayout);
GEMItem* menuItemLayout[layoutCount]; // dynamically generate item based on presets
GEMPage menuPageScales("Scales");
- GEMItem menuGotoScales("Scales", menuPageScales);
+ GEMItem menuGotoScales("Scales", menuPageScales);
GEMItem* menuItemScales[scaleCount]; // dynamically generate item based on presets and if allowed in given EDO tuning
GEMPage menuPageKeys("Keys");
@@ -1226,14 +1305,13 @@ hexOffset(h[i].coords,hexVector(DnLeft,radius)); // start at one corner of the
GEMItem* menuItemKeys[keyCount]; // dynamically generate item based on presets
GEMItem menuItemScaleLock( "Scale lock?", scaleLock, selectYesOrNo);
- GEMItem menuItemMPE( "MPE Mode:", MPE,
-selectYesOrNo, prepMIDIforMicrotones);
- GEMItem menuItemBuzzer( "Buzzer:", buzzer, selectBuzzer);
- GEMItem menuItemColor( "Color mode:", colorMode,
-selectColor, resetHexLEDs);
- GEMItem menuItemPercep( "Adjust color:", perceptual,
-selectYesOrNo, resetHexLEDs);
+ GEMItem 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);
void menuHome() {
menu.setMenuPageCurrent(menuPageMain);
@@ -1327,11 +1405,12 @@ selectYesOrNo, resetHexLEDs);
menuPageMain.addMenuItem(menuItemScaleLock);
menuPageMain.addMenuItem(menuItemColor);
- menuPageMain.addMenuItem(menuItemBuzzer);
+ menuPageMain.addMenuItem(menuItemMIDI);
+ menuPageMain.addMenuItem(menuItemPlayback);
+ menuPageMain.addMenuItem(menuItemWaveform);
menuPageMain.addMenuItem(menuItemAnimate);
-
- menuPageMain.addMenuItem(menuItemMPE);
menuPageMain.addMenuItem(menuItemPercep);
+ menuPageMain.addMenuItem(menuItemMPE);
}
@@ -1360,13 +1439,13 @@ selectYesOrNo, resetHexLEDs);
{
pinMode(multiplexPins[p], OUTPUT); // Setting the row multiplexer pins to output.
}
- Wire.setSDA(lightPinSDA);
- Wire.setSCL(lightPinSCL);
+ Wire.setSDA(SDAPIN);
+ Wire.setSCL(SCLPIN);
pinMode(rotaryPinC, INPUT_PULLUP);
}
void setupGrid() {
sendToLog(String("initializing hex grid..."));
- for (byte i = 0; i < hexCount; i++) {
+ for (byte i = 0; i < HEXCOUNT; i++) {
h[i].coords = indexToCoord(i);
h[i].isCmd = 0;
h[i].note = 255;
@@ -1397,6 +1476,18 @@ selectYesOrNo, resetHexLEDs);
void testDiagnostics() {
sendToLog(String("theHDM was here"));
}
+ void setupPiezo() {
+ gpio_set_function(TONEPIN, GPIO_FUNC_PWM);
+ pwm_set_phase_correct(TONE_SL, true);
+ pwm_set_wrap(TONE_SL, 254);
+ pwm_set_clkdiv(TONE_SL, 1.0f);
+ pwm_set_chan_level(TONE_SL, TONE_CH, 0);
+ pwm_set_enabled(TONE_SL, true);
+ hw_set_bits(&timer_hw->inte, 1u << ALARM_NUM); // initialize the timer
+ irq_set_exclusive_handler(ALARM_IRQ, advanceTick); // function to run every interrupt
+ irq_set_enabled(ALARM_IRQ, true);
+ timer_hw->alarm[ALARM_NUM] = timer_hw->timerawl + microSecondsPerTick;
+ }
// ====== loop routines
@@ -1421,22 +1512,22 @@ selectYesOrNo, resetHexLEDs);
}
}
void readHexes() {
- for (byte r = 0; r < rowCount; r++) { // Iterate through each of the row pins on the multiplexing chip.
+ for (byte r = 0; r < ROWCOUNT; r++) { // Iterate through each of the row pins on the multiplexing chip.
for (byte d = 0; d < 4; d++) {
digitalWrite(multiplexPins[d], (r >> d) & 1);
}
- for (byte c = 0; c < colCount; c++) { // Now iterate through each of the column pins that are connected to the current row pin.
+ for (byte c = 0; c < COLCOUNT; c++) { // Now iterate through each of the column pins that are connected to the current row pin.
byte p = columnPins[c]; // Hold the currently selected column pin in a variable.
pinMode(p, INPUT_PULLUP); // Set that row pin to INPUT_PULLUP mode (+3.3V / HIGH).
delayMicroseconds(10); // Delay to give the pin modes time to change state (false readings are caused otherwise).
bool didYouPressHex = (digitalRead(p) == LOW); // hex is pressed if it returns LOW. else not pressed
- h[c + (r * colCount)].updateKeyState(didYouPressHex);
+ h[c + (r * COLCOUNT)].updateKeyState(didYouPressHex);
pinMode(p, INPUT); // Set the selected column pin back to INPUT mode (0V / LOW).
}
}
}
- void actionHexes() {
- for (byte i = 0; i < hexCount; i++) { // For all buttons in the deck
+ void actionHexes() {
+ for (byte i = 0; i < HEXCOUNT; i++) { // For all buttons in the deck
switch (h[i].keyState) {
case 1: // just pressed
if (h[i].isCmd) {
@@ -1460,26 +1551,28 @@ selectYesOrNo, resetHexLEDs);
};
}
void arpeggiate() {
- if (buzzer > 1) {
+ if (playbackMode > 1) {
if (runTime - currentBuzzTime > arpeggiateLength) {
currentBuzzTime = millis();
- byte nextNoteToBuzz = nextHeldNote();
- if (nextNoteToBuzz < cmdCode) {
- buzz(nextNoteToBuzz);
+ byte n = nextHeldNote();
+ if (n != 255) {
+ tryBuzzing(nextHeldNote());
};
};
};
}
- void updateWheels() {
+ void updateWheels() {
velWheel.setTargetValue();
- bool upd = velWheel.updateValue(); // this function returns a boolean, gotta put it somewhere even if it isn't being used
+ bool upd = velWheel.updateValue();
if (upd) {
+ buzz(); // update the volume live
sendToLog(String("vel became " + String(velWheel.curValue)));
}
if (toggleWheel) {
pbWheel.setTargetValue();
upd = pbWheel.updateValue();
if (upd) {
+ buzz();
chgUniversalPB();
};
} else {
@@ -1490,12 +1583,12 @@ selectYesOrNo, resetHexLEDs);
};
};
}
- void animateLEDs() { // TBD
- for (byte i = 0; i < hexCount; i++) {
+ void animateLEDs() { // TBD
+ for (byte i = 0; i < HEXCOUNT; i++) {
h[i].animate = 0;
};
if (animationType) {
- switch (animationType) {
+ switch (animationType) {
case StarAnim: case SplashAnim:
animateRadial();
break;
@@ -1504,21 +1597,14 @@ selectYesOrNo, resetHexLEDs);
break;
case OctaveAnim: case NoteAnim:
animateMirror();
- break;
+ break;
default:
break;
};
};
}
- byte byteLerp(byte xOne, byte xTwo, float yOne, float yTwo, float y) {
- float weight = (y - yOne) / (yTwo - yOne);
- int temp = xOne + ((xTwo - xOne) * weight);
- if (temp < xOne) {temp = xOne;};
- if (temp > xTwo) {temp = xTwo;};
- return temp;
- }
- void lightUpLEDs() {
- for (byte i = 0; i < hexCount; i++) {
+ void lightUpLEDs() {
+ for (byte i = 0; i < HEXCOUNT; i++) {
if (!(h[i].isCmd)) {
if (h[i].animate) {
strip.setPixelColor(i,h[i].LEDcolorAnim);
@@ -1549,15 +1635,13 @@ selectYesOrNo, resetHexLEDs);
0,0,64
)));
strip.setPixelColor(assignCmd[4],strip.gamma32(strip.ColorHSV(
- transformHue(0),satP * (pbWheel.curValue > 0),satP *
-(pbWheel.curValue > 0)
+ transformHue(0),satP * (pbWheel.curValue > 0),satP * (pbWheel.curValue > 0)
)));
strip.setPixelColor(assignCmd[5],strip.gamma32(strip.ColorHSV(
hueP,satP,255
)));
strip.setPixelColor(assignCmd[6],strip.gamma32(strip.ColorHSV(
- transformHue(180),satP * (pbWheel.curValue < 0),satP *
-(pbWheel.curValue < 0)
+ transformHue(180),satP * (pbWheel.curValue < 0),satP * (pbWheel.curValue < 0)
)));
} else {
// mod blue / yellow
@@ -1568,8 +1652,7 @@ selectYesOrNo, resetHexLEDs);
hueM,satM,((modWheel.curValue > 63) ? satM : 0)
)));
strip.setPixelColor(assignCmd[5],strip.gamma32(strip.ColorHSV(
- hueM,satM,((modWheel.curValue > 63) ? 127 + (satM / 2) : 127 -
-(satM / 2))
+ hueM,satM,((modWheel.curValue > 63) ? 127 + (satM / 2) : 127 - (satM / 2))
)));
strip.setPixelColor(assignCmd[6],strip.gamma32(strip.ColorHSV(
hueM,satM,127 + (satM / 2)
@@ -1587,8 +1670,7 @@ selectYesOrNo, resetHexLEDs);
rotaryWasClicked = rotaryIsClicked;
if (rotaryKnobTurns != 0) {
for (byte i = 0; i < abs(rotaryKnobTurns); i++) {
- menu.registerKeyPress(rotaryKnobTurns < 0 ? GEM_KEY_UP :
-GEM_KEY_DOWN);
+ menu.registerKeyPress(rotaryKnobTurns < 0 ? GEM_KEY_UP : GEM_KEY_DOWN);
}
rotaryKnobTurns = 0;
screenTime = 0;
@@ -1607,19 +1689,18 @@ GEM_KEY_DOWN);
rotaryKnobTurns--;
break;
}
+ // rotaryKnobTurns = (rotaryKnobTurns > maxKnobTurns ? maxKnobTurns : (rotaryKnobTurns < -maxKnobTurns ? -maxKnobTurns : rotaryKnobTurns));
}
// ====== setup() and loop()
void setup() {
- #if (defined(ARDUINO_ARCH_MBED) && defined(ARDUINO_ARCH_RP2040))
- TinyUSB_Device_Init(0); // Manual begin() is required on core
-without built-in support for TinyUSB such as mbed rp2040
- #endif
+ #if (defined(ARDUINO_ARCH_MBED) && defined(ARDUINO_ARCH_RP2040))
+ TinyUSB_Device_Init(0); // Manual begin() is required on core without built-in support for TinyUSB such as mbed rp2040
+ #endif
setupMIDI();
setupFileSystem();
setupPins();
- testDiagnostics(); // Print diagnostic troubleshooting information to serial monitor
setupGrid();
setupLEDs();
setupGFX();
@@ -1627,9 +1708,10 @@ without built-in support for TinyUSB such as mbed rp2040
for (byte i = 0; i < 5 && !TinyUSBDevice.mounted(); i++) {
delay(1); // wait until device mounted, maybe
};
+ testDiagnostics(); // Print diagnostic troubleshooting information to serial monitor
}
- void setup1() {
- //
+ void setup1() { // set up on second core
+ setupPiezo();
};
void loop() { // run on first core
timeTracker(); // Time tracking functions
@@ -1645,4 +1727,4 @@ without built-in support for TinyUSB such as mbed rp2040
}
void loop1() { // run on second core
keepTrackOfRotaryKnobTurns();
- }
+ } \ No newline at end of file