Text Editor

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

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

CircuitPython Installation

First make sure you are running the latest version of Adafruit CircuitPython for your board.

Project Files

Download the Project Bundle linked below and copy them onto your CIRCUITPY drive.

Libraries and code.py

You'll need to download the .zip file in the Download Project Bundle button below and unzip the file to get all of the needed files.

Copy the lib directory as well as the code.py file to your Feather's CIRCUITPY drive.

# SPDX-FileCopyrightText: 2022 John Park & @todbot Tod Kurt for Adafruit Industries
# SPDX-License-Identifier: MIT
# NeoTrellis 8x8 MIDI Trigger Sequencer
# Does bi-directional MIDI with VCV Rack + Stoermelder MIDI-CAT module
# (can also work with with other apps, such as DAWs that send MIDI for LED controll)
# This is a simplified version of https://github.com/PatchworkBoy/TrowasoftControl

# Requirements:
#  -VCV Rack 2, w/ MIDI-CAT
#  -sequencer such as Trowa TrigSeq or Count Modula
#  -MIDI mappings on pads on "momentary"

import time
import board
import busio
from adafruit_neotrellis.neotrellis import NeoTrellis
from adafruit_neotrellis.multitrellis import MultiTrellis
import usb_midi
import adafruit_midi
from adafruit_midi.note_on import NoteOn
from adafruit_midi.note_off import NoteOff

note_base = 36
note_vel = 127
pad_midi_channel = 0  # add one for "real world" MIDI channel number, e.g. 0=1
led_midi_channel = 2  # see ^
pad_ager_secs = 0.1  # how many seconds to wait for MIDI-CAT to fail
pad_ager_secs_max = 1.0  # seconds max to wait for MIDI-CAT before giving up

# holds whether pad is currently lit or not, when it was pressed before being ack'd
# tuple of (pad lit?, pad_press_time_or_zero_if_ackd)
pad_states = [(False,0)] * 64

midi_usb = adafruit_midi.MIDI( midi_in=usb_midi.ports[0],
                               midi_out=usb_midi.ports[1] )

i2c = busio.I2C(board.SCL, board.SDA)
trelli = [  # adjust these to match your jumper settings if needed
     [NeoTrellis(i2c, False, addr=0x2E), NeoTrellis(i2c, False, addr=0x2F)],
     [NeoTrellis(i2c, False, addr=0x30), NeoTrellis(i2c, False, addr=0x31)]
]
trellis = MultiTrellis(trelli)

OFF = 0x000000
RED = 0x100000
YELLOW = 0x100c00
GREEN = 0x000c00
CYAN = 0x000303
BLUE = 0x000010
PURPLE = 0x130010

colors = [OFF, RED, YELLOW, GREEN, CYAN, BLUE, PURPLE]

color_table = [  # you can make custom color sections for clarity
  1, 1, 1, 1, 5, 5, 5, 5,
  1, 1, 1, 1, 5, 5, 5, 5,
  1, 1, 1, 1, 5, 5, 5, 5,
  1, 1, 1, 1, 5, 5, 5, 5,
  4, 4, 4, 4, 6, 6, 6, 6,
  4, 4, 4, 4, 6, 6, 6, 6,
  4, 4, 4, 4, 6, 6, 6, 6,
  4, 4, 4, 4, 6, 6, 6, 6
]

# convert an x,y (0-7,0-7) to 0-63
def xy_to_pos(x,y):
    return x+(y*8)

# convert 0-63 to x,y
def pos_to_xy(pos):
    return (pos%8, pos//8)

# callback when pads are pressed
def handle_pad(x, y, edge):
    pos = xy_to_pos(x,y)
    note_val = pos + note_base
    if edge == NeoTrellis.EDGE_RISING:
        (pad_on, pad_time) = pad_states[pos] # get pad state & press time
        print(pad_time)
        pad_on = not pad_on                  # toggle state
        pad_states[pos] = (pad_on, time.monotonic()) # and save it w/ new press time
        print("handle_pad: x,y,pos",x,y, pos, note_val, pad_on)
        if pad_on:
            noteon = NoteOn(note_val, note_vel, channel=pad_midi_channel)
            midi_usb.send(noteon)
        else:
            noteoff = NoteOff(note_val, note_vel, channel=pad_midi_channel)
            midi_usb.send(noteoff)

# called periodically in main loop to receive MIDI msgs
# saves to pad_state with LED on/off and 0 to indicate pad press acknowledgement
def midi_receive():
    msg_in = midi_usb.receive()
    if msg_in is None:
        return
    #print("msg_in:", msg_in.channel, msg_in.note, msg_in.velocity)
    if msg_in.channel == led_midi_channel:
        pos = msg_in.note - note_base
        x,y = pos_to_xy(pos)
        if isinstance(msg_in, NoteOn):
            print("midi_receive: LED ON  ",pos, x,y)
            pad_states[pos] = (True, 0)  # save pad state with press time 0 = acknowledgdd
            trellis.color(x,y, colors[color_table[pos]])
        if isinstance(msg_in, NoteOff):
            print("midi_receive: LED OFF ",pos, x,y)
            pad_states[pos] = (False, 0) # save pad state with press time 0 = acknowledged
            trellis.color(x,y, 0x000000)

# attempts to fix the MIDI-CAT bug by retoggling a pad if no LED msg sent
def send_pad_toggle(pad_on,pos):
    print("send_pad_toggle",pad_on,pos)
    note_val = pos + note_base
    noteon = NoteOn(note_val, note_vel, channel=pad_midi_channel)
    noteoff = NoteOff(note_val, note_vel, channel=pad_midi_channel)
    if pad_on:
        midi_usb.send(noteoff)
        midi_usb.send(noteon)
    else:
        midi_usb.send(noteon)
        midi_usb.send(noteoff)

# scan list of pads, if any haven't been acknowledged with LED msgs, toggle them
def pad_ager():
    for i in range(len(pad_states)):
        (pad_on, pad_time) = pad_states[i]
        if (pad_time > 0 and
            time.monotonic() - pad_time > pad_ager_secs and
            time.monotonic() - pad_time < pad_ager_secs_max):
            send_pad_toggle(pad_on,i)

# set up actions for each pad and do a startup light show
for y_pad in range(8):
    for x_pad in range(8):
        trellis.activate_key(x_pad, y_pad, NeoTrellis.EDGE_RISING)
        trellis.activate_key(x_pad, y_pad, NeoTrellis.EDGE_FALLING)
        trellis.set_callback(x_pad, y_pad, handle_pad)
        trellis.color( x_pad, y_pad, PURPLE)
        time.sleep(0.005)

for y_pad in range(8):  # finish light show
    for x_pad in range(8):
        trellis.color(x_pad, y_pad, OFF)
        time.sleep(0.005)

print("Ready. Be sure VCV Rack2 w/ MIDI-CAT is running and configured")
last_debug_time = 0
while True:
    trellis.sync()
    midi_receive()
    pad_ager()

    if time.monotonic() - last_debug_time > 5.0:
        last_debug_time = time.monotonic()
        # print("pads:", ''.join(['1' if i else '0' for (i,t) in pad_states]))

Next, you set up VCV Rack to work with the NeoTrellis 8x8 MIDI Feedback Controller.

This guide was first published on Jun 22, 2022. It was last updated on Mar 26, 2024.

This page (Code the Controller) was last updated on Mar 26, 2024.

Text editor powered by tinymce.