Building a MIDI Controller / MacroPad
by Guitarman9119 in Circuits > Raspberry Pi
4404 Views, 29 Favorites, 0 Comments
Building a MIDI Controller / MacroPad
I love tinkering with the Raspberry Pi Pico, since it is an easy starting point for anyone that wants to get into microcontrollers. In this detailed instructuble I want to guide you how you can create your own MIDI controller or MacroPad. If you are a musician and in need of a cheap but awesome looking MIDI pad then this project for you, or if you are just someone who is in need of a new MacroPad but you don't want to buy the generic cherry mx keys kits but would prefer some classic arcade buttons then you are in luck, because we can set the Raspberry Pi Pico to do either of those two functions.
If you are not sure what a MIDI controller or MacroPad is, let me break it down for you:
MIDI controller:
A MIDI controller is a simple way to sequence music and play virtual instruments on your Mac or PC. It works by sending MIDI data (Musical Instrument Digital Interface) to a computer or synthesizer, which then interprets the signal and spits out a sound.
MacroPad:
A macro pad can give you plenty of buttons for your macros. And, with most pads coming with some form of software for recording macros, program-specific actions can also be performed, usually called “shortcuts.” Shortcuts can do a variety of power things like launch programs, turn on and off your microphone, and more.
Supplies
To make this instructable as detailed as possible, I will include links from AliExpress, these are not affiliated links but will guide you on where to start. You might already have some of the components. For the wood it might be that you do not have access to a laser cutter, but there is online services that provide laser cutting, or alternatively the enclosure can be 3D printed, or made out of cardboard, the enclosure is limited to your own creativity.
Parts:
16 * Arcade Buttons (24 or 28 mm) - Link
4 * M3 Hex Brass Spacing Screws Threaded Pillar 50mm - Link
4 * M3 Nut and Screw 6mm Length - Link
1 * Raspberry Pi Pico + Breadboard + Cables - Link
1 * 2m - 20 AWG single core wire - Link
Material:
3mm wood + Black spray-paint
Equipment:
Ortur 2 Laser Master
Optional:
3D printer, tools for cutting cardboard or wood if laser cutter is not used.
Note:
The above should be used as a guidance of what will be needed to complete this project or make something similar, if your budget does not allow you to create this you can replace the arcade and enclosure and just use tactile switches on a breadboard.
Enclosure Design
The enclosure was designed in Fusion360. The reason for choosing this software is that it is free to use with a hobbyist license. I have included all the files that you will need to cut. If not possible to cut the enclosure out of wood, alternatively 3D print or create your own enclosure from different materials.
The enclosure is very simple design which consist of the top base to house the buttons and spacers to bottom base where you will have your Raspberry Pi Pico secured.
I have included all the steps in GIFS for you to follow along but in short.
- Make a square 220*220 mm
- Create a 30 diameter circle 42.5mm from the bottom and corner.
- Now duplicate this circle using the Rectangular Pattern function, setting the Distance Type -> Spacing. Set the quantity to 4 and distance 45 do to for the +x and +y direction.
- Using the fillet tool round the edges with a 30mm radius.
- Final step is to create holes for spacers from the outer corner circle add a 3mm hole a distance of 30 mm away from the circle center.
Now all that is left to do is either extrude and 3D print the enclosure or save the file to .dxf and use a laser cutter. The alternative will be to print this and stick on wood and cut out using power tools.
To give the enclosure a great looking finish I used some black spray-paint and give it 1-2 coats.
Downloads
Raspberry Pi Pico + Wiring
Let's first look at the Raspberry Pi Pico. The Pico has 26 are multipurpose GPIOs (general-purpose input/output) which for our application is perfect since we only need 16 inputs. The pinout of the Pico is given in Figure 1. We can use any GPIO pins to connect the arcade buttons, and I have included the connections I have made shown in the schematic diagram Figure 2. I decided to use the breadboard to make to process easier, and will on a later stage make a PCB to making things easier.
One side of the arcade buttons will be connected to the 3.3V power by the Pico's 3V3 output on pin 36. Using the 20 AWG wire it was stripped clean of insulation and then connected to one pin of the buttons where after the connections was made proper by soldering the wire to the pin to make good connection. This is shown in the GIF.
The rest of the arcade buttons we will use Male-Female jumper wires and cut of the plug end and solder them to the remaining pin for each of the 16 buttons. We now can connect it to the Pico as indicated in the schematic.
We will test for a HIGH input from our arcade buttons. The buttons will be pulled down to ground by internal pull-down resistors and be LOW, and when a button is pressed the 3V3 will be detected by the pin and set to HIGH and trigger an event.
Nuke Raspberry Pi Pico
There are some circumstances where you might want to make sure your Flash memory is empty. You can do this by dragging and dropping a special UF2 binary onto your Pico when it is in mass storage mode. We will do this to ensure that there is no data in memory that will cause errors.
I have included all the relevant files and code on my GitHub repository: here.
In this repository download the flash_nuke.uf2 file.
Start with your Pico unplugged from USB. Hold down the BOOTSEL button, and while continuing to hold it (don't let go!), plug the Pico into USB. A short GIF above illustrates this step. Continue to hold the BOOTSEL button until the RPI-RP2 drive appears.
You will see a new disk drive appear called RPI-RP2.
Drag the flash_nuke.uf2 file to RPI-RP2.
Installing CircuitPython
CircuitPython is a derivative of MicroPython designed to simplify experimentation and education on low-cost microcontrollers. We will use a library from the adafruit libraries for CircuitPython the HID library which will allow our Pico to be a Human Interface Device making it possible for us to receive input through a button push and send custom macro keycodes to the computer.
In this repository download the adafruit-circuitpython-raspberry_pi_pico file.
Start with your Pico unplugged from USB. Hold down the BOOTSEL button, and while continuing to hold it (don't let go!), plug the Pico into USB. A short GIF above illustrates this step. Continue to hold the BOOTSEL button until the RPI-RP2 drive appears.
You will see a new disk drive appear called RPI-RP2.
Drag the adafruit-circuitpython-raspberry_pi_pico-en_US-7.2.3.uf2 file to RPI-RP2.
Once you have installed CircuitPython is will show up as a flash Drive named CircuitPy, inside the folder lib copy the adafruit_hid and adafruit_midi folder and paste it in this folder.
MIDI Controller Code Explanation
The Keys are layout as follows:
''' This is the layout for the MIDI PAD with the Raspberry Pi Pico: key[3] Key[2] Key[1] Key[0] key[7] Key[6] Key[5] Key[4] key[11] Key[10] Key[9] Key[8] key[15] Key[14] Key[13] Key[12] '''
Take note of these as you will need it if you want to change the midi_notes.
We start of by uploading all the needed libraries
import time import board import terminalio import busio import digitalio import usb_midi import adafruit_midi from adafruit_midi.note_on import NoteOn from adafruit_midi.note_off import NoteOff
Next we will setup the midi to act as a USB MIDI output device. midi_out sends notes out from the device.
midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=0)
The pins used for the arcade buttons are stored in the note_pins array.
They are setup to be digital inputs in the for statement and are then stored in the note_buttons array.
Each arcade button has a state setup for debouncing. These states are stored in the note_states array.
note_pins = [board.GP0, board.GP1,board.GP2,board.GP3, board.GP4,board.GP5,board.GP6, board.GP7, board.GP8,board.GP9,board.GP10,board.GP11, board.GP12,board.GP13,board.GP14,board.GP16] note_buttons = [] for pin in note_pins: note_pin = digitalio.DigitalInOut(pin) note_pin.direction = digitalio.Direction.INPUT note_pin.pull = digitalio.Pull.DOWN note_buttons.append(note_pin) # note states note0_pressed = False note1_pressed = False note2_pressed = False note3_pressed = False note4_pressed = False note5_pressed = False note6_pressed = False note7_pressed = False note8_pressed = False note9_pressed = False note10_pressed = False note11_pressed = False note12_pressed = False note13_pressed = False note14_pressed = False note15_pressed = False # array of note states note_states = [note0_pressed, note1_pressed, note2_pressed, note3_pressed, note4_pressed, note5_pressed, note6_pressed, note7_pressed, note8_pressed, note9_pressed, note10_pressed, note11_pressed, note12_pressed, note13_pressed, note14_pressed, note15_pressed]
The midi_notes array holds the default MIDI notes that are assigned to the arcade buttons. If you want to change the MIDI notes you need to edit the array to the midi note you would want to use.
midi_notes = [60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75]
The arcade buttons send their assigned MIDI note number out with a MIDI NoteOn message when they are pressed.
When the arcade button is released, a NoteOff message is sent.
Now we create an endless loop to loop through our 16 buttons to see if any button is pressed, if a button is pressed, it will assign MIDI note number out with a MIDI NoteOn message.
while True: # MIDI input for i in range(16): buttons = note_buttons[i] # if button is pressed... if buttons.value and note_states[i] is True: # send the MIDI note midi.send(NoteOn(midi_notes[i], 120)) note_states[i] = False print(midi_notes[i]) # if the button is released... if not buttons.value and note_states[i] is False: # stop sending the MIDI note midi.send(NoteOff(midi_notes[i], 120)) note_states[i] = True
Now run this code, and we can go to FL studio to set up our midi as a drum pad or anything else that you want, but we will focus on a drum pad for this instructable.
Downloads
MacroPad Code Explanation
''' This is the layout for the MacroPad with the Raspberry Pi Pico: key[3] Key[2] Key[1] Key[0] key[7] Key[6] Key[5] Key[4] key[11] Key[10] Key[9] Key[8] key[15] Key[14] Key[13] Key[12] ''' In this first block of code, we import all the libraries we need from CircuitPython and then all the classes needed from the HID library: import time import board import digitalio import usb_hid from adafruit_hid.keycode import Keycode from adafruit_hid.keyboard import Keyboard from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS from adafruit_hid.consumer_control import ConsumerControl from adafruit_hid.consumer_control_code import ConsumerControlCode We then create variables and set them equal to functions from the HID library to initialize our input and output functions. cc - Set up Consumer Control - Control Codes can be found here keyboard - Set up a keyboard device. - Keycode can be found here: write_text - Set up keyboard to write strings from macro cc = ConsumerControl(usb_hid.devices) keyboard = Keyboard(usb_hid.devices) write_text = KeyboardLayoutUS(keyboard) The button input is also done this time using a list with a loops buttons = [board.GP0, board.GP1,board.GP2,board.GP3,board.GP4,board.GP5,board.GP6,board.GP7,board.GP8,board.GP9,board.GP10,board.GP11,board.GP12,board.GP13,board.GP14,board.GP16] key = [digitalio.DigitalInOut(pin_name) for pin_name in buttons] for x in range(0,len(buttons)): key[x].direction = digitalio.Direction.INPUT key[x].pull = digitalio.Pull.DOWN We create an endless loop and test if any buttons get pressed, making the input High. If the value goes from low to high, commands are sent to the computer. while True: if key[0].value: keyboard.send(Keycode.ZERO) time.sleep(0.1) print("Test") if key[1].value: keyboard.send(Keycode.ONE) time.sleep(0.1) if key[2].value: keyboard.send(Keycode.TWO) time.sleep(0.1) if key[3].value: keyboard.send(Keycode.THREE) time.sleep(0.1) if key[4].value: keyboard.send(Keycode.FOUR) time.sleep(0.1) if key[5].value: keyboard.send(Keycode.FIVE) time.sleep(0.1) if key[6].value: keyboard.send(Keycode.SIX) time.sleep(0.1) if key[7].value: keyboard.send(Keycode.SEVEN) time.sleep(0.1) if key[8].value: keyboard.send(Keycode.EIGHT) time.sleep(0.1) if key[9].value: keyboard.send(Keycode.NINE) time.sleep(0.1) if key[10].value: keyboard.send(Keycode.ONE) time.sleep(0.1) keyboard.send(Keycode.ZERO) time.sleep(0.1) if key[11].value: keyboard.send(Keycode.ONE) time.sleep(0.1) keyboard.send(Keycode.ONE) time.sleep(0.1) if key[12].value: keyboard.send(Keycode.ONE) time.sleep(0.1) keyboard.send(Keycode.TWO) time.sleep(0.1) if key[13].value: keyboard.send(Keycode.ONE) time.sleep(0.1) keyboard.send(Keycode.THREE) time.sleep(0.1) if key[14].value: keyboard.send(Keycode.ONE) time.sleep(0.1) keyboard.send(Keycode.FOUR) time.sleep(0.1) if key[15].value: keyboard.send(Keycode.ONE) time.sleep(0.1) keyboard.send(Keycode.FIVE) time.sleep(0.1) time.sleep(0.1)
Downloads
FL Studio
I decided to use FL studio to test the MIDI controller. There are many alternatives DAW's available that you can use. To setup the MIDI controller for a drum pad is straight forward. In FL Studio click on OPTIONS go to MIDI settings. CircuitPython will be shown as an Input and Output. Make all the necessary settings as indicated in the Figure above.
To setup the MIDI as a drum pad search FPC and click on the arrow pointing downwards. You will now have the option to Map notes for entire bank, where you can go from bottom left to right move a row upwards and go from right to left and repeat until you reach the first button.
Future Improvement
You probably know that no project is ever fully complete, and here is things which will be added in the second version once I receive the PCB.
- 3 Analog Inputs
- Rotary encoder
- Addressable RGB LEDs
- Option to choose number of Buttons 1 - 36
If you would like to collaborate please reach out. I hope you found this instructable useful.