Importing the Libraries

The code begins by importing the CircuitPython libraries.

import time
import board
import digitalio
import usb_midi
import adafruit_midi
from adafruit_midi.note_on import NoteOn

Digital Output Pins

Next, the Feather's digital pins are setup to be outputs to send on and off signals to the ULN2803, which will activate the solenoids.

#  pins for the solenoid output signals
noid_pins = [board.D5, board.D6, board.D9, board.D10]

#  array for the solenoids
noids = []

#  setup for the solenoid pins to be outputs
for pin in noid_pins:
    noid = digitalio.DigitalInOut(pin)
    noid.direction = digitalio.Direction.OUTPUT
    noids.append(noid)

MIDI Setup

The MIDI notes are setup next. If you need to change the MIDI notes that will activate the solenoids, you can edit this array with the note numbers that you need.

The MIDI object is setup after the notes. The Feather is setup to be a MIDI-in device, meaning that it is receiving MIDI data.

#  MIDI note array
notes = [60, 61, 62, 63]

#  MIDI in setup
midi = adafruit_midi.MIDI(midi_in=usb_midi.ports[0], in_channel=0)

Time Keeping

Finally, two variables are setup. speed will act as a delay for how long the solenoids will remain activated before retracting. retract will be a time.monotonic() device.

#  delay for solenoids
speed = 0.03
retract = 0

The Loop

The loop begins by setting up msg to receive any incoming MIDI data.

while True:

    #  msg holds MIDI messages
    msg = midi.receive()

Solenoid and MIDI Note Array Indexes

Next, the solenoids' array index position is setup the be held in noid_output. The same is done for the MIDI note numbers with notes_played.

for i in range(4):
        #  states for solenoid on/off
        noid_output = noids[i]

        #  states for MIDI note recieved
        notes_played = notes[i]

Play the Drum!

Then the real action of the code takes place. If a NoteOn MIDI message is received that matches one of the MIDI note numbers listed in notes_played, then the matching solenoids are activated. retract is also updated to hold the current value of time.monotonic().

#  if NoteOn msg comes in and the MIDI note # matches with predefined notes:
if isinstance(msg, NoteOn) and msg.note is notes_played:
  print(time.monotonic(), msg.note)

  #  solenoid is triggered
  noid_output.value = True
  #  quick delay
  retract = time.monotonic()

Finally, the solenoids retract when the sum of retract and speed (0.03) is less than the current time.monotonic() value.

By doing this, you can activate your solenoids at the same time to stay on beat. If you used the more traditional time.sleep(value) to delay the solenoids' retractions, you would run into delays in getting the solenoids to hit the drums.

#  retracts solenoid using time.monotonic() to avoid delays between notes activating
if (retract + speed) < time.monotonic():
  noid_output.value = False

This guide was first published on Aug 05, 2020. It was last updated on Jul 30, 2020.

This page (CircuitPython Code Walkthrough) was last updated on Aug 03, 2020.

Text editor powered by tinymce.