In order to make it simple to get up and running with your Keypad with no programming required, we've created a drag-and-drop firmware you can use. You can skip to the section on Custom Code in Arduino if you want to customize it.
Download
First, download the firmware file linked in the button below and save it to your computer hard drive somewhere you'll be able to find it, such as your Downloads folder.
Install the Firmware
Plug your QT Py into your computer with a good quality, data capable USB cable. Life is too short to go through the pain of accidentally using a power-only USB cable, so please round up any you own, cut them in half, travel to a distant land, bury them, and dance on their grave.
Your QT Py will start up with whatever code was running on it. Let's change that!
Bootloader Mode
Now, we'll put the QT Py into bootloader mode. In this mode it will appear as a USB drive on your computer and will be ready to receive a new .uf2 firmware file. Double-click the reset button on the top side of the board (you will need to open the case to do this if you previously closed it).
The QTPY_BOOT drive will show up on your computer. Simply drag-and-drop the key4x4.uf2 file onto it. It will copy the file and then automatically restart, running the code!
Play It
Now you can run the synth software of your choice (or hardware synth with USB MIDI Host capabilities) and pick the QT Py M0 as your active MIDI input.
The code will run at a clock rate of 120 BPM, sending MIDI clock, and when you press the keys you can play an A major chord (plus bonus G#) in two octaves.
Custom Code in Arduino
If you'd like to customize things further, you can edit the code in Arduino. Open your Arduino IDE. If you are new to Arduino, check out this guide: Adafruit Arduino IDE Setup.
You'll also need to make sure you've updated your Board Definitions to the latest version the SAMD boards by heading to Tools -> Board Manager in the Arduino IDE, filter for "Adafruit SAMD" and choose the install or update.
You'll also need to install the Tiny USB library, Keypad library, MIDI library, and NeoPixel library.
// SPDX-FileCopyrightText: 2021 John Park for Adafruit Industries // SPDX-License-Identifier: MIT // Keypad 4x4 for NeoKey Ortho Snap-apart PCB & QT Py M0 // Sends MIDI NoteOn/Off and Clock // --works well in VCV Rack for keeping clock timing via MIDI-CV CLK #include <Adafruit_TinyUSB.h> #include "Adafruit_Keypad.h" #include <Adafruit_NeoPixel.h> #include <MIDI.h> // User variables bool latch_mode = false; // set latch/toggle mode int BPM = 120; // set BPM int MIDI_OUT_CH = 1; // pick your midi output channel here uint32_t interval = 60000 / BPM; Adafruit_USBD_MIDI usb_midi; MIDI_CREATE_INSTANCE(Adafruit_USBD_MIDI, usb_midi, MIDI); const byte ROWS = 4; // rows const byte COLS = 4; // columns //define the symbols on the buttons of the keypads -- used for Serial output, debugging char keys[ROWS][COLS] = { {'1','2','3','4'}, {'5','6','7','8'}, {'A','B','C','D'}, {'E','F','G','H'} }; byte rowPins[ROWS] = {A3, A2, A1, A0}; //connect to the row pinouts of the keypad byte colPins[COLS] = {SCL, A6, A7, A8}; //connect to the column pinouts of the keypad //define the MIDI notes to send per key int pads[] = { // two A scales with added G# 76, 77, 79, 80, 69, 71, 72, 74, 64, 65, 67, 68, 57, 59, 60, 62 }; /*int pads[] = { // corresponds to 1010music blackbox pads for sample launching 48, 49, 50, 51, 44, 45, 46, 47, 40, 41, 42, 43, 36, 37, 38, 39 };*/ int current_key = 0; // current key press int int pixorder[] = { // to convert "snake" order to grid order 0, 1, 2, 3, 7, 6, 5, 4, 8, 9, 10, 11, 15, 14, 13, 12 } ; //initialize an instance of keypad Adafruit_Keypad customKeypad = Adafruit_Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS); #define NEO_PIN SDA #define NUMPIXELS ROWS*COLS Adafruit_NeoPixel pixels(NUMPIXELS, NEO_PIN, NEO_GRB + NEO_KHZ800); uint32_t red = pixels.Color(100, 0, 0); uint32_t light_blue = pixels.Color(0, 50, 60); uint32_t black = pixels.Color(0, 0, 0); uint32_t priorcolor = pixels.Color(0,0,0); // store state of color before sequence changes it uint32_t currentcolor = pixels.Color(0,0,0); int current_pixel = 0; // current pixel unsigned long previousMillis = 0; void setup() { //Serial.begin(9600); // use for debugging MIDI.begin(MIDI_OUT_CH); // begin MIDI before the delay to settle delay(1000); customKeypad.begin(); // begin keypad pixels.begin(); // begin neopixels for(int i=0; i<NUMPIXELS+1; i++) { // light up each pixel pixels.setPixelColor(pixorder[i], light_blue); pixels.setPixelColor(pixorder[i-1], black); pixels.show(); // Send the updated pixel colors to the hardware. delay(int(interval/4)); } } void loop() { customKeypad.tick(); unsigned long currentMillis = millis(); while(customKeypad.available()){ keypadEvent e = customKeypad.read(); // scan the keypad for changes //Serial.print((char)e.bit.KEY); if(e.bit.EVENT == KEY_JUST_PRESSED){ current_key = (e.bit.ROW * ROWS) + e.bit.COL ; //Serial.println(" pressed"); //Serial.println(interval); currentcolor = pixels.getPixelColor(pixorder[current_key]); if (latch_mode == true){ if (currentcolor==0){ MIDI.sendNoteOn(pads[current_key], 127, MIDI_OUT_CH); pixels.setPixelColor(pixorder[current_key], light_blue); } else{ MIDI.sendNoteOff(pads[current_key], 0, MIDI_OUT_CH); pixels.setPixelColor(pixorder[current_key], black); } } else{ MIDI.sendNoteOn(pads[current_key], 127, MIDI_OUT_CH); pixels.setPixelColor(pixorder[current_key], light_blue); } pixels.show(); } else if(e.bit.EVENT == KEY_JUST_RELEASED){ current_key = (e.bit.ROW * ROWS) + e.bit.COL ; //Serial.println(" released"); if (latch_mode == false){ MIDI.sendNoteOff(pads[current_key], 0, MIDI_OUT_CH); pixels.setPixelColor(pixorder[current_key], black); pixels.show(); } } // delay(10); } //----- Running light if (currentMillis - previousMillis >= interval) { MIDI.sendClock(); if (current_pixel==0){ // loop around to last pixel for color reset pixels.setPixelColor(pixorder[NUMPIXELS-1], priorcolor); } else{ pixels.setPixelColor(pixorder[current_pixel-1], priorcolor); } priorcolor = pixels.getPixelColor(pixorder[current_pixel]); // grabs the current color of the pixel for later use pixels.setPixelColor(pixorder[current_pixel], red); pixels.show(); current_pixel++; previousMillis = currentMillis; } if (current_pixel==NUMPIXELS){ current_pixel=0; } }
Customization
Latching
The main thing you may want to customize is the behavior of the switches -- do they latch or are the momentary?
Adjust this line from false to true and you will have each key latched as a NoteOn message when you press it. Press it a second time to unlatch it and send NoteOff.
bool latch_mode = false; // set latch/toggle mode
BPM clock
Beyond that you can change the BPM (note PPQN may vary between synths/softsynths so you may need to subdivide to get the desired clock rate).
int BPM = 120; // set BPM
MIDI Channel
You can also pick a different MIDI out channel:
int MIDI_OUT_CH = 1;
Scales
The array called pads[]
determines which notes are sent. You can adjust this to suit your needs. By default, the Neo Keypad sends two different octaves of A major scales with a bonus G#.
int pads[] = { // two A scales with added G#
76, 77, 79, 80,
69, 71, 72, 74,
64, 65, 67, 68,
57, 59, 60, 62
};
Uncomment this array for a chromatic set of notes that correspond to the pad launching mode of the 1010Music blackbox:
/*int pads[] = { // corresponds to 1010music blackbox pads for sample launching
48, 49, 50, 51,
44, 45, 46, 47,
40, 41, 42, 43,
36, 37, 38, 39
};*/
Page last edited January 21, 2025
Text editor powered by tinymce.