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
Click Download Project Zip below to get the full project with all the files

Below is a snapshot of what CIRCUITPY should look like.

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

This guide was first published on Dec 12, 2018. It was last updated on Dec 12, 2018.

This page (Code) was last updated on Sep 14, 2023.

Text editor powered by tinymce.