IceCube
I made a MIDI keyboard from scratch! This project was for my engineering class in Irvington High School taught by Ms. Berbawy. I chose to make a MIDI keyboard as a way of combining my growing interest in keyboards and love for music! This keyboard acts as a soundboard of sorts to plug into your computer and make music through a synthesizer software with a USB cable. I call this one the IceCube simply for the aesthetic.
Things to keep in mind when you're making:
- For the size of the screws and such (holes, standoffs), you don't need to use M2.5 because M3 is more accessible.
- Organize your stuff (super helpful when you're creating your PCB).
- Keep track of your measurements (this'll come in handy with the plates step. I used a notebook to keep track of my progress).
Disclaimer: this project was not successful for me for now, but this Instructable should be able to lead you to a working MIDI keyboard, if you do not make my mistakes.
Supplies
Things needed:
- 1 raspberry pi pico with pins
- 21 mechanical key switches (used clicky clear kailh switches)
- 21 keycaps
- tactile switch button
- M2.5 and M3 machine screws
- 4 brass M2.5 standoffs
- 4 M2.5x16mm screws
- 1 USB cable (A to micro B)
- 4 little rubber bumper feet (optional)
- filament
- laser cutter material (wood or acrylic)
Tools used for assembly:
- solder kit
- 3d printer
- laser cutter
- screw driver
PCB
Any PCB designer program will do but I used Fritzing for its simplicity
- Before starting on the PCB, make your circuit to know how everything connects (like a planning stage), connecting the key switches with the microcontroller (Raspberry Pi Pico) [image 3].
- I made each switch connect to one pin since we have 27 switches and 40 pins [image 2, 3].
- After making sure the schematic makes sense, go on the PCB view and start making copper trails to connect each component.
- You can find a list of best practices to do when connecting each component to prevent short circuiting and all like this one.
- I suggest using FreeRouting to have it all routed for you. All you need are the gerber files of your components and let the program do it for you. Then you can just order it with JLPCB!
- The spacing between each switch is 1.75 mm (from their centers).
- I used M2.5 holes in the corner of my PCB
- You can change this depending on the size of screw you want to use to connect this with the switch plate.
- I did M2.5 but I suggest M3 because those screws are easier to find.
- Keep track of the spacing between your components to make the later steps easier.
- When the PCB is finished, you can now order it! I suggest JLCPCB.com because they are quick and cheap, and you can get 5 boards for $20!
- Make sure to check if your files are correct. I sent them a whole folder that your PCB designer program should provide when you export your design.
- I printed the etch copper top view of my PCB just in case to check if the layout makes sense (printed something that looks like image 6 and poked the pins of the components to see if the spacing makes sense).
Now you can move on to the next steps while waiting for your PCB to arrive!
Keyboard
When ordering a PCB or finish designing them, you should be able to see the measurements of your PCB. Based on these measurements you can start creating the case for your keyboard!
Things that you'll be putting together:
- PCB
- components
- key switches
- Raspberry Pi Pico
- reset button (tactile button)
- cover plates (top and bottom)
- switch plate
Plates
You can either laser cut or 3D print your plates. I suggest laser cutting for a sturdier feel, so you can see the guts of what you made too! (to show how cool your project is!)
- Your switch plate will depend on your PCB and how you arranged your components. You can see mine in images 2 and 3, I used a keyboard layout editor to get raw data to plug into this plate and case builder website to get an SVG file.
- I used that SVG file to put into my sketches of the border I had made for my switch plate in Autodesk's Fusion360. Then, I printed it out and got what you can see in image 1, where it's already connected with the rest of the parts besides the top case.
- If you want, you can laser cut the switch plate to get a springy feel or the keyboard.
- For the bottom case, I put an SVG of the sketch I created in Fusion360 into Adobe Illustrator to export and lasercut in image 4.
- Make sure to include screw holes to connect it with the switch plate, or some other way to have the two in place and not wiggling around.
- To hold it all together, laser cut a top case that's screwed with the bottom case.
Solder
- Solder the Pico on the PCB with pins. I used male to male pins to connect them together.
- After creating the switch plate, connect the switches onto the plate and fit it onto the PCB.
- Solder the pins on the other side to hold them in place.
- Solder the reset switch.
- Screw together the switch plate onto the PCB using the hole you created for each (they should match if you did your measurements right).
- With the same screws, screw the bottom case onto the PCB.
- With the standoffs and nuts, prep the spot you will put the top case on.
- Tighten standoffs with nuts on the bottom of the board.
- Screw the top case with the 6mm screws.
Microcontroller
I have two pieces of code, each just slightly different from the other, but it's not working for me. There is a lot of room for error. The things that could've go wrong are:
- my soldering (of the Pico or the switches)
- my PCB routing (may be incorrect)
- the piece of code (least likely but still possible)
To test the code, you must be able to assemble everything (at least solder your components together when your PCB arrives, but don't forget to connect the switches on the plate before soldering).
What I did for my project:
- Download the package the guide offered. I linked the guides where I got the pieces of code from.
- Put the code given below as a code.py file on your Raspberry Pi using a text editor.
- I used Mu Editor to edit the file but any editor works.
- Saved it and tested it out with the Chrome MIDI monitor app.
(code taken from this guide)
# SPDX-FileCopyrightText: 2021 John Park for Adafruit Industries
# SPDX-License-Identifier: MIT
# Pico RP2040 Mechanical MIDI Modal Keyboard
# 7x3 mech keyboard
# Each key sends MIDI NoteOn / NoteOff message over USB
# Can be any scale/mode
# Key combo sends MIDI panic (see bottom section of code)
import time
import board
from digitalio import DigitalInOut, Direction, Pull
import usb_midi
import adafruit_midi
from adafruit_midi.note_on import NoteOn
from adafruit_midi.note_off import NoteOff
from adafruit_debouncer import Debouncer
print("---Pico MIDI Modal Mech Keyboard---")
MIDI_CHANNEL = 1 # pick your MIDI channel here
midi = adafruit_midi.MIDI(midi_out=usb_midi.ports[1], out_channel=MIDI_CHANNEL-1)
def send_midi_panic():
print("All MIDI notes off")
for x in range(128):
midi.send(NoteOff(x, 0))
led = DigitalInOut(board.LED)
led.direction = Direction.OUTPUT
led.value = True
num_keys = 21
# list of pins to use (skipping GP15 on Pico because it's funky)
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,
board.GP17,
board.GP18,
board.GP19,
board.GP20,
board.GP21,
)
keys = []
for pin in pins:
tmp_pin = DigitalInOut(pin)
tmp_pin.pull = Pull.UP
keys.append(Debouncer(tmp_pin))
root_notes = (48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59) # used during config
note_numbers = (48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83)
note_names = ("C2", "C#2", "D2", "D#2", "E2", "F2", "F#2", "G2", "G#2", "A2", "A#2", "B2",
"C3", "C#3", "D3", "D#3", "E3", "F3", "F#3", "G3", "G#3", "A3", "A#3", "B3",
"C4", "C#4", "D4", "D#4", "E4", "F4", "F#4", "G4", "G#4", "A4", "A#4", "B4",)
scale_root = root_notes[0] # default if nothing is picked
root_picked = False # state of root selection
mode_picked = False # state of mode selection
mode_choice = 0
# ----- User selection of the root note ----- #
print("Pick the root using top twelve keys, then press bottom right key to enter:")
print(". . . . . . .")
print(". . . . . o o")
print("o o o o o o .")
while not root_picked:
for i in range(12):
keys[i].update()
if keys[i].fell:
scale_root = root_notes[i]
midi.send(NoteOn(root_notes[i], 120))
print("Root is", note_names[i])
if keys[i].rose:
midi.send(NoteOff(root_notes[i], 0))
keys[20].update()
if keys[20].rose:
root_picked = True
print("Root picked.\n")
# lists of mode intervals relative to root
major = ( 0, 2, 4, 5, 7, 9, 11 )
minor = ( 0, 2, 3, 5, 7, 8, 10 )
dorian = ( 0, 2, 3, 5, 7, 9, 10 )
phrygian = ( 0, 1, 3, 5, 7, 8, 10 )
lydian = (0 , 2, 4, 6, 7, 9, 11 )
mixolydian = ( 0, 2, 4, 5, 7, 9, 10)
locrian = ( 0, 1, 3, 5, 6, 8, 10)
modes = []
modes.append(major)
modes.append(minor)
modes.append(dorian)
modes.append(phrygian)
modes.append(lydian)
modes.append(mixolydian)
modes.append(locrian)
mode_names = ("Major/Ionian",
"Minor/Aeolian",
"Dorian",
"Phrygian",
"Lydian",
"Mixolydian",
"Locrian")
intervals = list(mixolydian) # intervals for Mixolydian by default
print("Pick the mode with top seven keys, then press bottom right key to enter:")
print(". . . . . . .")
print("o o o o o o o")
print("o o o o o o .")
while not mode_picked:
for i in range(7):
keys[i].update()
if keys[i].fell:
mode_choice = i
print(mode_names[mode_choice], "mode")
for j in range(7):
intervals[j] = modes[i][j]
# play the scale
for k in range(7):
midi.send(NoteOn(scale_root+intervals[k], 120))
note_index = note_numbers.index(scale_root+intervals[k])
print(note_names[note_index])
time.sleep(0.15)
midi.send(NoteOff(scale_root+intervals[k], 0))
time.sleep(0.15)
midi.send(NoteOn(scale_root+12, 120))
note_index = note_numbers.index(scale_root+12)
print(note_names[note_index], "\n")
time.sleep(0.15)
midi.send(NoteOff(scale_root+12, 0))
time.sleep(0.15)
keys[20].update()
if keys[20].rose:
print(mode_names[mode_choice], "mode picked.\n")
mode_picked = True
scale = [] # create the base scale
for i in range(7):
scale.append(scale_root + intervals[i])
midi_notes = [] # build the list with three octaves
for k in range(7):
midi_notes.append(scale[k]+24)
for l in range(7):
midi_notes.append(scale[l]+12)
for m in range(7):
midi_notes.append(scale[m])
led.value = False
print("Ready, set, play!")
while True:
for i in range(num_keys):
keys[i].update()
if keys[i].fell:
try:
midi.send(NoteOn(midi_notes[i], 120))
note_index = note_numbers.index(midi_notes[i])
print("MIDI NoteOn:", note_names[note_index])
except ValueError: # deals w six key limit
pass
if keys[i].rose:
try:
midi.send(NoteOff(midi_notes[i], 0))
note_index = note_numbers.index(midi_notes[i])
print("MIDI NoteOff:", note_names[note_index])
except ValueError:
pass
# Key combo for MIDI panic
# . o o o o o .
# o o o . o o o
# . o o o o o .
if (not keys[0].value and
not keys[6].value
and not keys[10].value
and not keys[14].value
and not keys[20].value):
send_midi_panic()
time.sleep(1)
another one
(code taken from this guide)
# SPDX-FileCopyrightText: 2021 John Park for Adafruit Industries
# SPDX-License-Identifier: MIT
# RaspberryPi Pico RP2040 Mechanical Keyboard
import time
import board
from digitalio import DigitalInOut, Direction, Pull
import usb_hid
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keycode import Keycode
from adafruit_hid.consumer_control import ConsumerControl
from adafruit_hid.consumer_control_code import ConsumerControlCode
print("---Pico Pad Keyboard---")
led = DigitalInOut(board.LED)
led.direction = Direction.OUTPUT
led.value = True
kbd = Keyboard(usb_hid.devices)
cc = ConsumerControl(usb_hid.devices)
# list of pins to use (skipping GP15 on Pico because it's funky)
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,
board.GP17,
board.GP18,
board.GP19,
board.GP20,
board.GP21,
)
MEDIA = 1
KEY = 2
keymap = {
(0): (KEY, (Keycode.GUI, Keycode.C)),
(1): (KEY, (Keycode.GUI, Keycode.V)),
(2): (KEY, [Keycode.THREE]),
(3): (KEY, [Keycode.FOUR]),
(4): (KEY, [Keycode.FIVE]),
(5): (MEDIA, ConsumerControlCode.VOLUME_DECREMENT),
(6): (MEDIA, ConsumerControlCode.VOLUME_INCREMENT),
(7): (KEY, [Keycode.R]),
(8): (KEY, [Keycode.G]),
(9): (KEY, [Keycode.B]),
(10): (KEY, [Keycode.UP_ARROW]),
(11): (KEY, [Keycode.X]), # plus key
(12): (KEY, [Keycode.Y]),
(13): (KEY, [Keycode.Z]),
(14): (KEY, [Keycode.I]),
(15): (KEY, [Keycode.O]),
(16): (KEY, [Keycode.LEFT_ARROW]),
(17): (KEY, [Keycode.DOWN_ARROW]),
(18): (KEY, [Keycode.RIGHT_ARROW]),
(19): (KEY, [Keycode.ALT]),
(20): (KEY, [Keycode.U]),
}
switches = []
for i in range(len(pins)):
switch = DigitalInOut(pins[i])
switch.direction = Direction.INPUT
switch.pull = Pull.UP
switches.append(switch)
switch_state = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
while True:
for button in range(21):
if switch_state[button] == 0:
if not switches[button].value:
try:
if keymap[button][0] == KEY:
kbd.press(*keymap[button][1])
else:
cc.send(keymap[button][1])
except ValueError: # deals w six key limit
pass
switch_state[button] = 1
if switch_state[button] == 1:
if switches[button].value:
try:
if keymap[button][0] == KEY:
kbd.release(*keymap[button][1])
except ValueError:
pass
switch_state[button] = 0
time.sleep(0.01) # debounce
Here's where you can test if the code works: Chrome browser MIDI monitor web app
If you see an output on the web app, it's working!!
Music!!!!!!!!!!!!!!!!
YIPPEE!!!
Now your IceCube is ready to make some fire music!
You need a synthesizer and a DAW (digital audio workstation). I suggest Helm and Audacity since they're free, but use any program you want. Set up your station and play away!
How to use:
- You first need to select a root note using the first 12 switches. Then press the bottom right button to select.
- [ . . . . . . . ]
- [ . . . . . - - ]
- [ - - - - - - . ]
- After selecting a root note, choose a mode or musical scale using the top seven keys. Then press the bottom right button to select.
- If a note is stuck on an "on" state, press the combo of the four corner switches and the center switch to turn off all notes.
Conclusion
Huge thanks to Ms. Berbawy for funding this project, John Park for guiding me through this site, and Dhruv for giving important tips on building a keyboard with his instructable. If it weren't for these people, I wouldn't have been able to start this project.
Unfortunately, I was not able to finish this project by the time I was supposed to, so I'm still working on it!
Again, the things that could go wrong are:
- my soldering (of the Pico or the switches).
- my PCB routing (may be incorrect).
- the piece of code (least likely but still possible).
With more trial and error, I can figure out where my problem lies and have this MIDI keyboard make music :D