Maker.io main logo

Hexpad

2023-06-06 | By Adafruit Industries

License: See Original Project 3D Printing Programmers

Courtesy of Adafruit

Guide by John Park

Overview

 

 

Play only the "good notes" with this MIDI Hexpad. You can build a hextacular isomorphic controller using an Adafruit QT Py RP2040, low-profile Kailh CHOC keyswitches, a custom PCB, and hexagonal keyswitches, in a 3D printed case, all running on CircuitPython.

 

 

Parts

Hexagonal Choc Keycaps

These beautiful low-profile keycaps were designed by Sol Bekic, a.k.a. s-ol) and produced by ‎FKCaps, more info available here. I ordered mine from Little Keyboards.‎

Here's an excellent build log on the design and creation of the keycaps.‎

Sol's 0x33 MIDI board was the inspiration for this build.‎

You can also choose to 3D print similar hex keycaps, which work particularly well with resin ‎printing methods. More info on that, and 3D model files are available later in this guide.‎

Screws

‎6ea. of M3 x 5mm screws

leds_hexpad-1930_1

Hexpad PCB

Keyswitches aren't breadboard/protoboard friendly, so we'll create a custom PCB (printed ‎circuit board) to build the hexpad. This will also allow us to place reverse-mount NeoPixel LEDs ‎under each key for glow-through action.

‎I used Fritzing to create the parts and PCB, although you could certainly do this in your favorite ‎board CAD software, such as Eagle, KiCad, and others.‎

Visualization

It was helpful for me to visualize the parts on a breadboard (despite the fact that you can't ‎really use the keyswitches on a breadboard). Here you can see we have seven GPIO pin ‎connections for the switches, and one connection for the NeoPixel data, along with power and ‎ground.‎

breadboard_2

Schematic

Before creating the PCB layout it's a good idea to clearly lay out the schematic view. Note the ‎use of net labels to keep things from getting too crowded with wires.‎

leds_schematic_3

Custom PCB Shape

You can use a custom PCB shape in Fritzing by importing an .svg file into the board image ‎parameter.‎

Here's a guide with many details on creating the .svg necessary.‎

leds_board_4

leds_board_5

PCB Design in Fritzing

This guide is an introduction to designing your PCB in Fritzing. The same techniques are used ‎for the Hexboard.‎

Below you'll find the finished board and design files for download.‎

leds_board_top_b_6

leds_board_top_b_7

Hexboardv02.3.fzz

Order PCBs

This page goes through the details of verifying your design and exporting your Gerber files. If ‎you want to use the pre-made files, download the .zip linked below.‎

Hexboardv02_1_gerbers.zip

There are lots of places to have your PCBs made -- I'm a fan of both OSHPark and JLCPCB in ‎particular, and I know people who like PCBWay a lot too. I'd recommend OSHPark for your first ‎boards as they have terrific customer service and a great UI for helping you through the ‎process.‎

Head to oshpark.com and then drag your Hexboardv02_1_gerber.zip file onto the "Let's get ‎started!" box. They'll ingest the zip, extract the files, and invite you to inspect the layers.‎

Build the Hexpad

Solder the LEDs

Solder the seven NeoPixel LEDs in place. Note the location of the "-" pad on the silkscreen, ‎that's where the GND leg on the NeoPixel that has the notched corner goes.‎

leds_hexpad-1550_8

leds_hexpad-1550_9

leds_hexpad-1550_10

leds_hexpad-1550_11

Solder the Headers

Next, you'll solder the header pins for the QT Py in place. DO NOT SOLDER THE QT PY YET!‎

Flip the board over so the bottom side that has the QT Py outline on it is facing up.‎

Place two ten pin rows of headers into the holes and press the plastic spacers down so they are ‎flush with the board. This will allow the overlapping keyswitch to be mounted flat.‎

Flip the board over while holding the pins in place (blue tac or tape can be helpful here) and ‎solder them in place.‎

 

leds_hexpad-1683_12

leds_hexpad-1683_13

leds_hexpad-1683_14

Solder the Keyswitches

Fit the keyswitches into their holes on the top side of the board (with the silkscreen outlines for ‎the keys), being careful not to bend the fragile legs.‎

Flip the board over and solder the keyswitches.‎

leds_hexpad-1688_15

leds_hexpad-1688_16

leds_hexpad-1688_17

leds_hexpad-1688_18

Solder the QT Py

Now that the switches are soldered, you can solder the QT Py in place. Once soldered, you may ‎optionally clip the excess header pin length.

leds_hexpad-1692_19

leds_hexpad-1692_20

Build the Case

‎3D Printing‎

Download the case STL file using the link below. Bored of printing with just a single color of ‎filament? Try this!‎

You can achieve a multi-color part with an FDM 3D printer using a "change filament" technique.‎

Enclosure design by John Park

leds_Hex-Case-Animation_21

Download Hexpad Case CAD Files

Post Processing Filament Change

In the Ultimaker CURA software, access the Post Processing Plugin window by going to the top ‎menu:‎

Extensions > Post Processing > Modify G-Code

Click the Add a Script button and choose Filament Change from the dropdown menu.‎

To achieve the five layers of colors, add four Filament Change scripts. Enter a value in the layer ‎section and enable the Use Firmware Configuration option. ‎

leds_cura-change-filament_22

Filament Change Layers

Use a yellow-colored filament for the layers 1-19, then swap out the filament for black when ‎the 3D printer parks and goes through the change filament process.‎

  1. Layer 20 – Black

  2. ‎Layer 40 – Yellow

  3. Layer 60 – Black

  4. Layer 80 – Yellow

Support Material

For best print quality, use support material in the USB port area. This will help with bridging ‎and creates a nice quality surface. Use the following settings in your slicer program.

  • Support Placement: Everywhere

  • Enable Support Interface

  • Support Interface Resolution: 0.2mm

  • Support Interface Density: 20%

  • Support Density: 10%

leds_case-supports-cura_23

‎3D Printed Case‎

The case features an opening for a USB-C type cable for powering the QT Py RP2040.‎

There are six M3 sized holes for securing the hex board PCB.‎

leds_case-top_24

leds_case-top_25

Hex Keycaps

Hexagon Keycaps

These beautiful low-profile keycaps were designed by Sol Bekic, a.k.a. s-ol and produced by ‎FKCaps, more info available here. I ordered mine from Little Keyboards. If they're out of stock, ‎you can 3D print your own.‎

The hexagon keycaps are designed to fit low profile CHOC mechanical key switches.‎

leds_hex-keycaps-switches_26

CAD Files

The hex keycaps can be 3D printed using FDM or SLA 3D printers. Use the link below to ‎download the part model in various 3D file formats.‎

Hexagon Keycaps designed by Noe Ruiz

leds_hex-keycaps-switches_27

Download Hexagon_Keycap.zip

‎3D Printing Service‎

PCBWay.com is a service that can 3D print high-quality parts in various materials and colors.‎

UTR-8100 transparent resin is a great option for making crystal clear key caps. Use the ‎following settings to have PCBWay produce and ship them to you.

  • Enter your quantity

  • Material: Resin, UTR-8100 (transparent)

  • Color: Transparent (Spray Varnish)

  • Process: SLA

leds_pcbway-site_28

leds_hexpad-resin-caps_29

Clear resin key caps

Slicing Keycaps

Use your preferred slicing software for 3D printing the keycaps using an FDM 3D printer.‎

For best illumination we suggest using a light or semi-translucent filament for the NeoPixels to ‎shine through the material.‎

leds_cura-slice-keycaps_30

CircuitPython

CircuitPython is a derivative of MicroPython designed to simplify experimentation and ‎education on low-cost microcontrollers. It makes it easier than ever to get prototyping by ‎requiring no upfront desktop software downloads. Simply copy and edit files on ‎the CIRCUITPY drive to iterate.‎

CircuitPython Quickstart

Follow this step-by-step to quickly get CircuitPython running on your board.‎

Download the latest version of CircuitPython for this board via circuitpython.org

Click the link above to download the latest CircuitPython UF2 file.‎

Save it wherever is convenient for you.‎

install_circuitpython_on_rp2040_RP2040_31

adafruit_products_QTRP_buttons_32

To enter the bootloader, hold down the BOOT/BOOTSEL button (highlighted in red above), and ‎while continuing to hold it (don't let go!), press and release the reset button (highlighted in ‎blue above). Continue to hold the BOOT/BOOTSEL button until the RPI-RP2 drive appears!‎

If the drive does not appear, release all the buttons, and then repeat the process above.‎

You can also start with your board unplugged from USB, press, and hold the BOOTSEL button ‎‎(highlighted in red above), continue to hold it while plugging it into USB, and wait for the drive ‎to appear before releasing the button.‎

A lot of people end up using charge-only USB cables and it is very frustrating! Make sure you ‎have a USB cable you know is good for data sync.‎

You will see a new disk drive appear called RPI-RP2.‎‎ ‎

Drag the adafruit_circuitpython_etc.uf2 file to RPI-RP2.‎

install_circuitpython_on_rp2040_drive_33

install_circuitpython_on_rp2040_drive_34

The RPI-RP2 drive will disappear, and a new disk drive called CIRCUITPY will appear.‎

That's it, you're done! :)‎

install_circuitpython_on_rp2040_RP2040_CIRCUITPY_35

Safe Mode

You want to edit your code.py or modify the files on your CIRCUITPY drive but find that you ‎can't. Perhaps your board has gotten into a state where CIRCUITPY is read-only. You may have ‎turned off the CIRCUITPY drive altogether. Whatever the reason, safe mode can help.‎

Safe mode in CircuitPython does not run any user code on startup and disables auto-reload. This ‎means a few things. First, safe mode bypasses any code in boot.py (where you can ‎set CIRCUITPY read-only or turn it off completely). Second, it does not run the code in code.py. ‎And finally, it does not automatically soft-reload when data is written to the CIRCUITPY drive.‎

Therefore, whatever you may have done to put your board in a non-interactive state, safe mode ‎gives you the opportunity to correct it without losing all of the data on the CIRCUITPY drive.‎

Entering Safe Mode

To enter safe mode when using CircuitPython, plug in your board or hit reset (highlighted in red ‎above). Immediately after the board starts up or resets, it waits 1000ms. On some boards, the ‎onboard status LED (highlighted in green above) will blink yellow during that time. If you press ‎reset during that 1000ms, the board will start up in safe mode. It can be difficult to react to the ‎yellow LED, so you may want to think of it simply as a slow double click of the reset button. ‎‎(Remember, a fast double click of reset enters the bootloader.)‎

In Safe Mode

If you successfully enter safe mode on CircuitPython, the LED will intermittently blink yellow ‎three times.‎

If you connect to the serial console, you'll find the following message.‎

Copy Code
Auto-reload is off.
Running in safe mode! Not running saved code.

CircuitPython is in safe mode because you pressed the reset button during boot. Press again to exit safe mode.

Press any key to enter the REPL. Use CTRL-D to reload.

 You can now edit the contents of the CIRCUITPY drive. Remember, your code will not run until ‎you press the reset button, or unplug and plug in your board, to get out of safe mode.

Flash Resetting UF2‎

If your board ever gets into a really weird state and doesn't even show up as a disk drive when ‎installing CircuitPython, try loading this 'nuke' UF2 which will do a 'deep clean' on your Flash ‎Memory. You will lose all the files on the board, but at least you'll be able to revive it! After ‎loading this UF2, follow the steps above to re-install CircuitPython.‎

Download flash erasing "nuke" UF2‎

Code and Use the Hexpad

Here's a great demo by Astrophage:‎

 

 

Text Editor

Adafruit recommends using the Mu editor for editing your CircuitPython code. You can get ‎more info in this guide.‎

Alternatively, you can use any text editor that saves simple text files.‎

Download the Project Bundle

Your project will use a specific set of CircuitPython libraries, and the code.py file. To get ‎everything you need, click on the Download Project Bundle link below, and uncompressed ‎the .zip file.‎

Drag the contents of the uncompressed bundle directory onto your board's CIRCUITPY drive, ‎replacing any existing files or directories with the same names, and adding any new ones that ‎are necessary.‎

leds_libshex_36

Download Project Bundle

Copy Code
# SPDX-FileCopyrightText: 2023 John Park for Adafruit
#
# SPDX-License-Identifier: MIT
# Hexboard seven key modal note/chord pad for MIDI instruments
# Runs on QT Py RP2040
# (other QT Pys should work, but the BOOT button is handy for initiating configuration)

import time
import board
from digitalio import DigitalInOut, Pull
import keypad
import neopixel
import rainbowio
import usb_midi
import adafruit_midi
from adafruit_midi.note_on import NoteOn
from adafruit_midi.note_off import NoteOff

button = DigitalInOut(board.BUTTON)
button.pull = Pull.UP

num_switches = 7
leds = neopixel.NeoPixel(board.A0, num_switches, brightness=0.7)
leds.fill(rainbowio.colorwheel(5))
leds.show()

# root_picked = False
note = 0
root = 0  # defaults to a C

#  lists of modal intervals (relative to root). Customize these if you want other scales/keys
major = (0, 2, 4, 5, 7, 9, 11)
minor = (0, 2, 3, 5, 7, 8, 10)
dorian = (0, 2, 3, 5, 7, 9, 10)
phrygian = (0, 1, 3, 5, 7, 8, 10)
lydian = (0, 2, 4, 6, 7, 9, 11)
mixolydian = (0, 2, 4, 5, 7, 9, 10)
locrian = (0, 1, 3, 5, 6, 8, 10)

modes = []
modes.append(major)
modes.append(minor)
modes.append(dorian)
modes.append(phrygian)
modes.append(lydian)
modes.append(mixolydian)
modes.append(locrian)

octv = 4
mode = 0  # default to major scale
play_chords = True  # default to play chords
pre_notes = modes[mode]  # initial mapping
keymap = (4, 3, 5, 0, 2, 6, 1)  # physical to logical key mapping

#  Key chart  | logical  |Interval chart example
#    6   1    |  6   7   |   9  11
#   5  0  2   | 3  4  5  |  4   5   7
#    4   3    |  0   1   |    0   2

# MIDI Setup
midi_usb_channel = 1  # change this to your desired MIDI out channel, 1-16
midi_usb = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=midi_usb_channel-1)

# Keyswitch setup
keyswitch_pins = (board.A3, board.A2, board.SDA, board.SCL, board.TX, board.RX, board.A1)
keyswitches = keypad.Keys(keyswitch_pins, value_when_pressed=False, pull=True)

def pick_mode():
    print("Choose mode...")
    mode_picked = False
    # pylint: disable=global-statement
    global mode
    while not mode_picked:
        # pylint: disable=redefined-outer-name
        keyswitch = keyswitches.events.get()  # check for key events
        if keyswitch:
            if keyswitch.pressed:
                mode = keymap.index(keyswitch.key_number)  # bottom left key is 0/major
                print("Mode is:", mode)
            if keyswitch.released:
                mode_picked = True
                leds.fill(rainbowio.colorwheel(8))
                leds.show()
                pick_octave()

def pick_octave():
    print("Choose octave...")
    octave_picked = False
    # pylint: disable=global-statement
    global octv
    while not octave_picked:
        if button.value is False:  # pressed
            launch_config()
            time.sleep(0.1)
        # pylint: disable=redefined-outer-name
        keyswitch = keyswitches.events.get()  # check for key events
        if keyswitch:
            if keyswitch.pressed:
                octv = keymap.index(keyswitch.key_number)  # get remapped position, lower left is 0
                print("Octave is:", octv)
            if keyswitch.released:
                octave_picked = True
                leds.fill(rainbowio.colorwheel(16))
                pick_root()

def pick_root():# user selects key in which to play
    print("Choose root note...")
    root_picked = False
    # pylint: disable=global-statement
    global root
    while not root_picked:
        if button.value is False:  # pressed
            launch_config()
            time.sleep(0.1)
        # pylint: disable=redefined-outer-name
        keyswitch = keyswitches.events.get()  # check for key events
        if keyswitch:
            if keyswitch.pressed:
                root = keymap.index(keyswitch.key_number)  # get remapped position, lower left is 0
                print("ksw:", keyswitch.key_number, "keymap index:", root)
                note = pre_notes[root]
                print("note:", note)
                midi_usb.send(NoteOn(note + (12*octv), 120))
                root_notes.clear()
                # pylint: disable=redefined-outer-name
                for mode_interval in range(num_switches):
                    root_notes.append(modes[mode][mode_interval] + note)
                print("root note intervals:", root_notes)
            if keyswitch.released:
                note = pre_notes[root]
                midi_usb.send(NoteOff(note + (12*octv), 0))
                root_picked = True
                leds.fill(0x0)
                leds[3] = rainbowio.colorwheel(12)
                leds[4] = rainbowio.colorwheel(5)
                leds.show()
                pick_chords()

def pick_chords():
    print("Choose chords vs. single notes...")
    chords_picked = False
    # pylint: disable=global-statement
    global play_chords
    while not chords_picked:
        if button.value is False:  # pressed
            launch_config()
            time.sleep(0.1)
        # pylint: disable=redefined-outer-name
        keyswitch = keyswitches.events.get()  # check for key events
        if keyswitch:
            if keyswitch.pressed:
                if keyswitch.key_number == 4:
                    play_chords = True
                    print("Chords are on")
                    chords_picked = True
                    playback_led_colors()
                if keyswitch.key_number == 3:
                    play_chords = False
                    print("Chords are off")
                    chords_picked = True
                    playback_led_colors()

# create the interval list based on root key and mode that's been picked in variable
root_notes = []
for mode_interval in range(num_switches):
    root_notes.append(modes[mode][mode_interval] + note)
print("---Hexpad---")
print("\nRoot note intervals:", root_notes)

key_colors = (18, 10, 18, 26, 26, 18, 10)

def playback_led_colors():
    for i in range(num_switches):
        leds[i]=(rainbowio.colorwheel(key_colors[i]))
        leds.show()
        time.sleep(0.1)

playback_led_colors()

# MIDI Note Message Functions
def send_note_on(note_num):
    if play_chords is True:
        note_num = root_notes[note_num] + (12*octv)
        midi_usb.send(NoteOn(note_num, 120))
        midi_usb.send(NoteOn(note_num + modes[mode][2], 80))
        midi_usb.send(NoteOn(note_num + modes[mode][4], 60))
        midi_usb.send(NoteOn(note_num+12, 80))
    else:
        note_num = root_notes[note_num] + (12*octv)
        midi_usb.send(NoteOn(note_num, 120))


def send_note_off(note_num):
    if play_chords is True:
        note_num = root_notes[note_num] + (12*octv)
        midi_usb.send(NoteOff(note_num, 0))
        midi_usb.send(NoteOff(note_num + modes[mode][2], 0))
        midi_usb.send(NoteOff(note_num + modes[mode][4], 0))
        midi_usb.send(NoteOff(note_num+12, 0))
    else:
        note_num = root_notes[note_num] + (12*octv)
        midi_usb.send(NoteOff(note_num, 0))

def send_midi_panic():
    for x in range(128):
        midi_usb.send(NoteOff(x, 0))

def launch_config():
    print("-launching config-")
    send_midi_panic()
    leds.fill(rainbowio.colorwheel(5))
    leds.show()
    pick_mode()

send_midi_panic()  # turn off any stuck notes at startup


while True:
    keyswitch = keyswitches.events.get()  # check for key events
    if keyswitch:
        keyswitch_number=keyswitch.key_number
        if keyswitch.pressed:
            note_picked = keymap.index(keyswitch.key_number)
            send_note_on(note_picked)
            leds[keyswitch_number]=(rainbowio.colorwheel(10))

            leds.show()
        if keyswitch.released:
            note_picked = keymap.index(keyswitch.key_number)
            send_note_off(note_picked)
            leds[keyswitch_number]=(rainbowio.colorwheel(key_colors[keyswitch_number]))
            leds.show()

    if button.value is False:  # pressed
        launch_config()
        time.sleep(0.1)

View on GitHub

Play the Hexpad

 

 

You may already have a favorite software synth, and chances are it'll work with the Hexpad. In ‎case you don't have one already picked out, here are some good ones to try.‎

iOS (with an OTG USB to Lightning adapter)

Chrome Web Browser

Linux / Windows / mac os

Once you've picked a synth, plug in your Hexpad and get playing!‎

Plug in a known good power and data USB-C cable to the Hexpad and plug the other end into ‎your computer or OTG adapter.‎

MIDI Device

It's very likely the synth will recognize it immediately, but if not, check the preferences and ‎choose the "QT Py RP2040" MIDI device.

leds_midi_37

‎Now, press any keys or combinations to play! All notes will be "good" notes because of the scale ‎mode.‎

If you want to change the mode, octave, root note, and the choice of modal chords vs. single ‎notes, simply press the Boot button on the QT Py. You'll see a prompt for each step in a serial ‎REPL window, but you can also do these steps without it.‎

These are the configuration steps and corresponding keys:‎

Mode Map

  • key 0 = major

  • key 1 = minor

  • key 2 = dorian

  • key 3 = phrygian

  • key 4 = lydian

  • key 5 = mixolydian

  • key 6 = locrian

You can create other modes by editing the code.py file directly.‎

leds_map_modes_38

Octave Map

The octaves correspond to the key numbers shown here. 4 or 3 are good places to start, but you ‎can play super low or ultra-high if you like!‎

leds_map_octaves_39

Root Map

The key-to-note assignment will vary during play depending on the root note and mode. While ‎in configuration mode on the root selection step, these are the assignments.‎

You can adjust these in code if you want sharps/flats.

leds_map_roots_40

Chord Mode

For polyphonic chord playback, press the chord key. For monophonic note playback, press ‎the single key.

leds_map_chord_mode_41

How it Works

The code first imports libraries, including keypad, neopixel, and the MIDI libraries.‎

‎Download File

Copy Code
import time
import board
from digitalio import DigitalInOut, Pull
import keypad
import neopixel
import rainbowio
import usb_midi
import adafruit_midi
from adafruit_midi.note_on import NoteOn
from adafruit_midi.note_off import NoteOff

Boot Button

The Boot button on the QT Py RP2040 can be used as a user button. You'll set it up so that it can ‎be used later to initiate the configuration process.‎

‎Download File

Copy Code
button = DigitalInOut(board.BUTTON)
button.pull = Pull.UP

LED Setup

Next the NeoPixels are set up and lit.‎

‎Download File‎

Copy Code
num_switches = 7
leds = neopixel.NeoPixel(board.A0, num_switches, brightness=0.7)
leds.fill(rainbowio.colorwheel(5))
leds.show()

Note and Mode Variables

A number of variables and lists are set up to store the note values and intervals of the scale ‎modes. ‎

‎Download File

Copy Code
note = 0
root = 0  # defaults to a C

#  lists of modal intervals (relative to root). Customize these if you want other scales/keys
major = (0, 2, 4, 5, 7, 9, 11)
minor = (0, 2, 3, 5, 7, 8, 10)
dorian = (0, 2, 3, 5, 7, 9, 10)
phrygian = (0, 1, 3, 5, 7, 8, 10)
lydian = (0, 2, 4, 6, 7, 9, 11)
mixolydian = (0, 2, 4, 5, 7, 9, 10)
locrian = (0, 1, 3, 5, 6, 8, 10)

modes = []
modes.append(major)
modes.append(minor)
modes.append(dorian)
modes.append(phrygian)
modes.append(lydian)
modes.append(mixolydian)
modes.append(locrian)

octv = 4
mode = 0  # default to major scale
play_chords = True  # default to play chords
pre_notes = modes[mode]  # initial mapping
keymap = (4, 3, 5, 0, 2, 6, 1)  # physical to logical key mapping

MIDI Setup

Next, the MIDI object is set up. Here you can also change the output channel from 1-16.‎

‎Download File‎

Copy Code
midi_usb_channel = 1  # change this to your desired MIDI out channel, 1-16
midi_usb = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=midi_usb_channel-1)

Keyswitch Setup

The keyswitches are set up using the keypad library, with their GPIO pins selected in order so ‎the physical placement will correspond to the logical key assignments 0-6.‎

Download File‎

Copy Code
keyswitch_pins = (board.A3, board.A2, board.SDA, board.SCL, board.TX, board.RX, board.A1)
keyswitches = keypad.Keys(keyswitch_pins, value_when_pressed=False, pull=True)

Configuration Functions

These functions are used during optional configuration (initiated by the user by pressing ‎the Boot button).‎

Note the use of while not mode_picked (and others) to have the code wait for the user to press ‎a button.‎

‎Download File‎

Copy Code
def pick_mode():
    print("Choose mode...")
    mode_picked = False
    # pylint: disable=global-statement
    global mode
    while not mode_picked:
        # pylint: disable=redefined-outer-name
        keyswitch = keyswitches.events.get()  # check for key events
        if keyswitch:
            if keyswitch.pressed:
                mode = keymap.index(keyswitch.key_number)  # bottom left key is 0/major
                print("Mode is:", mode)
            if keyswitch.released:
                mode_picked = True
                leds.fill(rainbowio.colorwheel(8))
                leds.show()
                pick_octave()

def pick_octave():
    print("Choose octave...")
    octave_picked = False
    # pylint: disable=global-statement
    global octv
    while not octave_picked:
        if button.value is False:  # pressed
            launch_config()
            time.sleep(0.1)
        # pylint: disable=redefined-outer-name
        keyswitch = keyswitches.events.get()  # check for key events
        if keyswitch:
            if keyswitch.pressed:
                octv = keymap.index(keyswitch.key_number)  # get remapped position, lower left is 0
                print("Octave is:", octv)
            if keyswitch.released:
                octave_picked = True
                leds.fill(rainbowio.colorwheel(16))
                pick_root()

def pick_root():# user selects key in which to play
    print("Choose root note...")
    root_picked = False
    # pylint: disable=global-statement
    global root
    while not root_picked:
        if button.value is False:  # pressed
            launch_config()
            time.sleep(0.1)
        # pylint: disable=redefined-outer-name
        keyswitch = keyswitches.events.get()  # check for key events
        if keyswitch:
            if keyswitch.pressed:
                root = keymap.index(keyswitch.key_number)  # get remapped position, lower left is 0
                print("ksw:", keyswitch.key_number, "keymap index:", root)
                note = pre_notes[root]
                print("note:", note)
                midi_usb.send(NoteOn(note + (12*octv), 120))
                root_notes.clear()
                # pylint: disable=redefined-outer-name
                for mode_interval in range(num_switches):
                    root_notes.append(modes[mode][mode_interval] + note)
                print("root note intervals:", root_notes)
            if keyswitch.released:
                note = pre_notes[root]
                midi_usb.send(NoteOff(note + (12*octv), 0))
                root_picked = True
                leds.fill(0x0)
                leds[3] = rainbowio.colorwheel(12)
                leds[4] = rainbowio.colorwheel(5)
                leds.show()
                pick_chords()

def pick_chords():
    print("Choose chords vs. single notes...")
    chords_picked = False
    # pylint: disable=global-statement
    global play_chords
    while not chords_picked:
        if button.value is False:  # pressed
            launch_config()
            time.sleep(0.1)
        # pylint: disable=redefined-outer-name
        keyswitch = keyswitches.events.get()  # check for key events
        if keyswitch:
            if keyswitch.pressed:
                if keyswitch.key_number == 4:
                    play_chords = True
                    print("Chords are on")
                    chords_picked = True
                    playback_led_colors()
                if keyswitch.key_number == 3:
                    play_chords = False
                    print("Chords are off")
                    chords_picked = True
                    playback_led_colors()
                    
def launch_config():
    print("-launching config-")
    send_midi_panic()
    leds.fill(rainbowio.colorwheel(5))
    leds.show()
    pick_mode()

Root Notes in Scale Mode

This list is created to put the proper notes for the chosen root and mode.‎

‎Download File‎

Copy Code
root_notes = []
for mode_interval in range(num_switches):
    root_notes.append(modes[mode][mode_interval] + note)

LED Colors

These are the color assignments per NeoPixel, all specified as values in ‎the rainbowio.colorwheel.‎

They are all set when the Hexpad is in playback mode (at start and after configuration) with ‎the playback_led_colors() function.‎

Download File‎

Copy Code
key_colors = (18, 10, 18, 26, 26, 18, 10)

def playback_led_colors():
    for i in range(num_switches):
        leds[i]=(rainbowio.colorwheel(key_colors[i]))
        leds.show()
        time.sleep(0.1)

Note Functions

These functions are used for playing notes/chords as well as sending MIDI panic at reset to turn ‎off all notes.‎

‎Download File‎

Copy Code
def send_note_on(note_num):
    if play_chords is True:
        note_num = root_notes[note_num] + (12*octv)
        midi_usb.send(NoteOn(note_num, 120))
        midi_usb.send(NoteOn(note_num + modes[mode][2], 80))
        midi_usb.send(NoteOn(note_num + modes[mode][4], 60))
        midi_usb.send(NoteOn(note_num+12, 80))
    else:
        note_num = root_notes[note_num] + (12*octv)
        midi_usb.send(NoteOn(note_num, 120))


def send_note_off(note_num):
    if play_chords is True:
        note_num = root_notes[note_num] + (12*octv)
        midi_usb.send(NoteOff(note_num, 0))
        midi_usb.send(NoteOff(note_num + modes[mode][2], 0))
        midi_usb.send(NoteOff(note_num + modes[mode][4], 0))
        midi_usb.send(NoteOff(note_num+12, 0))
    else:
        note_num = root_notes[note_num] + (12*octv)
        midi_usb.send(NoteOff(note, 0))

def send_midi_panic():
    for x in range(128):
        midi_usb.send(NoteOff(x, 0))

Main Loop

The program checks for keyswitch events and plays/releases the corresponding notes. You can ‎press multiple keys to send chords and even "chords of chords".‎

The main loop also checks for the boot button to be pressed to launch configuration.‎

‎Download File‎

Copy Code
while True:
    keyswitch = keyswitches.events.get()  # check for key events
    if keyswitch:
        keyswitch_number=keyswitch.key_number
        if keyswitch.pressed:
            note_picked = keymap.index(keyswitch.key_number)
            send_note_on(note_picked)
            leds[keyswitch_number]=(rainbowio.colorwheel(10))

            leds.show()
        if keyswitch.released:
            note_picked = keymap.index(keyswitch.key_number)
            send_note_off(note_picked)
            leds[keyswitch_number]=(rainbowio.colorwheel(key_colors[keyswitch_number]))
            leds.show()

    if button.value is False:  # pressed
        launch_config()
        time.sleep(0.1)
制造商零件编号 4900
QT PY RP2040
Adafruit Industries LLC
制造商零件编号 5114
KAILH CHOC LOW PROF WHITE 1=10pc
Adafruit Industries LLC
制造商零件编号 4960
NEOPIXEL REVERSE MOUNT RGB LEDS
Adafruit Industries LLC
Add all DigiKey Parts to Cart
TechForum

Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.

Visit TechForum