Raspberry Breadstick

A long, lean, delicious development board with a unique form factor and high-quality components

Dec 20, 2023

Project update 3 of 8

Making MIDI Music with Raspberry Breadstick

by Michael Rangen

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)


Sign up to receive future updates for Raspberry Breadstick.

Subscribe to the Crowd Supply newsletter, highlighting the latest creators and projects