about summary refs log tree commit diff
diff options
context:
space:
mode:
authorZach DeCook <zachdecook@librem.one>2022-07-11 10:44:19 -0400
committerZach DeCook <zachdecook@librem.one>2022-07-11 10:44:19 -0400
commit3099179f40333457ed0bdc3999694f2f9deefe92 (patch)
treeff4c466d082285abaa600d24729b7ec43887392d
downloadHexBoard-3099179f40333457ed0bdc3999694f2f9deefe92.tar.gz
initial commit
-rw-r--r--HexBoard_V1.ino418
1 files changed, 418 insertions, 0 deletions
diff --git a/HexBoard_V1.ino b/HexBoard_V1.ino
new file mode 100644
index 0000000..4e142a4
--- /dev/null
+++ b/HexBoard_V1.ino
@@ -0,0 +1,418 @@
+// Hardware Information:
+// Teensy LC set to 48MHz with USB type MIDI
+
+//
+// Button matrix and LED locations
+// Portrait orientation top view:
+//            9   8   7   6   5   4   3   2   1
+//         20  19  18  17  16  15  14  13  12  11
+//           29  28  27  26  25  24  23  22  21
+//         40  39  38  37  36  35  34  33  32  31
+//           49  48  47  46  45  44  43  42  41
+//         60  59  58  57  56  55  54  53  52  51
+//   10      69  68  67  66  65  64  63  62  61
+// 30      80  79  78  77  76  75  74  73  72  71
+//   50      89  88  87  86  85  84  83  82  81
+// 70     100 99  98  97  96  95  94  93  92  91
+//   90     109 108 107 106 105 104 103 102 101
+//110     120 119 118 117 116 115 114 113 112 111
+//  130     129 128 127 126 125 124 123 122 121
+//        140 139 138 137 136 135 134 133 132 131
+
+//DIAGNOSTICS
+bool diagnostics = 0;
+
+// Define digital button matrix pins
+const byte columns[] = { 25, 24,  9,  8,  7,  6,  5,  4,  3,  2};                // Column pins in order from right to left
+const byte rows[]    = {10, 11, 12, 13, 14, 15, 16, 18, 19, 20, 21, 22, 23, 26}; // Row pins in order from top to bottom
+// 16 & 17 reserved for lights.
+const byte columnCount      = sizeof(columns);                          // The number of columns in the matrix
+const byte rowCount         = sizeof(rows);                             // The number of rows in the matrix
+const byte elementCount     = columnCount * rowCount;// The number of elements in the matrix
+
+// Since MIDI only uses 7 bits, we can give greater values special meanings.
+// (see commandPress)
+const int OCTAVEDOWN = 128;
+const int OCTAVEUP = 129;
+const int UNUSED = 255;
+
+// MIDI note value tables
+const byte wickiHaydenLayout[elementCount] = {
+   78,  80,  82,  84,  86,  88,  90,  92,  94,  OCTAVEUP,
+71,  73,  75,  77,  79,  81,  83,  85,  87,  89,
+   66,  68,  70,  72,  74,  76,  78,  80,  82,  OCTAVEDOWN,
+59,  61,  63,  65,  67,  69,  71,  73,  75,  77,
+   54,  56,  58,  60,  62,  64,  66,  68,  70,  UNUSED,
+47,  49,  51,  53,  55,  57,  59,  61,  63,  65,
+   42,  44,  46,  48,  50,  52,  54,  56,  58,  UNUSED,
+35,  37,  39,  41,  43,  45,  47,  49,  51,  53,
+   30,  32,  34,  36,  38,  40,  42,  44,  46,  UNUSED,
+23,  25,  27,  29,  31,  33,  35,  37,  39,  41
+};
+const byte harmonicTableLayout[elementCount] = {
+   20,  27,  34,  41,  48,  55,  62,  69,  76,  OCTAVEUP,
+17,  24,  31,  38,  45,  52,  59,  66,  73,  80,
+   21,  28,  35,  42,  49,  56,  63,  70,  77,  OCTAVEDOWN,
+18,  25,  32,  39,  46,  53,  60,  67,  74,  81,
+   22,  29,  36,  43,  50,  57,  64,  71,  78,  UNUSED,
+19,  26,  33,  40,  47,  54,  61,  68,  75,  82,
+   23,  30,  37,  44,  51,  58,  65,  72,  79,  UNUSED,
+20,  27,  34,  41,  48,  55,  62,  69,  76,  83,
+   24,  31,  38,  45,  52,  59,  66,  73,  80,  UNUSED,
+21,  28,  35,  42,  49,  56,  63,  70,  77,  84
+};
+const byte gerhardLayout[elementCount] = {
+   20,  21,  22,  23,  24,  25,  26,  27,  28,  OCTAVEUP,
+23,  24,  25,  26,  27,  28,  29,  30,  31,  32,
+   27,  28,  29,  30,  31,  32,  33,  34,  35,  OCTAVEDOWN,
+30,  31,  32,  33,  34,  35,  36,  37,  38,  39,
+   34,  35,  36,  37,  38,  39,  40,  41,  42,  UNUSED,
+37,  38,  39,  40,  41,  42,  43,  44,  45,  46,
+   41,  42,  43,  44,  45,  46,  47,  48,  49,  UNUSED,
+44,  45,  46,  47,  48,  49,  50,  51,  52,  53,
+   48,  49,  50,  51,  52,  53,  54,  55,  56,  UNUSED,
+51,  52,  53,  54,  55,  56,  57,  58,  59,  60
+};
+
+//byte *currentLayout = &wickiHaydenLayout;
+
+// Global time variables
+unsigned long   currentTime; // Program loop consistent variable for time in milliseconds since power on
+unsigned long timeBothPressed;
+const byte      debounceTime = 2; // Global digital button debounce time in milliseconds
+
+// Variables for holding digital button states and activation times
+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
+
+// Control button states
+
+byte octaveUpState; // Top row (white) right
+byte octaveDownState; // Bottom row (white) right
+
+
+// MIDI channel assignment
+byte midiChannel = 0;                                                   // Current MIDI channel (changed via user input)
+
+// MIDI program variables
+byte midiProgram[16];                                                   // MIDI program selection per channel (0-15)
+
+// Octave modifier
+int octave = 0;// Apply a MIDI note number offset (changed via user input in steps of 12)
+
+// Velocity levels
+byte velocity = 95;                                                     // Non-zero default velocity for testing; this will update via analog pot
+// END SETUP SECTION
+// ------------------------------------------------------------------------------------------------------------------------------------------------------------
+
+void setup()
+{ 
+  // Set pinModes for the digital button matrix.
+  for (int pinNumber = 0; pinNumber < columnCount; pinNumber++)                   // For each column pin...
+  {
+    pinMode(columns[pinNumber], INPUT_PULLUP);                                  // set the pinMode to INPUT_PULLUP (+3.3V / HIGH).
+  }
+  for (int pinNumber = 0; pinNumber < rowCount; pinNumber++)                      // For each row pin...
+  {
+    pinMode(rows[pinNumber], INPUT);                                            // Set the pinMode to INPUT (0V / LOW).
+  }
+}
+
+// ------------------------------------------------------------------------------------------------------------------------------------------------------------
+// START LOOP SECTION
+void loop()
+{
+  // Print diagnostic troubleshooting information to serial monitor
+  // diagnosticTest();
+
+  // Store the current time in a uniform variable for this program loop
+  currentTime = millis();
+
+  // Read and store the digital button states of the scanning matrix
+  readDigitalButtons();
+
+  // Set all states and values related to the control buttons and pots
+  //    runControlModule(); //turned off for now
+
+  // Run the octave select function
+  //    runOctave(); //wating until we get the basics sorted out
+
+  // Run the channel select function
+  //runChannelSelect();
+
+  // Send notes to the MIDI bus
+  playNotes();
+}
+// END LOOP SECTION
+// ------------------------------------------------------------------------------------------------------------------------------------------------------------
+
+
+// ------------------------------------------------------------------------------------------------------------------------------------------------------------
+// START FUNCTIONS SECTION
+
+void readDigitalButtons()
+{
+  // Button Deck
+  for (byte columnIndex = 0; columnIndex < columnCount; columnIndex++) // Iterate through each of the column pins.
+  {
+    if (diagnostics == 1){
+      Serial.println();
+    }
+    byte currentColumn = columns[columnIndex]; // Hold the currently selected column pin in a variable.
+    pinMode(currentColumn, OUTPUT); // Set that column pin to OUTPUT mode and...
+    digitalWrite(currentColumn, LOW); // set the pin state to LOW turning it into a temporary ground.
+    for (byte rowIndex = 0; rowIndex < rowCount; rowIndex++) // Now iterate through each of the row pins that are connected to the current column pin.
+    {
+      byte currentRow = rows[rowIndex]; // Hold the currently selected row pin in a variable.
+      pinMode(currentRow, INPUT_PULLUP); // Set that row pin to INPUT_PULLUP mode (+3.3V / HIGH).
+      byte buttonNumber = columnIndex + (rowIndex * columnCount); // Assign this location in the matrix a unique number.
+      if (diagnostics == 1){
+        Serial.print(buttonNumber);
+        Serial.print(" - ");
+      }
+      delayMicroseconds(50); // Delay to give the pin modes time to change state (false readings are caused otherwise).
+      byte buttonState = !digitalRead(currentRow); // Invert reading due to INPUT_PULLUP, and store the currently selected pin state.
+      if (buttonState == HIGH && (millis() - activeButtonsTime[buttonNumber]) > debounceTime)     // If button is active and passes debounce
+      {
+        if (diagnostics == 1){
+          Serial.print("1, ");
+        }
+        activeButtons[buttonNumber] = 1;                                                        // write a 1 to the storage variable
+        activeButtonsTime[buttonNumber] = millis();                                             // and save the last button press time for later debounce comparison.
+      }
+      if (buttonState == LOW)
+      {
+        if (diagnostics == 1){
+          Serial.print("0, ");
+        }
+        activeButtons[buttonNumber] = 0;                                                        // Or if the button is inactive, write a 0.
+      }
+      pinMode(currentRow, INPUT);                                                                 // Set the selected row pin back to INPUT mode (0V / LOW).
+    }
+    pinMode(currentColumn, INPUT);                                                                  // Set the selected column pin back to INPUT mode (0V / LOW) and move onto the next column pin.
+  }
+}
+
+void runControlModule()
+{
+  // Digital Buttons
+  for (int buttonNumber = 9; buttonNumber < 30; buttonNumber++) // Limit to the 10 buttons in the control panel
+  {
+    if (activeButtons[buttonNumber] != previousActiveButtons[buttonNumber]) // Compare current button state to the previous state, and if a difference is found...
+    {
+      if (activeButtons[buttonNumber] == 1) // If the buttons is active
+      {
+        if (buttonNumber == 9) {
+          octaveUpState      = HIGH;
+        }
+        if (buttonNumber == 29) {
+          octaveDownState    = HIGH;
+        }
+        previousActiveButtons[buttonNumber] = 1; // Update the "previous" variable for comparison next loop
+      }
+      if (activeButtons[buttonNumber] == 0) // If the button is inactive
+      {
+
+        if (buttonNumber == 19) {
+          octaveUpState      = LOW;
+        }
+        if (buttonNumber == 39) {
+          octaveDownState    = LOW;
+        }
+        previousActiveButtons[buttonNumber] = 0; // Update the "previous" variable for conparison next loop
+      }
+    }
+  }
+}
+
+// TODO: We still want to be able to change octaves with the two buttons 19 and 39.
+void runOctave()
+{
+  /*if (metaKeyState == LOW)                                                                        // If the meta key is not held
+    {
+      if (octaveUpState == HIGH && previousOctaveUpState == LOW && octave < 24)                   // Highest current Wicki-Hayden layout pitch is 94 - Keep pitch in bounds of 7 bit value range (0-127)
+      {
+          previousOctaveUpState = HIGH;                                                           // Lock input until released
+          for (int i = 10; i < elementCount; i++)                                                 // For all note buttons in the deck
+          {
+              activeButtons[i] = 0;                                                               // Pop any active notes to prevent hangs when abruptly changing octave modifier
+          }
+          playNotes();
+          octave = octave + 12;                                                                   // Increment octave modifier
+          // LCD Update Octave Info
+          if (octave ==  0) { lcd.setCursor(14,1); lcd.print(" 0"); }
+          if (octave == 12) { lcd.setCursor(14,1); lcd.write(0); lcd.print("1"); }
+          if (octave == 24) { lcd.setCursor(14,1); lcd.write(0); lcd.print("2"); }
+          if (octave == -12) { lcd.setCursor(14,1); lcd.write(1);lcd.print("1"); }
+          if (octave == -24) { lcd.setCursor(14,1); lcd.write(1);lcd.print("2"); }
+      }
+      if (octaveDownState == HIGH && previousOctaveDownState == LOW && octave > -23)              // Lowest current Wicki-Hayden layout pitch is 30 - Keep pitch in bounds of 7 bit value range (0-127)
+      {
+          previousOctaveDownState = HIGH;                                                         // Lock input until released
+          for (int i = 10; i < elementCount; i++)                                                 // For all note buttons in the deck
+          {
+              activeButtons[i] = 0;                                                               // Pop any active notes to prevent hangs when abruptly changing octave modifier
+          }
+          playNotes();
+          octave = octave - 12;                                                                   // Decrement octave modifier
+          // LCD Update Octave Info
+          if (octave ==  0) { lcd.setCursor(14,1); lcd.print(" 0"); }
+          if (octave == 12) { lcd.setCursor(14,1); lcd.write(0); lcd.print("1"); }
+          if (octave == 24) { lcd.setCursor(14,1); lcd.write(0); lcd.print("2"); }
+          if (octave == -12) { lcd.setCursor(14,1); lcd.write(1);lcd.print("1"); }
+          if (octave == -24) { lcd.setCursor(14,1); lcd.write(1);lcd.print("2"); }
+      }
+      if (octaveUpState == HIGH && octaveDownState == HIGH && octave != 0)                        // If both keys are pressed simultaneously and octave is not default
+      {
+          for (int i = 10; i < elementCount; i++)                                                 // For all note buttons in the deck
+          {
+              activeButtons[i] = 0;                                                               // Pop any active notes to prevent hangs when abruptly changing octave modifier
+          }
+          playNotes();
+          octave = 0;                                                                             // Reset octave modifier to 0
+          // LCD Update Octave Info
+          if (octave ==  0) { lcd.setCursor(14,1); lcd.print(" 0"); }
+          if (octave == 12) { lcd.setCursor(14,1); lcd.write(0); lcd.print("1"); }
+          if (octave == 24) { lcd.setCursor(14,1); lcd.write(0); lcd.print("2"); }
+          if (octave == -12) { lcd.setCursor(14,1); lcd.write(1);lcd.print("1"); }
+          if (octave == -24) { lcd.setCursor(14,1); lcd.write(1);lcd.print("2"); }
+      }
+    }*/
+}
+
+
+
+void playNotes()
+{
+  for (int i = 0; i < elementCount; i++) // For all buttons in the deck
+  {
+    if (activeButtons[i] != previousActiveButtons[i]) // If a change is detected
+    {
+      if (activeButtons[i] == 1) // If the button is active (newpress)
+      {
+        if (wickiHaydenLayout[i] < 128) {
+          noteOn(midiChannel, (wickiHaydenLayout[i] + octave) % 128 , velocity);
+        } else {
+      commandPress(wickiHaydenLayout[i]);
+        }
+        
+        previousActiveButtons[i] = 1; // Update the "previous" variable for comparison on next loop
+      }
+      if (activeButtons[i] == 0) // If the button is inactive (released)
+      {
+        if (wickiHaydenLayout[i] < 128) {
+          noteOff(midiChannel, (wickiHaydenLayout[i] + octave) % 128, 0);
+        } else {
+          commandRelease(wickiHaydenLayout[i]);
+        }
+        
+        previousActiveButtons[i] = 0; // Update the "previous" variable for comparison on next loop
+      }
+    }
+  }
+}
+
+// MIDI PACKET FUNCTIONS
+
+// Send MIDI Note On
+// 1st byte = Event type (0x09 = note on, 0x08 = note off).
+// 2nd byte = Event type bitwise ORed with MIDI channel.
+// 3rd byte = MIDI note number.
+// 4th byte = Velocity (7-bit range 0-127)
+void noteOn(byte channel, byte pitch, byte velocity)
+{
+  usbMIDI.sendNoteOn(pitch, velocity, channel);
+}
+void loopNoteOn(byte channel, byte pitch, byte velocity)
+{
+
+}
+
+void commandPress(byte command)
+{
+  if(command == OCTAVEDOWN){
+    octave -= 12;
+    octaveDownState = HIGH;
+  } else if (command == OCTAVEUP){
+    octave += 12;
+        octaveUpState = HIGH;
+  }
+  if (octaveDownState && octaveUpState) {
+    timeBothPressed = currentTime;
+  } else {
+    timeBothPressed = 0;
+  }
+}
+void commandRelease(byte command)
+{
+  if(command == OCTAVEDOWN){
+      octaveDownState = LOW;
+  } else if (command == OCTAVEUP){
+    octaveUpState = LOW;
+  }
+  if (timeBothPressed && currentTime > timeBothPressed + 500){
+    octave = 0;
+    // also change modes
+    timeBothPressed = 0;
+  }
+}
+
+
+// Send MIDI Note Off
+// 1st byte = Event type (0x09 = note on, 0x08 = note off).
+// 2nd byte = Event type bitwise ORed with MIDI channel.
+// 3rd byte = MIDI note number.
+// 4th byte = Velocity (7-bit range 0-127)
+void noteOff(byte channel, byte pitch, byte velocity)
+{
+  usbMIDI.sendNoteOff(pitch, velocity, channel);
+}
+void loopNoteOff(byte channel, byte pitch, byte velocity)
+{
+
+}
+
+// Control Change
+// 1st byte = Event type (0x0B = Control Change).
+// 2nd byte = Event type bitwise ORed with MIDI channel.
+// 3rd byte = MIDI CC number (7-bit range 0-127).
+// 4th byte = Control value (7-bit range 0-127).
+void controlChange(byte channel, byte control, byte value)
+{
+
+}
+void loopControlChange(byte channel, byte control, byte value)
+{
+
+}
+
+// Program Change
+// 1st byte = Event type (0x0C = Program Change).
+// 2nd byte = Event type bitwise ORed with MIDI channel.
+// 3rd byte = Program value (7-bit range 0-127).
+void programChange(byte channel, byte value)
+{
+
+}
+
+// Pitch Bend
+// (14 bit value 0-16363, neutral position = 8192)
+// 1st byte = Event type (0x0E = Pitch bend change).
+// 2nd byte = Event type bitwise ORed with MIDI channel.
+// 3rd byte = The 7 least significant bits of the value.
+// 4th byte = The 7 most significant bits of the value.
+void pitchBendChange(byte channel, byte lowValue, byte highValue)
+{
+
+}
+void loopPitchBendChange(byte channel, byte lowValue, byte highValue)
+{
+
+}
+
+// END FUNCTIONS SECTION
+// ------------------------------------------------------------------------------------------------------------------------------------------------------------
+
+// END OF PROGRAM
+// ------------------------------------------------------------------------------------------------------------------------------------------------------------