First, the CircuitPython libraries are imported.
import time import board import busio from adafruit_mcp230xx.mcp23017 import MCP23017 from digitalio import Direction import adafruit_ble from adafruit_ble.advertising.standard import ProvideServicesAdvertisement import adafruit_ble_midi # These import auto-register the message type with the MIDI machinery. # pylint: disable=unused-import import adafruit_midi from adafruit_midi.control_change import ControlChange from adafruit_midi.midi_message import MIDIUnknownEvent from adafruit_midi.note_off import NoteOff from adafruit_midi.note_on import NoteOn from adafruit_midi.pitch_bend import PitchBend
Next, I2C and the two MCP23017's are setup.
# i2c setup i2c = busio.I2C(board.SCL, board.SDA) # i2c addresses for muxes mcp1 = MCP23017(i2c, address=0x20) mcp2 = MCP23017(i2c, address=0x21)
This is followed by the arrays (noids0
and noids1
) that will hold the solenoids that are connected to the two multiplexers. This allows them to be accessed as digital outputs.
# 1st solenoid array, corresponds with 1st mux noids0 = [] for pin in range(16): noids0.append(mcp1.get_pin(pin)) for n in noids0: n.direction = Direction.OUTPUT # 2nd solenoid array, corresponds with 2nd mux noids1 = [] for pin in range(16): noids1.append(mcp2.get_pin(pin)) for n in noids1: n.direction = Direction.OUTPUT
Following the solenoid arrays are two arrays for the MIDI note numbers. notes0
will correspond with noids0
and notes1
will correspond with noids1
. Later in the loop, these arrays will be used to match against incoming NoteOn
MIDI messages.
# MIDI note arrays. notes0 = noids0; notes1 = noids1 notes0 = [55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70] notes1 = [71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86]
The BLE MIDI service is setup, followed by the BLE connection.
# setup MIDI BLE service midi_service = adafruit_ble_midi.MIDIService() advertisement = ProvideServicesAdvertisement(midi_service) # BLE connection setup ble = adafruit_ble.BLERadio() if ble.connected: for c in ble.connections: c.disconnect()
midi
is setup to be a MIDI-in device on MIDI channel 1. Channel 1 is defined as 0
in the CircuitPython MIDI library. A MIDI-in device is able to receive MIDI output from a digital audio workstation (DAW) or other MIDI communication method.
# MIDI in setup midi = adafruit_midi.MIDI(midi_in=midi_service, in_channel=0)
With everything setup, BLE can begin advertising for a connection.
# start BLE advertising print("advertising") ble.start_advertising(advertisement)
The last step before the loop is setting up speed
to hold the delay between solenoids triggering and retracting. You can adjust this depending on your preferences.
# delay for solenoids speed = 0.01
The loop begins with the initial BLE connection. Once a connection is established, "Connected
" will print to the REPL. This is followed by a delay that helps to stabilize the BLE MIDI communications before everything begins.
while True: # waiting for BLE connection print("Waiting for connection") while not ble.connected: pass print("Connected") # delay after connection established time.sleep(1.0)
Once BLE is connected, msg
is setup to hold the incoming MIDI messages.
while ble.connected: # msg holds MIDI messages msg = midi.receive()
The for
statement allows for the ItsyBitsy to identify if an individual MIDI note number has been received and control individual solenoids attached to the multiplexers. notes0_played
and notes1_played
will handle the MIDI note numbers. noid0_output
and noid1_output
will handle the multiplexer outputs.
for i in range(16): # states for solenoid on/off # noid0 = mux1 # noid1 = mux2 noid0_output = noids0[i] noid1_output = noids1[i] # states for MIDI note recieved # notes0 = mux1 # notes1 = mux2 notes0_played = notes0[i] notes1_played = notes1[i]
The following if
statements, nested in the previous for
statement, are where the action is. The code is looking for a NoteOn
message that contains one of the MIDI notes in either notes0_played
or notes1_played
. If there's a match, then the solenoid in the corresponding array index will trigger and retract with the predefined speed acting as the delay.
# if NoteOn msg comes in and the MIDI note # matches with predefined notes: if isinstance(msg, NoteOn) and msg.note is notes0_played: print(time.monotonic(), msg.note) # solenoid is triggered noid0_output.value = True # quick delay time.sleep(speed) # solenoid retracts noid0_output.value = False # identical to above if statement but for mux2 if isinstance(msg, NoteOn) and msg.note is notes1_played: print(time.monotonic(), msg.note) noid1_output.value = True time.sleep(speed) noid1_output.value = False
Why is it only looking for a NoteOn
message? The way that mallet instruments work is that they have to be struck quickly in order for the note to resonate properly. If the code were waiting for a NoteOff
message, then for longer note values the solenoid may not retract quickly enough to sound the note. That's why everything is relying on that initial NoteOn
message.
The code ends by checking if BLE disconnects. If it does, then BLE will begin advertising again to reconnect and print to the REPL that it has disconnected.
# if BLE disconnects try reconnecting print("Disconnected") print() ble.start_advertising(advertisement)
Text editor powered by tinymce.