Once you've finished setting up your Feather M4 Express 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 as a zipped folder.
# SPDX-FileCopyrightText: 2022 Liz Clark for Adafruit Industries # # SPDX-License-Identifier: MIT import board import busio import simpleio import adafruit_vl53l4cd import adafruit_tca9548a import adafruit_midi from adafruit_midi.note_off import NoteOff from adafruit_midi.note_on import NoteOn from adafruit_midi.program_change import ProgramChange from adafruit_midi.control_change import ControlChange # Create I2C bus as normal i2c = board.I2C() # uses board.SCL and board.SDA # i2c = board.STEMMA_I2C() # For using the built-in STEMMA QT connector on a microcontroller # Create the TCA9548A object and give it the I2C bus tca = adafruit_tca9548a.TCA9548A(i2c) # setup time of flight sensors to use TCA9548A inputs tof_0 = adafruit_vl53l4cd.VL53L4CD(tca[0]) tof_1 = adafruit_vl53l4cd.VL53L4CD(tca[1]) tof_2 = adafruit_vl53l4cd.VL53L4CD(tca[2]) tof_3 = adafruit_vl53l4cd.VL53L4CD(tca[3]) tof_4 = adafruit_vl53l4cd.VL53L4CD(tca[4]) tof_5 = adafruit_vl53l4cd.VL53L4CD(tca[5]) tof_6 = adafruit_vl53l4cd.VL53L4CD(tca[6]) tof_7 = adafruit_vl53l4cd.VL53L4CD(tca[7]) # array of tof sensors flights = [tof_0, tof_1, tof_2, tof_3, tof_4, tof_5, tof_6, tof_7] # setup each tof sensor for flight in flights: flight.inter_measurement = 0 flight.timing_budget = 50 flight.start_ranging() # midi uart setup for music maker featherwing uart = busio.UART(board.TX, board.RX, baudrate=31250) midi_in_channel = 1 midi_out_channel = 1 # midi setup # UART is setup as the input midi = adafruit_midi.MIDI( midi_in=uart, midi_out=uart, in_channel=(midi_in_channel - 1), out_channel=(midi_out_channel - 1), debug=False, ) # height cutoff for tof sensors # adjust depending on the height of your ceiling/performance area flight_height = 150 # state of each tof sensor # tracks if you have hit the laser range pluck_0 = False pluck_1 = False pluck_2 = False pluck_3 = False pluck_4 = False pluck_5 = False pluck_6 = False pluck_7 = False # array of tof sensor states plucks = [pluck_0, pluck_1, pluck_2, pluck_3, pluck_4, pluck_5, pluck_6, pluck_7] # midi notes for each tof sensor notes = [48, 52, 55, 59, 60, 64, 67, 71] # midi instrument voice midi.send(ProgramChange(80)) while True: # iterate through the 8 tof sensors for f in range(8): while not flights[f].data_ready: pass # reset tof sensors flights[f].clear_interrupt() # if the reading from a tof is not 0... if flights[f].distance != 0.0: # map range of tof sensor distance to midi parameters # modulation mod = round(simpleio.map_range(flights[f].distance, 0, 100, 120, 0)) # sustain sus = round(simpleio.map_range(flights[f].distance, 0, 100, 127, 0)) # velocity vel = round(simpleio.map_range(flights[f].distance, 0, 150, 120, 0)) modulation = int(mod) sustain = int(sus) # create sustain and modulation CC message pedal = ControlChange(71, sustain) modWheel = ControlChange(1, modulation) # send the sustain and modulation messages midi.send([modWheel, pedal]) # if tof registers a height lower than the set max height... if int(flights[f].distance) < flight_height and not plucks[f]: # set state tracker plucks[f] = True # convert tof distance to a velocity value velocity = int(vel) # send midi note with velocity and sustain message midi.send([NoteOn(notes[f], velocity), pedal]) # if tof registers a height = to or greater than set max height # aka you remove your hand from above the sensor... if int(flights[f].distance) > flight_height and plucks[f]: # reset state plucks[f] = False # send midi note off midi.send(NoteOff(notes[f], velocity))
Upload the Code and Libraries to the Feather M4 Express
After downloading the Project Bundle, plug your Feather M4 Express 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 Feather M4 Express' CIRCUITPY drive.
- lib folder
- code.py
Your Feather M4 Express CIRCUITPY drive should look like this after copying the lib folder and the code.py file.

Additional Examples
In addition to the main code.py file, there are two more CircuitPython code files that you can use for different features.
The usb_midi_code.py file has code to allow the laser harp to be used as a USB MIDI controller. This way you can control your favorite software synth or DAW.
The laser_harp_two_voice.py file has extra features that build on the original code.py file. It can send out two different synth instruments depending on the height that the time of flight sensors detect. You can also control pitch bend or volume by raising or lowering your hand while playing a note.
To use either of these files instead of the original code.py file, remove the code.py file from your CIRCUITPY drive and rename your chosen .py file as code.py.
The code begins by setting up I2C to use board.SCL
and board.SDA
. The TCA9548A I2C multiplexer is setup as tca
.
# Create I2C bus as normal i2c = board.I2C() # uses board.SCL and board.SDA # Create the TCA9548A object and give it the I2C bus tca = adafruit_tca9548a.TCA9548A(i2c)
The time of flight sensors all use the same I2C address, 0x29
. When they are setup using the adafruit_vl53l4cd
library, their I2C pins are set as different channels on the TCA9548A.
The time of flight sensors are inserted into an array called flights
. A for
statement is used to setup each time of flight sensor with inter_measurement
and timing_budget
values, along with the function start_ranging()
to begin reading data.
# setup time of flight sensors to use TCA9548A inputs tof_0 = adafruit_vl53l4cd.VL53L4CD(tca[0]) tof_1 = adafruit_vl53l4cd.VL53L4CD(tca[1]) tof_2 = adafruit_vl53l4cd.VL53L4CD(tca[2]) tof_3 = adafruit_vl53l4cd.VL53L4CD(tca[3]) tof_4 = adafruit_vl53l4cd.VL53L4CD(tca[4]) tof_5 = adafruit_vl53l4cd.VL53L4CD(tca[5]) tof_6 = adafruit_vl53l4cd.VL53L4CD(tca[6]) tof_7 = adafruit_vl53l4cd.VL53L4CD(tca[7]) # array of tof sensors flights = [tof_0, tof_1, tof_2, tof_3, tof_4, tof_5, tof_6, tof_7] # setup each tof sensor for flight in flights: flight.inter_measurement = 0 flight.timing_budget = 50 flight.start_ranging()
MIDI is setup to use UART. The Music Maker FeatherWing takes in MIDI over UART to be used as a synth.
# midi uart setup for music maker featherwing uart = busio.UART(board.TX, board.RX, baudrate=31250) midi_in_channel = 1 midi_out_channel = 1 # midi setup # UART is setup as the input midi = adafruit_midi.MIDI( midi_in=uart, midi_out=uart, in_channel=(midi_in_channel - 1), out_channel=(midi_out_channel - 1), debug=False, )
There are a few variables that you may want to change depending on your preferences.
-
flight_height
is used as a maximum height value for the time of flight sensors. Adjust this to increase or decrease the expected range for the laser harp -
notes
is the array of MIDI notes assigned to each time of flight sensor. Change these numbers to play different notes - The
midi.send(ProgramChange(80)
message changes the instrument sound being used by the Music Maker FeatherWing. You can change the number to set a different instrument sound. Check out this reference page for a list of possible sounds and their numbers.
# height cutoff for tof sensors # adjust depending on the height of your ceiling/performance area flight_height = 150 # state of each tof sensor # tracks if you have hit the laser range pluck_0 = False pluck_1 = False pluck_2 = False pluck_3 = False pluck_4 = False pluck_5 = False pluck_6 = False pluck_7 = False # array of tof sensor states plucks = [pluck_0, pluck_1, pluck_2, pluck_3, pluck_4, pluck_5, pluck_6, pluck_7] # midi notes for each tof sensor notes = [48, 52, 55, 59, 60, 64, 67, 71] # midi instrument voice midi.send(ProgramChange(80))
In the loop, the time of flight sensors are iterated through and their values are read. The values are mapped to different MIDI parameters: modulation, sustain and velocity. Modulation and sustain are sent as a MIDI message together.
while True: # iterate through the 8 tof sensors for f in range(8): while not flights[f].data_ready: pass # reset tof sensors flights[f].clear_interrupt() # if the reading from a tof is not 0... if flights[f].distance != 0.0: # map range of tof sensor distance to midi parameters # modulation mod = round(simpleio.map_range(flights[f].distance, 0, 100, 120, 0)) # sustain sus = round(simpleio.map_range(flights[f].distance, 0, 100, 127, 0)) # velocity vel = round(simpleio.map_range(flights[f].distance, 0, 150, 120, 0)) modulation = int(mod) sustain = int(sus) # create sustain and modulation CC message pedal = ControlChange(71, sustain) modWheel = ControlChange(1, modulation) # send the sustain and modulation messages midi.send([modWheel, pedal])
If the time of flight sensor detects a height that is lower than the maximum threshold set as flight_height
, the assigned MIDI note is sent with a NoteOn
message. If the time of flight sensor detects a height that is equal to or lower than flight_height
, a NoteOff
message is sent.
# if tof registers a height lower than the set max height... if int(flights[f].distance) < flight_height and not plucks[f]: # set state tracker plucks[f] = True # convert tof distance to a velocity value velocity = int(vel) # send midi note with velocity and sustain message midi.send([NoteOn(notes[f], velocity), pedal]) # if tof registers a height = to or greater than set max height # aka you remove your hand from above the sensor... if int(flights[f].distance) > flight_height and plucks[f]: # reset state plucks[f] = False # send midi note off midi.send(NoteOff(notes[f], velocity))
Page last edited January 22, 2025
Text editor powered by tinymce.