In stock
View Purchasing OptionsProject update 3 of 9
Hey everyone! Thank you for your continued support and for telling the world about the Raspberry Breadstick. We’re 2 weeks into the campaign and over 30% funded!
I wanted to try something new this week, so I made my Breadstick into a MIDI instrument! I downloaded and installed Waveform-Free to serve as my digital audio workstation that will turn the MIDI messages we send it into sounds. Adafruit has written some great MIDI libraries for CircuitPython, let’s step through it together.
We’re going to need a way to delay or wait between loops, so we import the time module that contains a sleep function.
import time
We need to be able to reference the pins on our board, but we don’t want to have do do write board.D6, board.IMU_SCL
, etc. so we’ll import all the board pins with a *
so we can simply write D6
or `IMU_SCL later on in our code.
from board import *
We could use the digitalio module and create a DigitalInOut object for every button we add to our breadboard, but then we’d need to write the code that periodically checks the buttons, sees if the state of their pin has changed from the last time it was polled, and there’s no reason for all that boring work when there’s an amazing keypad library written to tackle that task!
import keypad
Time to import the MIDI modules. These will let us create a MIDI object that can communicate over USB to the DAW running on the computer and send messages that tell it to start or stop playing notes, how loudly to play them, and also to do cool things like bend the pitch of the notes being played!
import usb_midi
import adafruit_midi
from adafruit_midi.note_on import NoteOn
from adafruit_midi.note_off import NoteOff
from adafruit_midi.pitch_bend import PitchBend
from adafruit_midi.control_change import ControlChange
I want the pitch to bend when I wave the Breadstick back and forth, so we need to import the libraries that will let us talk to the IMU.
import busio
from adafruit_lsm6ds import Rate, AccelRange, GyroRange
from adafruit_lsm6ds.lsm6ds3trc import LSM6DS3TRC as LSM6DS
Finally I want a function that can help map values from one range to another. The PitchBend function expects an integer value of 0 through 8192 (midpoint, no bend) to 16383 but our gyroscope is will be giving us values like -18.6 or +21.3 so mapping one range to another is the easiest way to solve this.
from adafruit_simplemath import map_range
Now we’re finished importing things, let’s set up the keys. We’ll utilize internal pull-up resistors inside the RP2040 microcontroller on the Raspberry Breadstick, so all we need to do on our breadboards is use a tactile switch to connect each I/O pin to ground.
Create a list of pins that we’ll attach buttons to
key_pins = (D6, D5, D4, D3, D13, D14, D15, D16)
Create a list of musical notes to play when each button is pressed. The order is important! When the button connected to D6 is pressed, musical note "B3" will be sent.
key_notes = ("B3", "C#4", "D4", "E4", "F#4", "G4", "A4", "B4")
Create a Keys object from the keypad module that will set each of the I/O pins listed in key_pins as an input, connect them to an internal pull-up resistor, detect a button as pressed when a pin is pulled low to ground, and scan through the list of pins every 0.02 seconds to capture events.
keys = keypad.Keys(key_pins, value_when_pressed=False, pull=True, interval=0.02)
Create a MIDI object for sending musical messages to your DAW over USB.
midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=0)
Let’s keep the volume (velocity in MIDI speak) straight forward for now.
volume = 120
Setup the I2C comms to the IMU for collecting accelerometer and gyroscope data.
i2c = busio.I2C(IMU_SCL, IMU_SDA)
IMU = LSM6DS(i2c)
IMU.accelerometer_range = AccelRange.RANGE_4G
IMU.gyro_range = GyroRange.RANGE_1000_DPS
IMU.accelerometer_data_rate = Rate.RATE_1_66K_HZ
IMU.gyro_data_rate = Rate.RATE_1_66K_HZ
And now we’re ready for the main loop of the program! It will periodically scan the keys for events, if a button is pressed it will send a MIDI message to the DAW to begin playing a note, if a key is released, it will send a message to stop playing that note, and will bend the notes being played based on the rotational velocity measured by the z-axis of the gyroscope!
while True:
event = keys.events.get()
# event will be None if nothing has happened.
if event:
if event.pressed:
key = event.key_number
note = key_notes[key]
midi.send(NoteOn(note, volume))
print(f"NoteOn({note},{volume})")
if event.released:
key = event.key_number
note = key_notes[key]
midi.send(NoteOff(note))
print(f"NoteOff({note})")
acc_x, acc_y, acc_z = IMU.acceleration
gyro_x, gyro_z, gyro_z = IMU.gyro
bend = PitchBend(int(map_range(gyro_z, -25.0, 25.0, 0.0, 16383.0)))
midi.send(bend)
time.sleep(0.025)
And here is the complete code!
import time
from board import *
import keypad
import usb_midi
import adafruit_midi
from adafruit_midi.note_on import NoteOn
from adafruit_midi.note_off import NoteOff
from adafruit_midi.pitch_bend import PitchBend
from adafruit_midi.control_change import ControlChange
import busio
from adafruit_lsm6ds import Rate, AccelRange, GyroRange
from adafruit_lsm6ds.lsm6ds3trc import LSM6DS3TRC as LSM6DS
from adafruit_simplemath import map_range
# Setup the midi keys using a list of pins and associated musical notes
key_pins = (D6, D5, D4, D3, D13, D14, D15, D16)
key_notes = ("B3", "C#4", "D4", "E4", "F#4", "G4", "A4", "B4")
keys = keypad.Keys(key_pins, value_when_pressed=False, pull=True, interval=0.02)
# Setup MIDI communications over USB
midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=0)
volume = 120
print("Midi test")
print("Default output MIDI channel:", midi.out_channel + 1)
# Setup the IMU so we can get data from the gyroscope and accelerometer
i2c = busio.I2C(IMU_SCL, IMU_SDA)
IMU = LSM6DS(i2c)
IMU.accelerometer_range = AccelRange.RANGE_4G
print("Accelerometer range set to: %d G" % AccelRange.string[IMU.accelerometer_range])
IMU.gyro_range = GyroRange.RANGE_1000_DPS
print("Gyro range set to: %d DPS" % GyroRange.string[IMU.gyro_range])
IMU.accelerometer_data_rate = Rate.RATE_1_66K_HZ
print("Accelerometer rate set to: %d HZ" % Rate.string[IMU.accelerometer_data_rate])
IMU.gyro_data_rate = Rate.RATE_1_66K_HZ
print("Gyro rate set to: %d HZ" % Rate.string[IMU.gyro_data_rate])
while True:
event = keys.events.get()
# event will be None if nothing has happened.
if event:
if event.pressed:
key = event.key_number
note = key_notes[key]
midi.send(NoteOn(note, volume))
print(f"NoteOn({note},{volume})")
if event.released:
key = event.key_number
note = key_notes[key]
midi.send(NoteOff(note))
print(f"NoteOff({note})")
acc_x, acc_y, acc_z = IMU.acceleration
gyro_x, gyro_z, gyro_z = IMU.gyro
bend = PitchBend(int(map_range(gyro_z, -25.0, 25.0, 0.0, 16383.0)))
midi.send(bend)
time.sleep(0.025)