Build this sliders-and-keyswitches input device to speed up your photo color grading workflow!

Adobe Lightroom Classic can be controlled via MIDI messages, giving you quick access to essential processing controls. It's a snap to adjust fundamental photo developing parameters such as exposure, color temperature, contrast, and saturation with the handy sliders. Use the keys to swap photos, and pull up the crop and heal tools.

Or, use it for any MIDI-capable application or host device, such as synthesizers, mixers, and more.

This project can be expanded to use more buttons and sliders by adding additional STEMMA QT boards with unique addresses, then making adjustments to the code.
MIDI2LR allows color grading via the Neocontroller


Angled shot of black rectangular microcontroller "Feather RP2040"
A new chip means a new Feather, and the Raspberry Pi RP2040 is no exception. When we saw this chip we thought "this chip is going to be awesome when we give it the Feather...
In Stock

4ea. NeoSlider

Long rectangular breakout board with a slide potentiometer changing colors as a hand moves up and down.
Our family of I2C-friendly user interface elements grows by one with this new product that makes it plug-n-play-easy to add a 75mm long slide potentiometer to any microcontroller or...
In Stock
Top view video of a fully assembled NeoKey 1x4 QT I2C with switches and smoke gray keycaps powered by a QT Py on a breadboard. A hand reaches down to press the keys, which emit rainbow colors.
The only thing better than a nice mechanical key is, perhaps, FOUR mechanical keys that also can glow any color of the rainbow - and that's what the Adafruit...
In Stock

4ea. Keycaps

Angled shot of single black 1U R4 lamp keycap.
These stark and elegant Black Windowed Lamp R4 Keycaps are made of opaque black plastic with a small cutout in the south-center edge that wraps around the top and...
In Stock
Angled shot of STEMMA QT / Qwiic JST SH 4-pin Cable.
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...
In Stock
Black Nylon Screw and Stand-off Set with M2.5 Threads, kit box
Totaling 380 pieces, this M2.5 Screw Set is a must-have for your workstation. You'll have enough screws, nuts, and hex standoffs to fuel your maker...
In Stock
Opened box showing many nylon screws
Totaling 420 pieces, this M3 Screw Set is a must-have for your workstation. You'll have enough screws, nuts, and hex standoffs to fuel...
Out of Stock
LED RGB matrix 12" x 12" with "Adafruit Industries LED Matrix" text showing, and LED acrylic slowly covering to make it nicely diffused
A nice whoppin' slab of some lovely black acrylic to add some extra diffusion to your LED Matrix project. This material is 2.6mm (0.1") thick and is made of special cast...
In Stock
Pack of 4 Little Rubber Bumper Feet
Keep your electronics from going barefoot, give them little rubber feet! These small sticky bumpers are our favorite accessory for any electronic kit or device. They are sticky, but...
In Stock

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.

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

Save it wherever is convenient for you.

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.

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

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

Safe Mode

You want to edit your 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 (where you can set CIRCUITPY read-only or turn it off completely). Second, it does not run the code in 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.

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.

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 file. To get everything you need, click on the Download Project Bundle link below, and uncompress the .zip file.

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

# SPDX-FileCopyrightText: 2021 john park for Adafruit Industries
# SPDX-License-Identifier: MIT
# NeoController: NeoSlider(x4) + NeoKey MIDI input device

import board
import busio
from adafruit_simplemath import map_range
import usb_midi
import adafruit_midi
from adafruit_midi.control_change import ControlChange
from adafruit_midi.note_on import NoteOn
from adafruit_midi.note_off import NoteOff
from adafruit_seesaw.seesaw import Seesaw
from adafruit_seesaw import neopixel
from adafruit_seesaw.analoginput import AnalogInput
from adafruit_neokey.neokey1x4 import NeoKey1x4

i2c = busio.I2C(board.SCL, board.SDA)  # for QT Py RP2040, use SCL1/SDA1

# --- NeoSlider object setup
addresses = (0x30, 0x31, 0x32, 0x33)  # cut jumpers to proper addreses (none, A0, A1, A0+A1)
neosliders = []  # list to hold neoslider objects
for address in addresses:  # create each neoslider w proper address
    temp_neosliders = Seesaw(i2c, address)
    neosliders.append(temp_neosliders)  # append to the list

# --- NeoSlider analog read setup
analogin_pin = 18  # slider is on this analog pin
analog_pins = (0, 1)
analog_inputs = []
for n in range(len(neosliders)):
    temp_analog_in = AnalogInput(neosliders[n], analogin_pin)

# --- Slider NeoPixels Setup
PIX_PIN = 14
pixels = []
for p in range(len(neosliders)):
    temp_pix = neopixel.NeoPixel(neosliders[p], PIX_PIN, PIX_NUM)
    pixels[p].brightness = 1.0
    pixels[p].fill((20, 20, 0))

# --- NeoKey 1x4 Setup --- #
neokey = NeoKey1x4(i2c, addr=0x38)
amber = 0x300800
blue = 0x002040
red = 0x900000

keys = [  # neokey (adjust if multiple sets), key number, keypress color, MIDI note
    (neokey, 0, blue, 40),
    (neokey, 1, red, 42),
    (neokey, 2, blue, 43),
    (neokey, 3, red, 46),

#  states for key presses
key_states = [False, False, False, False]

# --- MIDI setup
midi = adafruit_midi.MIDI(
cc_numbers = [48, 49, 50, 51]  # pick the CC number for each slider to send over

last_sliders = [0] * len(neosliders)  # list to hold state variables

while True:
    # check NeoKeys
    for k in range(len(keys)):
        neokey, key_number, color, midi_note = keys[k]
        if neokey[key_number] and not key_states[key_number]:
            midi.send(NoteOn(midi_note, 127))
            key_states[key_number] = True
            neokey.pixels[key_number] = color
        if not neokey[key_number] and key_states[key_number]:
            midi.send(NoteOff(midi_note, 0))
            neokey.pixels[key_number] = amber
            key_states[key_number] = False

    # check sliders
    for i in range(len(neosliders)):
        slider = analog_inputs[i].value
        if abs(slider - last_sliders[i]) > 1:
            cc_val = int(map_range(slider, 0, 1023, 0, 127))
            midi.send(ControlChange(cc_numbers[i], cc_val))
            color_val = int(map_range(slider, 0, 1023, 5, 255))
            pixels[i].fill((color_val, color_val, 0))
            last_sliders[i] = slider

Customize Settings

The code allows you to customize the MIDI note values and CC numbers. To change the notes the keyswitches send, change the fourth value of the items in this list:

keys = [  # neokey (adjust if multiple sets), key number, keypress color, MIDI note
    (neokey, 0, blue, 40),
    (neokey, 1, red, 42),
    (neokey, 2, blue, 43),
    (neokey, 3, red, 46),

In order to send the 0-127 values over different CC numbers when you move the NeoSliders, adjust this list:

cc_numbers = [48, 49, 50, 51]  # pick the CC number for each slider to send over

Set Addresses

To use four NeoSliders on one I2C bus, each board needs a unique address. You will cut the traces on three of the boards to set them.

Set them to:

  • 0x30 (default, don't cut any traces)
  • 0x31 -- cut trace A0
  • 0x32 -- cut trace A1
  • 0x33 -- cut traces A0 and A1

More info is available here.

The NeoKey 1x4 has a default I2C address of 0x30 so to prevent a conflict, you can solder the A3 jumper closed, changing it to address 0x38 as shown here.

NeoKey Prep

Insert the keyswitches through the keyplate (3D printing file linked above), minding the orientation so they'll fit into the hot swap sockets. Position them so the "window" in the switch is above the reverse-mount NeoPixels.

Then, carefully align the legs with the NeoKey board and insert them all at once, squeezing with even pressure.

Connect the Boards

Connect the Feather, NeoKey, and NeoSliders as shown using the STEMMA QT cables.

Cut the Enclosure

Use the linked .ai file (it is the blue .zip file above) to cut the case parts from acrylic. You can do this on a laser cutter or CNC machine, by hand with a saw and drill (print the .ai as a template), or send the .ai file to a service bureau for cutting. You can also use the .pdf file that includes dimensions for other cutting methods.

Connect the Base

Use the M2.5 screws and nuts to secure the NeoSliders to the case bottom as shown.

NeoKey Mounting

Mount the NeoKey onto the screws as shown, by threading the nuts on first, and then sliding the board and keyplate holes over the screws. This places the board at a good height and it will stay put in use, no need for longer screws or top nuts.

Mount the Feather

Use short screws and nuts to mount the feather board as shown, with the USB port facing the back of the board, over the notched cutout.

Top Plate

Mount the M3 standoffs from the bottom with short M3 screws.

Place the top plate over the controls, then secure with four more screws.


Place the four fader caps and keycaps onto their respective controls.


Stick four adhesive bump-on feet under the enclosure to prevent sliding.

Plug in USB and you're ready to go!

Use the Neocontroller

Plug in the Neocontroller to your computer over USB-A to USB-C data/power cable. For typical MIDI use, simply select the Feather RP2040 MIDI device. To use with Lightroom Classic, first download and install the free MIDI2LR plug-in as shown here.

There are plenty of good setup and configuration videos out there, such as this.

Here it is in action:

This guide was first published on Dec 19, 2021. It was last updated on Jul 18, 2024.