# USB MIDI Keyset Controller

## Overview

https://youtu.be/htniGNKc7W0

![A 3D printed MIDI controller with keys and knobs connected to a mobile phone running musical software](https://cdn-learn.adafruit.com/assets/assets/000/138/990/medium800/3d_printing_hero-mobile.jpg?1755099609 MIDI keyset controller powered by a KB2040 using CircuitPython)

Build a 3D printed USB MIDI controller that features keyset style switches, potentiometers, a rotary encoder, and an OLED DISPLAY using CircuitPython code powered by the Adafruit KB2040.&nbsp;

The OLED features a GUI that allows customization of MIDI notes, velocity, channel, and MIDI CC messages. Five CHOC key switches are used to trigger MIDI notes, and the potentiometers can adjust modulation and LFOs.

## Intuitive GUI

On the main screen, the MIDI notes and potentiometer values are overlaid on shapes that represent their components.

The rotary encoder can be used to select and edit the potentiometers or the keys. In the edit mode, you can change MIDI notes, MIDI CC, MIDI channel, velocity, and minimum/maximum range.&nbsp;

![GUI demo of MIDI Keyset project](https://cdn-learn.adafruit.com/assets/assets/000/139/036/medium640thumb/3d_printing_GUI-demo.jpg?1755029000)

## Inspired Design

The enclosure design is inspired by our&nbsp;[DIY USB keyset project](https://learn.adafruit.com/usb-keyset) that features similar hinged keys. The shape of the device felt like a vintage synthesizer, so we thought we'd reimagine the keyset as a MIDI controller.

![](https://cdn-learn.adafruit.com/assets/assets/000/139/061/medium640/3d_printing_final-2.jpg?1755096748)

# Parts
### Adafruit KB2040 - RP2040 Kee Boar Driver

[Adafruit KB2040 - RP2040 Kee Boar Driver](https://www.adafruit.com/product/5302)
A wild Kee Boar appears! It’s a shiny **KB2040**! An Arduino Pro Micro-shaped board for Keebs with RP2040. (#keeblife 4 evah) A lot of folks like using Adafruit parts for their Keeb builds – but with the ItsyBitsy not being pin-compatible with the Pro Micro pinout, it...

In Stock
[Buy Now](https://www.adafruit.com/product/5302)
[Related Guides to the Product](https://learn.adafruit.com/products/5302/guides)
![Angled shot of short black microcontroller.](https://cdn-shop.adafruit.com/640x480/5302-07.jpg)

### Monochrome 1.3" 128x64 OLED graphic display - STEMMA QT / Qwiic

[Monochrome 1.3" 128x64 OLED graphic display - STEMMA QT / Qwiic](https://www.adafruit.com/product/938)
These displays are small, only about 1.3" diagonal, but very readable due to the high contrast of an OLED display. This display is made of 128x64 individual white OLED pixels, each one is turned on or off by the controller chip. Because the display makes its own light, no backlight is...

In Stock
[Buy Now](https://www.adafruit.com/product/938)
[Related Guides to the Product](https://learn.adafruit.com/products/938/guides)
![Monochrome 1.3" OLED module with Adafruit logos falling like snow](https://cdn-shop.adafruit.com/product-videos/640x480/938-05.jpg)

### Rotary Encoder + Extras

[Rotary Encoder + Extras](https://www.adafruit.com/product/377)
This rotary encoder is the best of the best, it's a high-quality 24-pulse encoder, with detents and a nice feel. It is panel mountable for placement in a box, or you can plug it into a breadboard (just cut/bend the two mechanical side tabs.) We also include a nice soft-touch knob with an...

In Stock
[Buy Now](https://www.adafruit.com/product/377)
[Related Guides to the Product](https://learn.adafruit.com/products/377/guides)
![Rotary Encoder with rubbery knob](https://cdn-shop.adafruit.com/640x480/377-02.jpg)

### Kailh CHOC Low Profile White Clicky Key Switches

[Kailh CHOC Low Profile White Clicky Key Switches](https://www.adafruit.com/product/5114)
For crafting your very own custom keyboard, these **&nbsp;Kailh Choc Low Profile Clicky White&nbsp;mechanical key switches** &nbsp;are super slim, with ultra-low profile compared to MX compatible switches!

Please note these are&nbsp; **NOT MX...**

In Stock
[Buy Now](https://www.adafruit.com/product/5114)
[Related Guides to the Product](https://learn.adafruit.com/products/5114/guides)
![Angled shot of ten white Kailh Choc V1 keycaps.](https://cdn-shop.adafruit.com/640x480/5114-00.jpg)

**Three (3) of each:**

### Panel Mount 10K potentiometer (Breadboard Friendly)

[Panel Mount 10K potentiometer (Breadboard Friendly)](https://www.adafruit.com/product/562)
This potentiometer is a two-in-one, good in a breadboard or with a panel. It's a fairly standard linear taper 10K ohm potentiometer, with a grippy shaft. It's smooth and easy to turn, but not so loose that it will shift on its own. We like this one because the legs are 0.2" apart...

In Stock
[Buy Now](https://www.adafruit.com/product/562)
[Related Guides to the Product](https://learn.adafruit.com/products/562/guides)
![Angled Shot of the Panel Mount 10K potentiometer (Breadboard Friendly)](https://cdn-shop.adafruit.com/640x480/562-03.jpg)

 **Also:**

### Part: White Micro Potentiometer Knob
quantity: 1
4 pack
[White Micro Potentiometer Knob](https://www.adafruit.com/product/5538)

### Part: STEMMA QT Cable
quantity: 1
Qwiic JST SH 4-pin Cable - 100mm Long
[STEMMA QT Cable](https://www.adafruit.com/product/4210)

### Part: USB Type C to USB A Cable
quantity: 1
 with 540 Degree Rotating End - 1 meter long
[USB Type C to USB A Cable](https://www.adafruit.com/product/6278)

### Part: Switch Sockets for Kailh CHOC Compatible Keys
quantity: 1
10 Pack
[Switch Sockets for Kailh CHOC Compatible Keys](https://www.adafruit.com/product/5118)

### Part: Silicone Cover Stranded-Core Wire
quantity: 1
50ft 30AWG Red
[Silicone Cover Stranded-Core Wire](https://www.adafruit.com/product/3165)

### Part: Silicone Cover Stranded-Core Wire
quantity: 1
50ft 30AWG Blue
[Silicone Cover Stranded-Core Wire](https://www.adafruit.com/product/3166)

### Part: Silicone Cover Stranded-Core Wire
quantity: 1
50ft 30AWG Black
[Silicone Cover Stranded-Core Wire](https://www.adafruit.com/product/3164)

### Part: Silicone Cover Stranded-Core Wire
quantity: 1
50ft 30AWG Yellow
[Silicone Cover Stranded-Core Wire](https://www.adafruit.com/product/3167)

### Part: Silicone Cover Stranded-Core Wire
quantity: 1
50ft 30AWG Green
[Silicone Cover Stranded-Core Wire](https://www.adafruit.com/product/3168)

### Part: Silicone Cover Stranded-Core Wire
quantity: 1
50ft 30AWG White
[Silicone Cover Stranded-Core Wire](https://www.adafruit.com/product/3169)

## Hardware

The following fasteners are required for the assembly of the 3D printed enclosure.

- 4x M2.5 x 6mm long steel machine screws
- 7x M3 x 6mm long steel machine screws

# USB MIDI Keyset Controller

## Circuit Diagram

The diagram below provides a general visual reference for wiring of the components once you get to the **Assembly** page. This diagram was created using the software package [Fritzing](http://fritzing.org/download/).

## Adafruit Library for Fritzing

Adafruit uses the Adafruit Fritzing parts library to create circuit diagrams for projects. You can download the library or just grab individual parts. Get the library and parts from [GitHub - Adafruit Fritzing Parts](https://github.com/adafruit/Fritzing-Library/tree/master/parts).

![](https://cdn-learn.adafruit.com/assets/assets/000/138/529/medium800/3d_printing_circuit-diagram.jpg?1753291613 )

## Wired Connections

- **Switch 1** &nbsp;to **D4** pin on **KB2040**
- **Switch 2** to **D5** pin on **KB2040**
- **Switch 3** to **D6** pin on **KB2040**
- **Switch 4** &nbsp;to **D7** pin on **KB2040**
- **Switch 5** to **D8** pin on **KB2040**
- **Switch 1-5 ground** pin to **ground** pin on **KB2040**
- **Rotary encoder** to **D2, D3** and **Ground** on **KB2040**
- **Rotary switch** to **A3** and **Ground** on **KB204**
- **Potentiometer 1** to **A2** , **Ground** and **3V** pins&nbsp;on **KB2040**
- **Potentiometer 2** to **A1** , **Ground** and **3V** pins&nbsp;on **KB2040**
- **Potentiometer 3** to **A0** , **Ground** and **3V** pins&nbsp;on **KB2040**
- **OLED STEMMA QT** to **STEMMA QT** on&nbsp; **KB2040**

The **Adafruit KB2040** &nbsp;is powered via USB.

# USB MIDI Keyset Controller

## CAD Files

## CAD Assembly

The main assembly is available in Fusion 360 and STEP file formats. This includes all of the 3D printed parts and electronic components used in the project. Use the main assembly to create any edits, updates, or modifications.&nbsp;

![](https://cdn-learn.adafruit.com/assets/assets/000/139/066/medium640thumb/3d_printing_cad.jpg?1755176302)

![](https://cdn-learn.adafruit.com/assets/assets/000/139/067/medium640/3d_printing_3d-parts.jpg?1755176311)

## 3D Printed Parts

Individual 3MF files for 3D printing are oriented and ready to print on FDM machines using PLA filament. Original design source may be downloaded using the links below. Make multiple copies of the following parts.

- 5x Key.3mf
- 4x M2.5 Washer.3mf

![](https://cdn-learn.adafruit.com/assets/assets/000/139/042/medium640/3d_printing_slice-parts-plate.jpg?1755032741)

[Download 3MF.zip](https://cdn-learn.adafruit.com/assets/assets/000/139/046/original/3MF.zip?1755033288)
[Download CAD Source Files](https://cdn-learn.adafruit.com/assets/assets/000/139/045/original/CAD_source.zip?1755033269)
## Build Volume

The parts require a 3D printer with a minimum build volume of:

- 130mm (X) x 110mm (Y) x 122mm (Z)

![](https://cdn-learn.adafruit.com/assets/assets/000/139/043/medium640/3d_printing_slice-size.jpg?1755032916)

## Multicolor Part (Optional)

The cover can optionally be printed in multiple colors using a multicolor capable 3D printer. The 3MF file contains multiple objects that can be assigned different colors in the slicing software.&nbsp;

![](https://cdn-learn.adafruit.com/assets/assets/000/139/040/medium640/3d_printing_slice-multicolor.jpg?1755032147)

![](https://cdn-learn.adafruit.com/assets/assets/000/139/041/medium640/3d_printing_slice-parts-colors.jpg?1755032245)

## Design Source Files

The project assembly was designed in Fusion 360. Once opened in Fusion 360, It can be exported in different formats like STEP, STL and more.

Electronic components like Adafruit's boards, displays, connectors and more can be downloaded from the&nbsp;[Adafruit CAD parts GitHub Repo](https://github.com/adafruit/Adafruit_CAD_Parts/).

![](https://cdn-learn.adafruit.com/assets/assets/000/139/044/medium640thumb/3d_printing_5302-KB2040.jpg?1755033033)

# USB MIDI Keyset Controller

## CircuitPython

[CircuitPython](https://github.com/adafruit/circuitpython) is a derivative of [MicroPython](https://micropython.org) 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](https://circuitpython.org/board/adafruit_kb2040/)
 **Click the link above to download the latest CircuitPython UF2 file.**

Save it wherever is convenient for you.

![install_circuitpython_on_rp2040_RP2040_UF2_downloaded.jpg](https://cdn-learn.adafruit.com/assets/assets/000/101/655/medium640/install_circuitpython_on_rp2040_RP2040_UF2_downloaded.jpg?1618943202)

![](https://cdn-learn.adafruit.com/assets/assets/000/108/852/medium800/adafruit_products_KeeBoar_boot_reset.jpg?1644515196)

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 red or blue above).&nbsp;**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**.

&nbsp;

Drag the **adafruit\_circuitpython\_etc.uf2** file to **RPI-RP2.**

![install_circuitpython_on_rp2040_RP2040_bootloader_drive.jpg](https://cdn-learn.adafruit.com/assets/assets/000/101/656/medium640/install_circuitpython_on_rp2040_RP2040_bootloader_drive.jpg?1618943666)

![install_circuitpython_on_rp2040_RP2040_drag_UF2.jpg](https://cdn-learn.adafruit.com/assets/assets/000/101/657/medium640/install_circuitpython_on_rp2040_RP2040_drag_UF2.jpg?1618943674)

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.jpg](https://cdn-learn.adafruit.com/assets/assets/000/101/658/medium640/install_circuitpython_on_rp2040_RP2040_CIRCUITPY.jpg?1618943864)

## 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.

```terminal
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 CIRCUITPY doesn't show up as a disk drive after installing CircuitPython, try loading this 'nuke' UF2 to RPI-RP2. 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](https://cdn-learn.adafruit.com/assets/assets/000/101/659/original/flash_nuke.uf2?1618945856)
# USB MIDI Keyset Controller

## Code the Keyset

Once you've finished setting up your KB2040 with CircuitPython, you can access the code and necessary libraries by downloading the Project Bundle.

To do this, click on the **Download Project Bundle** button in the window below. It will download to your computer as a zipped folder.

https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/Keyset_MIDI_Controller/code.py

## Upload the Code and Libraries to the KB2040

After downloading the Project Bundle, plug your KB2040 into the computer's USB port with a known good USB data+power cable. You should see a new flash drive appear in the computer's File Explorer or Finder (depending on your operating system) called **CIRCUITPY**. Unzip the folder and copy the following items to the KB2040's **CIRCUITPY** drive.

- **lib** folder
- **code.py**

Your KB2040 **CIRCUITPY** drive should look like this after copying the **lib** folder&nbsp;and **code.py** file:

![CIRCUITPY drive](https://adafruit.github.io/Adafruit_Learning_System_Guides/Keyset_MIDI_Controller.png )

## How the CircuitPython Code Works

At the top of the code is a list of all of the MIDI note names as strings. They are in the same index position as their matching MIDI note number.&nbsp;

```python
# midi note names
MIDI_NOTE_NAMES = [
        "C-2", "C#-2", "D-2", "D#-2", "E-2", "F-2", "F#-2", "G-2", "G#-2", "A-2", "A#-2", "B-2",
        "C-1", "C#-1", "D-1", "D#-1", "E-1", "F-1", "F#-1", "G-1", "G#-1", "A-1", "A#-1", "B-1",
        "C0", "C#0", "D0", "D#0", "E0", "F0", "F#0", "G0", "G#0", "A0", "A#0", "B0",
        "C1", "C#1", "D1", "D#1", "E1", "F1", "F#1", "G1", "G#1", "A1", "A#1", "B1",
        "C2", "C#2", "D2", "D#2", "E2", "F2", "F#2", "G2", "G#2", "A2", "A#2", "B2",
        "C3", "C#3", "D3", "D#3", "E3", "F3", "F#3", "G3", "G#3", "A3", "A#3", "B3",
        "C4", "C#4", "D4", "D#4", "E4", "F4", "F#4", "G4", "G#4", "A4", "A#4", "B4",
        "C5", "C#5", "D5", "D#5", "E5", "F5", "F#5", "G5", "G#5", "A5", "A#5", "B5",
        "C6", "C#6", "D6", "D#6", "E6", "F6", "F#6", "G6", "G#6", "A6", "A#6", "B6",
        "C7", "C#7", "D7", "D#7", "E7", "F7", "F#7", "G7", "G#7", "A7", "A#7", "B7",
        "C8", "C#8", "D8", "D#8", "E8", "F8", "F#8", "G8", "G#8", "A8", "A#8", "B8",
        "C9", "C#9", "D9", "D#9", "E9", "F9", "F#9", "G9", "G#9", "A9", "A#9", "B9"
        ]
```

This is followed by a dictionary called `control_settings`. This dictionary is used for the settings for the potentiometers and keys.

```python
# Data structure to store all control settings
control_settings = {
    'pots': [
        {'cc_num': 1, 'range_min': 0, 'range_max': 127, 'channel': 0, 'current_val': 0},  # Pot A
        {'cc_num': 10, 'range_min': 0, 'range_max': 127, 'channel': 0, 'current_val': 0}, # Pot B
        {'cc_num': 11, 'range_min': 0, 'range_max': 127, 'channel': 0, 'current_val': 0}, # Pot C
    ],
    'keys': [
        {'note': 61, 'velocity': 120, 'channel': 0},  # Key 1
        {'note': 64, 'velocity': 120, 'channel': 0},  # Key 2
        {'note': 66, 'velocity': 120, 'channel': 0},  # Key 3
        {'note': 68, 'velocity': 120, 'channel': 0},  # Key 4
        {'note': 73, 'velocity': 120, 'channel': 0},  # Key 5
    ]
}
```

## Hardware

The rotary encoder, keys and potentiometers are set up. The keys are instantiated with `keypad` and the button on the rotary encoder is setup as a debouncer `Button` to allow for long and short press detection.

```python
#  midi note numbers (will be replaced by control_settings)
midi_notes = [key['note'] for key in control_settings['keys']]


#rotary encoder and button
encoder = rotaryio.IncrementalEncoder(board.D3, board.D2)
last_position = 0

#  midi setup
midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=0)

# Create keypad object with the pins (removed encoder button)
keys = keypad.Keys(
    pins=(board.D4, board.D5, board.D6, board.D7, board.D8),
    value_when_pressed=False,  # Buttons pull to ground when pressed
    pull=True  # Enable internal pull-up resistors
)

# Set up encoder button with debouncer
encoder_button_pin = DigitalInOut(board.A3)
encoder_button_pin.direction = Direction.INPUT
encoder_button_pin.pull = digitalio.Pull.UP
encoder_button = Button(encoder_button_pin, value_when_pressed=False, long_duration_ms=500)

#  potentiometer setup
pot_A = AnalogIn(board.A2)
pot_B = AnalogIn(board.A1)
pot_C = AnalogIn(board.A0)
```

## Display and Bitmap Background

The OLED display is connected over I2C. The&nbsp; **main.bmp** image file is loaded in with the `adafruit_imageload` library.

```python
#display setup
i2c = board.STEMMA_I2C()
displayio.release_displays()

# oled
oled_reset = board.D9
display_bus = i2cdisplaybus.I2CDisplayBus(i2c, device_address=0x3D, reset=oled_reset)
WIDTH = 128
HEIGHT = 64
display = adafruit_displayio_ssd1306.SSD1306(display_bus, width=WIDTH, height=HEIGHT)

bitmap, palette = adafruit_imageload.load("/main.bmp",
                                          bitmap=displayio.Bitmap,
                                          palette=displayio.Palette)

# Create a TileGrid to hold the bitmap
bitmap_grid = displayio.TileGrid(bitmap, pixel_shader=palette)
```

Rectangles are used to highlight the different options on the main menu screen. A dictionary is used to hold the positions and dimensions for these rectangles.

```python
# Create main page
maingroup = displayio.Group()

#dictionary for rectangle highlights
rect_dict = [
    {'pos': (0, 0), 'dim': (42, 32)},
    {'pos': (42, 0), 'dim': (42, 32)},
    {'pos': (84, 0), 'dim': (42, 32)},
    {'pos': (1, 32), 'dim': (25, 32)},
    {'pos': (26, 32), 'dim': (25, 32)},
    {'pos': (51, 32), 'dim': (25, 32)},
    {'pos': (76, 32), 'dim': (25, 32)},
    {'pos': (101, 32), 'dim': (25, 32)},
]

current_index = 0
rect_info = rect_dict[current_index]
x, y = rect_info['pos']
w, h = rect_info['dim']

# Create the rectangle (outline only, no fill)
rectangle = Rect(x, y, w, h, fill=None, outline=0xFFFFFF)
```

## Value Text

Each of the keys have a text label, pulled from the&nbsp;`MIDI_NOTE_NAMES` list by passing the assigned MIDI note number as the index.

```python
#text container
keynote_maingroup = displayio.Group()
font = terminalio.FONT
color = 0x000000

keynotes = [
    {'num': control_settings['keys'][0]['note'], 'pos': (14, 42)},
    {'num': control_settings['keys'][1]['note'], 'pos': (39, 42)},
    {'num': control_settings['keys'][2]['note'], 'pos': (64, 42)},
    {'num': control_settings['keys'][3]['note'], 'pos': (89, 42)},
    {'num': control_settings['keys'][4]['note'], 'pos': (114, 42)},
]
keynote_labels = []
for keynote in keynotes:
    keynote_area = label.Label(terminalio.FONT,
                               text=MIDI_NOTE_NAMES[keynote['num']],
                               color=0x000000)
    keynote_area.anchor_point = (0.5, 0.0)
    keynote_area.anchored_position = (keynote['pos'][0], keynote['pos'][1])
    keynote_labels.append(keynote_area)
    keynote_maingroup.append(keynote_area)
maingroup.append(keynote_maingroup)
```

Similarly, there are text labels for the values currently being sent from the potentiometers.

```python
# labels for potentiometers

potval_maingroup = displayio.Group()
potvals = [
    {'num': "0", 'pos': (22, 10)},
    {'num': "0", 'pos': (64, 10)},
    {'num': "0", 'pos': (106, 10)},
]
potvals_labels = []

for potval in potvals:
    potval_area = label.Label(
        terminalio.FONT,
        text=potval['num'],
        color=0x000000,
    )
    potval_area.anchor_point = (0.5, 0.0)
    potval_area.anchored_position = (potval['pos'][0], potval['pos'][1])
    potvals_labels.append(potval_area)
    potval_maingroup.append(potval_area)
maingroup.append(potval_maingroup)
```

## The Edit Menu

The&nbsp;`editgroup` is a second graphics group that holds the graphics elements for editing settings assigned to each key and potentiometer.

```python
# Create edit page
editgroup = displayio.Group()

# Labels for Edit Page
header_area = label.Label(
    terminalio.FONT,
    text="",
    color=0x000000,
    x=0, y=10,
    background_color=0xFFFFFF,
    padding_left=34,
    padding_right=34,
    padding_top=2,
    padding_bottom=2,
    )
header_area.anchor_point = (0.5, 0.0)
header_area.anchored_position = (64, 2)

item1_area = label.Label(terminalio.FONT, text="", color=0xFFFFFF, x=4, y=25,)
item2_area = label.Label(terminalio.FONT, text="", color=0xFFFFFF, x=4, y=38,)
item3_area = label.Label(terminalio.FONT, text="", color=0xFFFFFF, x=4, y=52,)

# Create separate labels for range min and max
range_label = label.Label(terminalio.FONT, text="Range:", color=0xFFFFFF, x=4, y=38,)
range_min_label = label.Label(terminalio.FONT, text="", color=0xFFFFFF, x=65, y=38,)
range_dash_label = label.Label(terminalio.FONT, text="-", color=0xFFFFFF, x=85, y=38,)
range_max_label = label.Label(terminalio.FONT, text="", color=0xFFFFFF, x=95, y=38,)

item_areas = [item1_area, item2_area, item3_area]
edit_rectangle = Rect(0, 0, 128, 14, fill=None, outline=0xFFFFFF)
range_rectangle = Rect(0, 0, 20, 14, fill=0x000000, outline=0xFFFFFF)

editgroup.append(edit_rectangle)
editgroup.append(header_area)
editgroup.append(item1_area)
editgroup.append(item2_area)
editgroup.append(item3_area)
editgroup.append(range_rectangle)
range_rectangle.hidden = True
```

The function `update_edit_display()` is used to update the values in edit mode. There are different text elements when editing the potentiometers versus the keys, so this function handles showing the correct elements when one is selected.

```python
# Function to update edit mode display
def update_edit_display(index):
    # First, remove the range components if they exist in editgroup
    if range_label in editgroup:
        editgroup.remove(range_label)
        editgroup.remove(range_min_label)
        editgroup.remove(range_dash_label)
        editgroup.remove(range_max_label)
    if index &lt; 3:  # Potentiometer
        p = control_settings['pots'][index]
        header_area.text = f"EDIT POT {'ABC'[index]}"
        item1_area.text = f"CC Number:     {p['cc_num']}"
        # Clear item2_area text since we'll use separate labels
        item2_area.text = ""
        # Add range components
        editgroup.append(range_label)
        editgroup.append(range_min_label)
        editgroup.append(range_dash_label)
        editgroup.append(range_max_label)
        range_min_label.text = str(p['range_min'])
        range_max_label.text = str(p['range_max'])
        item3_area.text = f"MIDI Channel:  {p['channel'] + 1}"
    else:  # Button
        k = index - 3
        s = control_settings['keys'][k]
        note_name = MIDI_NOTE_NAMES[s['note']]
        header_area.text = f"EDIT KEY {k + 1}"
        item1_area.text = f"MIDI Note:     {note_name}"
        item2_area.text = f"Velocity:      {s['velocity']}"
        item3_area.text = f"MIDI Channel:  {s['channel'] + 1}"
```

## The Loop

In the loop, the rotary encoder button helps to navigate between the main menu and edit mode. A long press with an element highlighted on the main menu enters edit mode for that potentiometer or key. Short presses select different parameters to change the value of with the rotary encoder.

Outside of menu navigation, two short presses sends the MIDI panic message if you ever experience stuck notes.

```python
while True:
    # Update encoder button state
    encoder_button.update()

    # Handle encoder button presses
    if encoder_button.short_count == 1:
        if not edit_mode:
            # Short press enters edit mode
            update_edit_display(current_index)
            edit_current_index = 0
            edit_rectangle.x = 0
            edit_rectangle.y = item_areas[edit_current_index].y - 7
            edit_active = False
            range_rectangle.hidden = True
            display.root_group = editgroup
            edit_mode = True
        elif edit_mode and edit_active and current_index &lt; 3 and edit_current_index == 1:
            # In range editing mode, short press switches between min/max
            range_edit_selection = 1 - range_edit_selection  # Toggle between 0 and 1
            if range_edit_selection == 0:
                range_rectangle.x = range_min_label.x - 2
            else:
                range_rectangle.x = range_max_label.x - 2
        else:
            # Short press exits edit mode
            display.root_group = maingroup
            edit_mode = False
            edit_active = False
            range_edit_active = False
            edit_rectangle.fill = None
            item_areas[edit_current_index].color = 0xFFFFFF
            if current_index &lt; 3 and edit_current_index == 1:
                range_min_label.color = 0xFFFFFF
                range_max_label.color = 0xFFFFFF
                range_label.color = 0xFFFFFF
                range_dash_label.color = 0xFFFFFF
                range_rectangle.hidden = True
    if encoder_button.short_count == 2:
        # send midi panic
        panic = ControlChange(123, 120)
        #  send CC message
        midi.send(panic)
    
    # Handle long press in edit mode
    if edit_mode and encoder_button.long_press:
        if current_index &lt; 3 and edit_current_index == 1:
            # We're on the Range line
            if not edit_active:
                # Not in range edit mode yet, enter it
                edit_active = True
                range_edit_active = False
                edit_rectangle.fill = 0xFFFFFF
                # Show the range rectangle
                range_rectangle.hidden = False
                range_rectangle.fill = None
                range_rectangle.outline = 0x000000
                range_edit_selection = 0  # Start with min
                # Position range rectangle over the min value
                range_rectangle.x = range_min_label.x - 2
                range_rectangle.y = range_min_label.y - 7
                range_label.color = 0x000000
                range_dash_label.color = 0x000000
                range_max_label.color = 0x000000
                range_min_label.color = 0x000000
            elif edit_active and not range_edit_active:
                # In selection mode, enter value editing mode
                range_edit_active = True
                range_rectangle.fill = 0x000000
                range_min_label.color = 0xFFFFFF
                range_max_label.color = 0xFFFFFF
            elif edit_active and range_edit_active:
                # In value editing mode, exit range editing completely
                edit_active = False
                range_edit_active = False
                edit_rectangle.fill = None
                # Hide range rectangle and restore colors
                range_rectangle.hidden = True
                range_label.color = 0xFFFFFF
                range_dash_label.color = 0xFFFFFF
                range_min_label.color = 0xFFFFFF
                range_max_label.color = 0xFFFFFF
        else:
            # Non-range items - simple toggle
            edit_active = not edit_active
            range_edit_active = False  # Reset range edit active state

            # Change rectangle color to indicate active editing
            if edit_active:
                edit_rectangle.fill = 0xFFFFFF
                # Make text black on white background for other items
                item_areas[edit_current_index].color = 0x000000
            else:
                edit_rectangle.fill = None
                # Make text white
                item_areas[edit_current_index].color = 0xFFFFFF
```

## Keypad

The keys use the&nbsp;`keypad` module. When a key is pressed, a `NoteOn` message with the selected MIDI note and velocity are sent. When the key is released, a matching `NoteOff` message is sent.

```python
event = keys.events.get()

    if event:
        # event.key_number gives you the index (0-4) of which button
        key_index = event.key_number

        if event.pressed:
            # Button was pressed - send NoteOn using settings
            key_settings = control_settings['keys'][key_index]
            midi.send(NoteOn(key_settings['note'], key_settings['velocity']))

        if event.released:
            # Button was released - send NoteOff using settings
            key_settings = control_settings['keys'][key_index]
            midi.send(NoteOff(key_settings['note'], key_settings['velocity']))
```

## Rotary Encoder

The rotary encoder lets you navigate around the main menu. When you turn the encoder, the highlight rectangle is deleted and then recreated with the dimensions and position from the dictionary.

In edit mode, the rotary encoder lets you navigate around the different parameters to edit for each key or potentiometer. When a parameter it selected, it is used to change the value.

```python
position = encoder.position

    # Check if encoder moved
    if position != last_position:
        if not edit_mode:
            # Main menu navigation
            if position &gt; last_position:
                current_index = (current_index + 1) % len(rect_dict)
            else:
                current_index = (current_index - 1) % len(rect_dict)

            rect_info = rect_dict[current_index]
            x, y = rect_info['pos']
            w, h = rect_info['dim']

            # Remove old rectangle and create new one with updated dimensions
            maingroup.remove(rectangle)
            rectangle = Rect(x, y, w, h, fill=None, outline=0xFFFFFF)
            maingroup.append(rectangle)
            last_position = position

        elif edit_mode and not edit_active:
            # Edit mode navigation - cycle through editable items
            # First restore previous item colors
            item_areas[edit_current_index].color = 0xFFFFFF

            if position &gt; last_position:
                edit_current_index = (edit_current_index + 1) % len(item_areas)
            else:
                edit_current_index = (edit_current_index - 1) % len(item_areas)

            # Update rectangle position to highlight current item
            edit_rectangle.x = 0
            edit_rectangle.y = item_areas[edit_current_index].y - 7
            last_position = position

        elif edit_mode and edit_active:
            # Actively editing a value
            direction = 1 if position &gt; last_position else -1

            if current_index &lt; 3:  # Editing potentiometer
                pot_settings = control_settings['pots'][current_index]

                if edit_current_index == 0:  # CC Number
                    pot_settings['cc_num'] = (pot_settings['cc_num'] + direction) % 128
                    item1_area.text = f"CC Number:     {pot_settings['cc_num']}"
                elif edit_current_index == 1:  # Range editing
                    if range_edit_active:
                        # Actually edit the value
                        if range_edit_selection == 0:  # Editing min
                            pot_settings['range_min'] = (pot_settings['range_min'] +
                                                         direction) % 128
                            range_min_label.text = f"{pot_settings['range_min']}"
                        else:  # Editing max
                            pot_settings['range_max'] = (pot_settings['range_max'] +
                                                         direction) % 128
                            range_max_label.text = f"{pot_settings['range_max']}"
                    else:
                        # Special handling for range - switch between min and max
                        if direction &gt; 0 and range_edit_selection == 0:
                            # Moving right from min, switch to max
                            range_edit_selection = 1
                            range_rectangle.x = range_max_label.x - 2
                        elif direction &lt; 0 and range_edit_selection == 1:
                            # Moving left from max, switch to min
                            range_edit_selection = 0
                            range_rectangle.x = range_min_label.x - 2
                elif edit_current_index == 2:  # MIDI Channel
                    pot_settings['channel'] = (pot_settings['channel'] + direction) % 16
                    item3_area.text = f"MIDI Channel:  {pot_settings['channel'] + 1}"

            else:  # Editing key
                key_index = current_index - 3
                key_settings = control_settings['keys'][key_index]

                if edit_current_index == 0:  # MIDI Note
                    key_settings['note'] = (key_settings['note'] + direction) % len(MIDI_NOTE_NAMES)
                    key_note_name = MIDI_NOTE_NAMES[key_settings['note']]
                    item1_area.text = f"MIDI Note:     {key_note_name}"
                    # Update the main screen label too
                    keynote_labels[key_index].text = key_note_name
                elif edit_current_index == 1:  # Velocity
                    key_settings['velocity'] = (key_settings['velocity'] + direction) % 127
                    item2_area.text = f"Velocity:      {key_settings['velocity']}"
                elif edit_current_index == 2:  # MIDI Channel
                    key_settings['channel'] = (key_settings['channel'] + direction) % 16
                    item3_area.text = f"MIDI Channel:  {key_settings['channel'] + 1}"

            last_position = position
```

## Potentiometers

The potentiometer values are mapped to the range defined in the&nbsp;`control_settings` dictionary for each potentiometer. When each potentiometer is turned, it sends a MIDI CC message out.

```python
pot_A_val1 = round(simpleio.map_range(val(pot_A), 65535, 0,
                                                      control_settings['pots'][0]['range_min'],
                                                      control_settings['pots'][0]['range_max']))
    pot_B_val1 = round(simpleio.map_range(val(pot_B), 65535, 0,
                                                      control_settings['pots'][1]['range_min'],
                                                      control_settings['pots'][1]['range_max']))
    pot_C_val1 = round(simpleio.map_range(val(pot_C), 65535, 0,
                                                      control_settings['pots'][2]['range_min'],
                                                      control_settings['pots'][2]['range_max']))

    #  if modulation value is updated...
    if abs(pot_A_val1 - pot_A_val2) &gt; 1:
        #  update pot_A_val2
        pot_A_val2 = pot_A_val1
        #  create integer
        modulation = int(pot_A_val2)
        control_settings['pots'][0]['current_val'] = modulation
        potvals_labels[0].text = str(modulation)
        #  create CC message
        pot_settings = control_settings['pots'][0]
        modWheel = ControlChange(pot_settings['cc_num'], modulation)
        #  send CC message
        midi.send(modWheel)

    if abs(pot_B_val1 - pot_B_val2) &gt; 1:
        #  update pot_B_val2
        pot_B_val2 = pot_B_val1
        #  create integer
        ControllerB = int(pot_B_val2)
        control_settings['pots'][1]['current_val'] = ControllerB
        potvals_labels[1].text = str(ControllerB)
        #  create CC message
        pot_settings = control_settings['pots'][1]
        ControlB = ControlChange(pot_settings['cc_num'], ControllerB)
        #  send CC message
        midi.send(ControlB)

    if abs(pot_C_val1 - pot_C_val2) &gt; 1:
        #  update pot_c_val2
        pot_C_val2 = pot_C_val1
        #  create integer
        ControllerC = int(pot_C_val2)
        control_settings['pots'][2]['current_val'] = ControllerC
        potvals_labels[2].text = str(ControllerC)
        #  create CC message
        pot_settings = control_settings['pots'][2]
        ControlC = ControlChange(pot_settings['cc_num'], ControllerC)
        #  send CC message
        midi.send(ControlC)
```

# USB MIDI Keyset Controller

## Wiring

## Parts

Get the parts ready to wire up. Take a moment to cross check the parts list for this project.

- 1x Rotary Encoder
- 1x 1.3 OLED
- 3x potentiometers
- 1x KB2040
- 5x CHOC key switches
- 5x CHOCK key sockets
- 1x STEMMA QT cable (100mm long)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/886/medium640/3d_printing_parts.jpg?1754661016)

## Wire for Keys

Use the white colored spool of wire to create a set of five wires that are approximately 4.5" inches (115mm) long.

Use the blue colored spool of wire to create four wires that are approximately 1" inch (25mm) long.

Cut a single blue wire to 4.5" inches (115mm) in length.

Use wire strippers to remove a bit of insulation from the tips of each wire then apply a bit of solder to tin them.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/918/medium640/3d_printing_wires-tinned.jpg?1754932036)

## Install Key Switches&nbsp;

Snap the five CHOC key switches into the 3D printed key plate oriented as shown here.

Ensure all of the keys are installed in the same orientation.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/919/medium640/3d_printing_keyplate-sw.jpg?1754932065)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/920/medium640/3d_printing_keyplate-sw-install.jpg?1754932080)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/921/medium640/3d_printing_keyplate-sw-installed.jpg?1754932093)

## Install Key Sockets

Remove the protective cover from the cut tape reel of key sockets.

Insert the key sockets into CHOC key switches being very careful not to bend the pins.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/982/medium640/3d_printing_key-sockets.jpg?1755006201)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/983/medium640/3d_printing_key-sockets-installing.jpg?1755006213)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/984/medium640/3d_printing_key-sockets-installed.jpg?1755006224)

## Solder Wires to Sockets

Use the short blue wires to connect ground pins together on the switches.

Use the white wires to connect to the signal pins on the switches.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/922/medium640/3d_printing_sw-wires-installed.jpg?1754932129)

## Solder Wires to KB2040

Connect the signal wires from the switches to the GPIO pins on the KB2040.

- Key 1 to D4
- Key 2 to D5
- Key 3 to D6
- Key 4 to D7
- Key 5 to D8

Solder the long blue wire to **GND** on the KB2040.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/923/medium640/3d_printing_kb2040-key-wires-solder.jpg?1754932233)

## Solder Wires to Rotary Encoder

Use black, blue, yellow, and white wires to create connections for the rotary encoder.

Cut black, blue, yellow, and white wires to 4 inches (102mm) in length. Then, create a shorter black wire (1.5in/38mm long).

Solder the two black wires to the ground encoder switch pin. Then, solder the shorter black wire to the middle pin of the rotary encoder.

Solder the blue wire to either the far left or right pin. Solder yellow wire to the remaining pin on the rotary encoder.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/924/medium640/3d_printing_rotary-wires-solder-1.jpg?1754932318)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/925/medium640/3d_printing_rotary-wires-solder-2.jpg?1754932342)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/926/medium640/3d_printing_rotary-wires-solder-3.jpg?1754932368)

## Rotary Encoder Wired

Double check the wires on the rotary encoder and ensure the wires have been properly soldered to the pins.

Optionally, use a piece of heat shrink tubing to keep the wires bundled together.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/927/medium640/3d_printing_rotary-wires-soldered.jpg?1754932400)

## Wires for Potentiometers

Use green, black and red colored wires to create connections for the three potentiometers.

Make three green wires, a single black and red wire to be 4.4in (112mm) long.

Make two black and red wires to be 1.5in (38mm) long.

Optionally apply labels to the bottom of the potentiometers.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/928/medium640/3d_printing_pots-wires.jpg?1754932439)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/929/medium640/3d_printing_pots-labels.jpg?1754932480)

## Solder Potentiometers

Start with the first potentiometer ( **A-A2** ) and solder a short and long black wire to the far left pin. Then, solder a longer green wire to the middle pin. Solder a short and long red wire to the remaining pin on the potentiometer.

Solder the short black wire from **Potentiometer A** along with a second short black wire to the far left pin on **Potentiometer B**.&nbsp;Solder a green wire to the middle pin. Solder the short red wire from Potentiometer A along with the second short red wire to the remaining pin.

Solder the short black wire from **Potentiometer B** to the far left pin on **Potentiometer C**. Solder the last green wire to the middle pin. Solder the short red wire from **Potentiometer B** to the remaining pin.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/930/medium640/3d_printing_pot-a-wires-soldered.jpg?1754932503)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/931/medium640/3d_printing_pot-b-wires-soldered.jpg?1754932513)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/932/medium640/3d_printing_pot-c-wires-soldered.jpg?1754932541)

## Wired Potentiometers

Take a moment to ensure all of the wires have been properly soldered to the pins on each potentiometer.

Optionally add a piece of heat shrink tubing to keep the wires bundled together.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/933/medium640/3d_printing_pots-wires-soldered.jpg?1754932580)

## Connect Rotary Encoder to KB2040

Solder the wires from the rotary encoder to the following pins on the KB2040.

- Black wire to Ground
- Blue wire to D2
- Yellow wire to D3
- White wire to A3

Double check all of the wires have been properly soldered to the correct pins on the KB2040.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/934/medium640/3d_printing_kb2040-rotary-presolder.jpg?1754932618)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/935/medium640/3d_printing_kb2040-rotary-solder.jpg?1754932652)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/936/medium640/3d_printing_kb2040-rotary-soldered.jpg?1754932669)

## Connect Pots to KB2040

Solder the wires from the potentiometers to the following pins on the KB2040

- Pot A green wire to A2
- Pot B green wire to A1
- Pot C green wire to A0
- Shared black wire to Ground
- Shared red wire to 3V

Double check all of the wires from the potentiometer have been properly soldered to the correct pins on the KB2040.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/937/medium640/3d_printing_kb2040-pots-presolder.jpg?1754932697)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/939/medium640/3d_printing_kb2040-pots-wires-solder.jpg?1754932734)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/940/medium640/3d_printing_kb2040-pots-soldered.jpg?1754932749)

# USB MIDI Keyset Controller

## Assembly

## Connect STEMMA QT to OLED

Plug in the Stemma QT cable to the right port on the 1.3in OLED.

Place the 1.3in OLED onto the 3D printed cover with the mounting tabs lined up.

Double check the OLED is in the correct orientation.

Peel off the screen cover if it's still there.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/941/medium640/3d_printing_oled-stemma-cable.jpg?1754932819)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/942/medium640/3d_printing_oled-cover-install.jpg?1754932860)

## Secure OLED to Cover

Get four M2.5 x 6mm steel machine screws. Insert the 3D printed washers to each of the screws.

Insert and fasten the four M2.5 steel machine screws to the 3D printed cover. Be careful not to over tighten them.&nbsp;

Double check the 1.3in OLED has been properly secured to the 3D printed cover.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/943/medium640/3d_printing_oled-screw-washers.jpg?1754932889)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/944/medium640/3d_printing_oled-fasten-cover.jpg?1754932918)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/945/medium640/3d_printing_oled-cover-secured.jpg?1754932930)

## Secure Pots to Cover

Start by positioning the three potentiometers with the 3D printed cover taking note of the order starting with potentiometer A on the left, B then C on the far right.

Insert each pot into their respected mounting holes on the 3D printed cover, taking note the order is reversed from the other side.

Insert and fasten the washers to secure each potentiometer to the cover. Use a socket driver or pair of pliers to securely tighten the washers.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/946/medium640/3d_printing_pots-cover-preinstall.jpg?1754932963)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/947/medium640/3d_printing_pots-cover-installing.jpg?1754932990)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/948/medium640/3d_printing_pots-cover-secure.jpg?1754933013)

## Install Knobs

Start by rotating the shaft of each potentiometer so they're positioned all the way to the far left.

Make sure the set screw on each knob is loose enough to allow it to seat onto the shaft of the potentiometer.

Place the three knobs onto the shaft of each potentiometer with the marking matching the position in the photo.&nbsp;

Use the proper sized hex screw driver bit to fasten and secure the knobs to the potentiometers.&nbsp;

![](https://cdn-learn.adafruit.com/assets/assets/000/138/949/medium640/3d_printing_pots-knobs.jpg?1754933031)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/950/medium640/3d_printing_pots-knobs-fasten.jpg?1754933060)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/951/medium640/3d_printing_pots-knobs-secured.jpg?1754933087)

## Install Rotary Encoder to Cover

Insert the rotary encoder into the respective mounting hole on the 3D printed cover.

Insert and fasten the washer onto the shaft of the rotary encoder. Use a pair of pliers to tighten and secure the rotary encoder to the cover.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/952/medium640/3d_printing_rotary-cover-install.jpg?1754933119)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/953/medium640/3d_printing_rotary-cover-fasten.jpg?1754933134)

## Install Knob to Rotary Encoder

Get the 3D printed knob and orient it with the shaft of the rotary encoder.

Firmly press the knob onto the shaft of the rotary encoder. It should be able to press to click the built-in button switch.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/954/medium640/3d_printing_rotary-knob-installing.jpg?1754933152)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/955/medium640/3d_printing_rotary-knob-installed.jpg?1754933171)

## Install KB2040 to PCB Mount

Orient the KB2040 with the 3D printed PCB mount.

Insert the KB2040 into the mount with the edge of the PCB fitted under the corner clips. Slightly bend the PCB mount to fit the other side of the PCB so it fits under the clips.

Double check the KB2040 is properly secured to the 3D printed PCB mount.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/956/medium640/3d_printing_kb2040-mount-preinstall.jpg?1754933210)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/957/medium640/3d_printing_kb2040-mount-installing.jpg?1754933245)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/958/medium640/3d_printing_kb2040-mount-installed.jpg?1754933257)

## Secure PCB mount to Base

Place the PCB mount with the KB2040 onto the base with the mounting tabs line up with the two mounting holes.

Insert and fasten two M3 x 6mm long machine screws.

Use two M3 hex nuts to secure the parts together.

Double check the PCB mount is properly secured to the 3D printed enclosure base.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/959/medium640/3d_printing_kb2040-mount-base-installing.jpg?1754933287)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/960/medium640/3d_printing_kb2040-mount-base-fasten.jpg?1754933303)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/961/medium640/3d_printing_kb2040-mount-base-secured.jpg?1754933317)

## Connect OLED to KB2040

Grab the STEMMA QT cable from the OLED and plug it into the KB2040.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/962/medium640/3d_printing_kb2040-oled-connect.jpg?1754933350)

## Install Hinge Rest

Place the 3D printed hinge rest onto the dedicated spot on the enclosure base. The part is symmetrical so the orientation isn't critical and the mounting holes line up properly.

Insert and fasten three M3 x 6mm long screws to secure the hinge rest to the enclosure base.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/963/medium640/3d_printing_base-hinge-install.jpg?1754933388)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/964/medium640/3d_printing_base-hinge-fasten.jpg?1754933422)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/965/medium640/3d_printing_base-hinge-secured.jpg?1754933462)

## Prep Keys

Grab the five keys and the dowel. Insert the dowel through the bores that are built into the keys. The dowel should freely slide into each key.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/966/medium640/3d_printing_key-dowel-install.jpg?1754933649)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/967/medium640/3d_printing_keys-dowel-installed.jpg?1754933670)

## Install Keys to Hinge Rest

Place the set of keys onto the hinge rest with each key fitting into the segmented cavities. Space out the 3D printed keys so they seat into their respective spots.

Press the set keys down so the dowel is fully seated into the channel in the hinge rest.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/968/medium640/3d_printing_keyset-base-install.jpg?1754933700)

## Install Key Plate to Base

Place the key plate onto the tabs built into the base with the mounting holes lined up and in the correct orientation.

Insert and fasten two M3 x 6mm long screws to secure the key plate to the base.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/969/medium640/3d_printing_keyplate-base-install.jpg?1754933727)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/970/medium640/3d_printing_keyplate-base-fasten.jpg?1754933756)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/971/medium640/3d_printing_keyplate-base-secured.jpg?1754933775)

## Test Keys

Take a moment to press down on each 3D printed key. Each key should be able to hinge and actuate the CHOC key switches.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/972/medium640/3d_printing_keys-press-test.jpg?1754933796)

## Install Cover

Begin to fit the 3D printed cover onto the base enclosure. Place the cover over the key plate and make sure all of the wires stay inside the enclosure.

Firmly press the cover onto the base with the nubs on the base fitting into the mounting points on the cover. They should click into plate.

Double check any wires are not outside of the cover and base.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/973/medium640/3d_printing_cover-base-installing.jpg?1754933817)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/974/medium640/3d_printing_cover-base-install.jpg?1754933832)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/975/medium640/3d_printing_cover-base-secured.jpg?1754933847)

## Test Knobs and Keys

Take a moment to press all keys and turn all knobs.

The three potentiometers should freely rotary.

The rotary encoder should be able to press down to click and freely rotate.

The five keys should be able to be pressed and actuate the CHOC switches.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/976/medium640/3d_printing_key-knob-test.jpg?1754933875)

## USB Connect

Plug in a USB-C cable to the KB2040 on the side of the enclosure. Connect the other end of the cable to a computer or mobile device.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/977/medium640/3d_printing_usb-power.jpg?1754933893)

## Final Build

Congratulations on your build!

Take a moment to test all of the components. Reference the Usage page for detailed instructions on how to use the interface.

![](https://cdn-learn.adafruit.com/assets/assets/000/138/978/medium640/3d_printing_final-1.jpg?1754933911)

![](https://cdn-learn.adafruit.com/assets/assets/000/138/979/medium640/3d_printing_final-2.jpg?1754933921)

# USB MIDI Keyset Controller

## Usage

![The MIDI Keyset, fully assembled with the select knob on the left next to a small display and three tuning knobs on the right, all above the five keys below.](https://cdn-learn.adafruit.com/assets/assets/000/139/059/medium800/3d_printing_final-1.jpg?1755101077 The MIDI Keyset displaying the main page.)

## Connect MIDI Controller to Device

As a USB MIDI controller, you'll need to connect the MIDI Keyset to a computer or mobile device that supports music software with MIDI input capabilities.

DAW (Digital Audio Workstation) software such as Ableton, Garageband, Reason, etc. will support USB MIDI controllers out of the box.

Each music software will differ so this guide won't cover any specifics on how to create software instruments or assign/mapping MIDI CC.

![](https://cdn-learn.adafruit.com/assets/assets/000/139/064/medium640/3d_printing_hero-mobile.jpg?1755101574)

## Main Menu

On boot, the OLED will display the main menu. The top three circles represent the three potentiometers. The five rectangles represent the five key switches.&nbsp;

Values from the potentiometer are overlaid on the three circles. These values will update whenever the potentiometers are adjusted. The values represent the MIDI CC (Control Change) values ranging from 0–127.

MIDI note names are overlaid on the five rectangles. These labels represent the assigned MIDI note to each key. They range from C-2 – B9.

## Menu Navigation

Turn the rotary encoder to highlight different elements on the main page.

![](https://cdn-learn.adafruit.com/assets/assets/000/139/057/medium640thumb/3d_printing_usage-main.jpg?1755095370)

## Edit Page

Short press the rotary encoder to enter the edit page on the selected item. A title at the top will display the name of the component that is being edited.

Rotate the rotary encoder to highlight items within the edit page.&nbsp;

Long press the rotary encoder to enter edit mode on the highlighted item. The text will display a white background to indicate it is in edit mode.

While in edit mode, rotate the rotary encoder to change the value.

Long press the rotary encoder to save the change, Or, short press to save and exit the edit mode and return to the main menu.

![](https://cdn-learn.adafruit.com/assets/assets/000/139/060/medium640thumb/3d_printing_GUI-demo.jpg?1755095836)


## Featured Products

### Adafruit KB2040 - RP2040 Kee Boar Driver

[Adafruit KB2040 - RP2040 Kee Boar Driver](https://www.adafruit.com/product/5302)
A wild Kee Boar appears! It’s a shiny **KB2040**! An Arduino Pro Micro-shaped board for Keebs with RP2040. (#keeblife 4 evah) A lot of folks like using Adafruit parts for their Keeb builds – but with the ItsyBitsy not being pin-compatible with the Pro Micro pinout, it...

In Stock
[Buy Now](https://www.adafruit.com/product/5302)
[Related Guides to the Product](https://learn.adafruit.com/products/5302/guides)
### Monochrome 1.3" 128x64 OLED graphic display - STEMMA QT / Qwiic

[Monochrome 1.3" 128x64 OLED graphic display - STEMMA QT / Qwiic](https://www.adafruit.com/product/938)
These displays are small, only about 1.3" diagonal, but very readable due to the high contrast of an OLED display. This display is made of 128x64 individual white OLED pixels, each one is turned on or off by the controller chip. Because the display makes its own light, no backlight is...

In Stock
[Buy Now](https://www.adafruit.com/product/938)
[Related Guides to the Product](https://learn.adafruit.com/products/938/guides)
### Rotary Encoder + Extras

[Rotary Encoder + Extras](https://www.adafruit.com/product/377)
This rotary encoder is the best of the best, it's a high-quality 24-pulse encoder, with detents and a nice feel. It is panel mountable for placement in a box, or you can plug it into a breadboard (just cut/bend the two mechanical side tabs.) We also include a nice soft-touch knob with an...

In Stock
[Buy Now](https://www.adafruit.com/product/377)
[Related Guides to the Product](https://learn.adafruit.com/products/377/guides)
### Kailh CHOC Low Profile White Clicky Key Switches

[Kailh CHOC Low Profile White Clicky Key Switches](https://www.adafruit.com/product/5114)
For crafting your very own custom keyboard, these **&nbsp;Kailh Choc Low Profile Clicky White&nbsp;mechanical key switches** &nbsp;are super slim, with ultra-low profile compared to MX compatible switches!

Please note these are&nbsp; **NOT MX...**

In Stock
[Buy Now](https://www.adafruit.com/product/5114)
[Related Guides to the Product](https://learn.adafruit.com/products/5114/guides)
### Switch Sockets for Kailh CHOC Compatible Keys - 10 Pack

[Switch Sockets for Kailh CHOC Compatible Keys - 10 Pack](https://www.adafruit.com/product/5118)
Back in _my_ day, if you wanted to make a mechanical keyboard, you had to solder in expensive key switches onto your PCB - and woe to the person who decided they wanted to change their linear keys instead of clicky ones! Bah humbug, kids these days have no idea, what with their...

In Stock
[Buy Now](https://www.adafruit.com/product/5118)
[Related Guides to the Product](https://learn.adafruit.com/products/5118/guides)
### Panel Mount 10K potentiometer (Breadboard Friendly)

[Panel Mount 10K potentiometer (Breadboard Friendly)](https://www.adafruit.com/product/562)
This potentiometer is a two-in-one, good in a breadboard or with a panel. It's a fairly standard linear taper 10K ohm potentiometer, with a grippy shaft. It's smooth and easy to turn, but not so loose that it will shift on its own. We like this one because the legs are 0.2" apart...

In Stock
[Buy Now](https://www.adafruit.com/product/562)
[Related Guides to the Product](https://learn.adafruit.com/products/562/guides)
### White Micro Potentiometer Knob - 4 pack

[White Micro Potentiometer Knob - 4 pack](https://www.adafruit.com/product/5538)
Have you found yourself in a maze of twisty-turny knobs, all alike? These micro potentiometer&nbsp;(or rotary encoder) knobs offer a rainbow burst of color to your synth or control panel. They are made of a hard plastic, with a grippy star shape. The shape is sometimes referred to as...

In Stock
[Buy Now](https://www.adafruit.com/product/5538)
[Related Guides to the Product](https://learn.adafruit.com/products/5538/guides)
### STEMMA QT / Qwiic JST SH 4-pin Cable - 100mm Long

[STEMMA QT / Qwiic JST SH 4-pin Cable - 100mm Long](https://www.adafruit.com/product/4210)
This 4-wire cable is a little over 100mm / 4" long and fitted with JST-SH female 4-pin connectors on both ends. Compared with the chunkier JST-PH these are 1mm pitch instead of 2mm, but still have a nice latching feel, while being easy to insert and remove.

<a...></a...>

Out of Stock
[Buy Now](https://www.adafruit.com/product/4210)
[Related Guides to the Product](https://learn.adafruit.com/products/4210/guides)

## Related Guides

- [Adafruit KB2040](https://learn.adafruit.com/adafruit-kb2040.md)
- [I2C Addresses and Troublesome Chips](https://learn.adafruit.com/i2c-addresses.md)
- [Fisher-Price USB Controller](https://learn.adafruit.com/fisher-price-usb-controller.md)
- [Super Nintendo USB Controller](https://learn.adafruit.com/super-nintendo-usb-controller.md)
- [How to Choose a Microcontroller](https://learn.adafruit.com/how-to-choose-a-microcontroller.md)
- [PB Gherkin 30% keyboard with KMK, CircuitPython, & KB2040](https://learn.adafruit.com/pb-gherkhin-30-keyboard-with-kmk-circuitpython-kb2040.md)
- [CircuitPython Powered Sip & Puff with ST LPS33HW Pressure Sensor](https://learn.adafruit.com/st-lps33-and-circuitpython-sip-and-puff.md)
- [Rotary Phone Dial Keypad](https://learn.adafruit.com/rotary-phone-dial-keypad.md)
- [MIDI for Makers](https://learn.adafruit.com/midi-for-makers.md)
- [MIDI Melody Maker](https://learn.adafruit.com/midi-melody-maker.md)
- [Stepper Motor Turntable](https://learn.adafruit.com/stepper-motor-turntable.md)
- [Planetary Gear Dreidels](https://learn.adafruit.com/planetary-gear-dreidels.md)
- [Toddler Timer](https://learn.adafruit.com/toddler-timer.md)
- [CircuitPython OLED and Dual Knob Sketcher](https://learn.adafruit.com/circuitpython-oled-knob-sketcher.md)
- [Arcade Fightstick](https://learn.adafruit.com/arcade-fightstick.md)
