We'll be using CircuitPython for this project. Are you new to using CircuitPython? No worries, there is a full getting started guide here.
Adafruit suggests using the Mu editor to edit your code and have an interactive REPL in CircuitPython. You can learn about Mu and its installation in this tutorial.
There's a guide to get you up and running with CircuitPython specifically for the NeoTrellis M4. You should read it before starting to get the most recent CircuitPython build for the NeoTrellisM4 installed and running along with the required libraries.
Full code with links to github is on the Downloads page.
Navigating the NeoTrellis
To get your NeoTrellis M4 set up to run this project's code, first follow these steps:
1) Update the bootloader for NeoTrellis from the NeoTrellis M4 guide
2) Install the latest CircuitPython for NeoTrellis from the NeoTrellis M4 guide
3) Get the latest CircuitPython library pack, the version number of which should match your version of CircuitPython (4.x or greater), unzip it, and drag the libraries you need over into the /lib folder on CIRCUITPY. The latest library package includes support for NeoTrellis.
https://github.com/adafruit/Adafruit_CircuitPython_Bundle/releases/
For this project you will need the following libraries:
- adafruit_trellism4.mpy
- neopixel.mpy
- adafruit_matrixkeypad.mpy
Overall Structure
The codebase is broken into several classes/files:
- main.py sets everything up and contains the main loop the checks for input from the user and acts accordingly
- parser.py contains the MIDI file parser
- header.py contains a class implementing the MIDI file header structure
- events.py contains several classes representing the various events that can be present in the MIDI files
- sequencer.py contains the sequencer which takes a MIDI track (a temporally ordered list of events) and executes them
- synth.py contains the Synthesizer class that plays samples as required
Below is a snapshot of what CIRCUITPY should look like.

# SPDX-FileCopyrightText: 2018 Dave Astels for Adafruit Industries # # SPDX-License-Identifier: MIT """ NeoTrellis M4 Express MIDI synth Adafruit invests time and resources providing this open source code. Please support Adafruit and open source hardware by purchasing products from Adafruit! Written by Dave Astels for Adafruit Industries Copyright (c) 2018 Adafruit Industries Licensed under the MIT license. All text above must be included in any redistribution. """ import os import parser import sequencer import synth import adafruit_trellism4 trellis = adafruit_trellism4.TrellisM4Express(rotation=0) trellis.pixels.brightness = 0.1 trellis.pixels.fill(0) syn = synth.Synth() seq = sequencer.Sequencer(syn) p = parser.MidiParser() voices = sorted([f.split('.')[0] for f in os.listdir('/samples') if f.endswith('.txt') and not f.startswith('.')]) print('Voices found: ', voices) tunes = sorted([f for f in os.listdir('/midi') if f.endswith('.mid') and not f.startswith('.')]) print('Midi files found: ', tunes) selected_voice = None def reset_voice_buttons(): for i in range(len(voices)): trellis.pixels[(i, 0)] = 0x0000FF def reset_tune_buttons(): for i in range(len(tunes)): trellis.pixels[(i % 8, (i // 8) + 1)] = 0x00FF00 current_press = set() reset_voice_buttons() reset_tune_buttons() while True: pressed = set(trellis.pressed_keys) just_pressed = pressed - current_press for down in just_pressed: if down[1] == 0: if down[0] < len(voices): # a voice selection selected_voice = down[0] reset_voice_buttons() trellis.pixels[down] = 0xFFFFFF syn.voice = voices[selected_voice] else: tune_index = (down[1] - 1) * 8 + down[0] if tune_index < len(tunes) and selected_voice is not None: trellis.pixels[down] = 0xFFFFFF header, tracks = p.parse('/midi/' + tunes[tune_index]) for track in tracks: seq.play(track) reset_tune_buttons() current_press = pressed