Firmware for HexBoard MIDI controller
Diffstat (limited to 'HexBoard_V1.1.ino')
| -rw-r--r-- | HexBoard_V1.1.ino | 173 |
1 files changed, 169 insertions, 4 deletions
diff --git a/HexBoard_V1.1.ino b/HexBoard_V1.1.ino index 209c81d..30892ba 100644 --- a/HexBoard_V1.1.ino +++ b/HexBoard_V1.1.ino @@ -239,7 +239,8 @@ bool pitchModToggle = 1; // Used to toggle between pitch bend and mod wheel byte activeButtons[elementCount]; // Array to hold current note button states byte previousActiveButtons[elementCount]; // Array to hold previous note button states for comparison unsigned long activeButtonsTime[elementCount]; // Array to track last note button activation time for debounce - +byte animationStep[elementCount]; // Array to track reactive lighting steps +int animationTime = 0; // Used for tracking how long since last lighting update // Variables for sequencer mode typedef struct { 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 }; @@ -386,7 +387,7 @@ GEMItem menuItemBuzzer("Buzzer:", buzzer); // For use when testing out unfinished features GEMItem menuItemTesting("Testing", menuPageTesting); boolean release = false; // Whether this is a release or not -GEMItem menuItemVersion("V0.3.1 ", release, GEM_READONLY); +GEMItem menuItemVersion("V0.4.0 ", release, GEM_READONLY); void sequencerSetup(); //Forward declaration // For enabling basic sequencer mode - not complete GEMItem menuItemSequencer("Sequencer:", sequencerMode, sequencerSetup); @@ -518,6 +519,9 @@ void loop() { heldButtons(); } + // Animations + reactiveLighting(); + // Do the LEDS strip.show(); @@ -792,7 +796,7 @@ void playNotes() { { if (currentLayout[i] < 128) { if (isNotePlayable(currentLayout[i])) { // If note is within the selected scale, light up and play - strip.setPixelColor(i, strip.ColorHSV(((currentLayout[i] - key + transpose) % 12) * 5006, 255, pressedBrightness)); + //strip.setPixelColor(i, strip.ColorHSV(((currentLayout[i] - key + transpose) % 12) * 5006, 255, pressedBrightness)); noteOn(midiChannel, (currentLayout[i] + transpose) % 128, midiVelocity); } } else { @@ -802,7 +806,7 @@ void playNotes() { // If the button is inactive (released) if (currentLayout[i] < 128) { if (isNotePlayable(currentLayout[i])) { - setLayoutLED(i); + //setLayoutLED(i); noteOff(midiChannel, (currentLayout[i] + transpose) % 128, 0); } } else { @@ -813,6 +817,166 @@ void playNotes() { } } +void reactiveLighting() { + animationTime = animationTime + loopTime; + if (animationTime >= 33) { // If it has been at least 33 ms (30fps) from last run, + animationTime = 0; // reset clock. + setLayoutLEDs(); // Start by setting the lights to their defaults so we can "paint" on top of it. + for (int i = 0; i < elementCount; i++) { // Scanning through the buttons + if (isNotePlayable(currentLayout[i]) && currentLayout[i] < 128) { // (if they are playable) + switch (lightMode) { // and implementing the selected lighting pattern + case 0: + buttonPattern(i); // Lights up the button pressed. + break; + case 1: + notePattern(i); // Lights up the same exact notes as played across the array. + break; + case 2: + octavePattern(i); // Lights up the same notes as played in all octaves across the array. + break; + case 3: + splashPattern(i); // Creates an expanding ring around the pressed button. + break; + case 4: + starPattern(i); // Creates a starburst around the pressed button. + break; + default: // Just in case something goes wrong? + buttonPattern(i); + break; + } + } + } + } +} + +void buttonPattern(int i) { + if (activeButtons[i] == 1) { // If it's an active button... + // ...then we light it up! + strip.setPixelColor(i, strip.ColorHSV(((currentLayout[i] - key + transpose) % 12) * 5006, 240, pressedBrightness)); + } +} + +void notePattern(int i) { + if (activeButtons[i] == 1) { // Check to see if the it's an active button. + for (int m = 0; m < elementCount; m++) { // Scanning through all the lights + if (currentLayout[m] < 128) { // Only runs on lights in the playable area + if (currentLayout[m] == currentLayout[i]) { // If it's the same note as the active button... + // ...then we light it up! + strip.setPixelColor(m, strip.ColorHSV(((currentLayout[m] - key + transpose) % 12) * 5006, 240, pressedBrightness)); + } + } + } + } +} + +void octavePattern(int i) { + if (activeButtons[i] == 1) { // Check to see if the it's an active button. + for (int m = 0; m < elementCount; m++) { // Scanning through all the lights + if (currentLayout[m] < 128) { // Only runs on lights in the playable area + if (currentLayout[m] % 12 == currentLayout[i] % 12) { // If it's in different octaves as the active button... + // ...then we light it up! + strip.setPixelColor(m, strip.ColorHSV(((currentLayout[m] - key + transpose) % 12) * 5006, 240, pressedBrightness)); + } + } + } + } +} + +void splashPattern(int i) { + int x1 = i % 10; // Calculate the coordinates of the pressed button + int y1 = i / 10; + if (animationStep[i] > 0) { // Oh boy, animation time! Might be overcomplicating this... + for (int m = 0; m < elementCount; m++) { // Scanning through all the lights + if (currentLayout[m] < 128) { // Only runs on lights in the playable area + int x2 = m % 10; // Coordinates of lights + int y2 = m / 10; + int dx = x1 - x2; // Difference between light and button pressed + int dy = y1 - y2; +// Penalty int shifts the lights over depending on if they are on odd or even rows to correct for the staggered rows +#if ModelNumber == 1 + int penalty = (((y1 % 2 == 0) && (y2 % 2 != 0) && (x1 > x2)) || ((y2 % 2 == 0) && (y1 % 2 != 0) && (x2 > x1))) ? 1 : 0; +#elif ModelNumber == 2 + int penalty = (((y1 % 2 == 0) && (y2 % 2 != 0) && (x1 < x2)) || ((y2 % 2 == 0) && (y1 % 2 != 0) && (x2 < x1))) ? 1 : 0; +#endif + // If the light is the correct distance from the button... + if (max(abs(dy), abs(dx) + floor(abs(dy) / 2) + penalty) == animationStep[i]) { + // light it up! + strip.setPixelColor(m, strip.ColorHSV(((currentLayout[m] - key + transpose) % 12) * 5006, 240, pressedBrightness)); + // or we could have it fade as it moves + //strip.setPixelColor(m, strip.ColorHSV(((currentLayout[m] - key + transpose) % 12) * 5006, 240, (pressedBrightness - animationStep[i]*12))); + } + } + } + } + if (activeButtons[i] == 1) { // Check to see if the it's an active button. + // Then we light up the pressed button + strip.setPixelColor(i, strip.ColorHSV(((currentLayout[i] - key + transpose) % 12) * 5006, 240, pressedBrightness)); + if (animationStep[i] < 16) { + animationStep[i]++; // Increment the animation to the next step for next time. + } + } else { + animationStep[i] = 0; // Stop the animation if the key is released + } +} + +void starPattern(int i) { + int x1 = i % 10; // Calculate the coordinates of the pressed button + int y1 = i / 10; + // Define the relative offsets of neighboring buttons in the pattern +#if ModelNumber == 1 + int offsets[][2] = { + { 0, 1 }, // Left + { 0, -1 }, // Right + { -1, (y1 % 2 == 0) ? 0 : -1 }, // Top Left (adjusted based on row parity) + { -1, (y1 % 2 == 0) ? 1 : 0 }, // Top Right (adjusted based on row parity) + { 1, (y1 % 2 == 0) ? 1 : 0 }, // Bottom Right (adjusted based on row parity) + { 1, (y1 % 2 == 0) ? 0 : -1 } // Bottom Left (adjusted based on row parity) + }; +#elif ModelNumber == 2 + int offsets[][2] = { + { 0, -1 }, // Left + { 0, 1 }, // Right + { -1, (y1 % 2 == 0) ? 0 : 1 }, // Top Left (adjusted based on row parity) + { -1, (y1 % 2 == 0) ? -1 : 0 }, // Top Right (adjusted based on row parity) + { 1, (y1 % 2 == 0) ? -1 : 0 }, // Bottom Right (adjusted based on row parity) + { 1, (y1 % 2 == 0) ? 0 : 1 } // Bottom Left (adjusted based on row parity) + }; +#endif + + + if (animationStep[i] > 0) { // Oh boy, animation time! + for (const auto& offset : offsets) { + // Calculate the neighboring button coordinates + int y2 = y1 + offset[0] * animationStep[i]; +#if ModelNumber == 1 + int x2 = x1 + offset[1] * animationStep[i] + ((y1 % 2 == 0) ? -1 : 1) * ((y1 == y2) ? 0 : 1) * (animationStep[i] / 2); +#elif ModelNumber == 2 + int x2 = x1 + offset[1] * animationStep[i] + ((y1 % 2 == 0) ? 1 : -1) * ((y1 == y2) ? 0 : 1) * (animationStep[i] / 2); +#endif + // Check if the neighboring button is within the layout boundaries + if (y2 >= 0 && y2 < 14 && x2 >= 0 && x2 < 10) { + // Calculate the index of the neighboring button + int neighborIndex = y2 * 10 + x2; + if (currentLayout[neighborIndex] < 128) { // If it's in the playable area... + // ...set the color for the neighboring button + strip.setPixelColor(neighborIndex, strip.ColorHSV(((currentLayout[neighborIndex] - key + transpose) % 12) * 5006, 240, pressedBrightness)); + } + } + } + } + + + if (activeButtons[i] == 1) { // Check to see if the it's an active button. + // Then we light up the pressed button + strip.setPixelColor(i, strip.ColorHSV(((currentLayout[i] - key + transpose) % 12) * 5006, 240, pressedBrightness)); + if (animationStep[i] < 16) { + animationStep[i]++; // Increment the animation to the next step for next time. + } + } else { + animationStep[i] = 0; // Stop the animation if the key is released + } +} + void heldButtons() { for (int i = 0; i < elementCount; i++) { if (activeButtons[i]) { @@ -1033,6 +1197,7 @@ void setupMenu() { menuPageMain.addMenuItem(menuItemBendSpeed); menuPageMain.addMenuItem(menuItemModSpeed); menuPageMain.addMenuItem(menuItemBrightness); + menuPageMain.addMenuItem(menuItemLighting); menuPageMain.addMenuItem(menuItemBuzzer); menuPageMain.addMenuItem(menuItemTesting); // Add menu items to Layout Select page |