IceCube

by deniselarasambilay in Circuits > Raspberry Pi

208 Views, 3 Favorites, 0 Comments

IceCube

IMG_2062 (1).JPG

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:

Tools used for assembly:

  • solder kit
  • 3d printer
  • laser cutter
  • screw driver

PCB

IMG_9746.png
fritzing screenshot.png
schematic screenshot.png
pcb with traces (top view).png
pcb with traces (bottom view with no ground fill).png
midi-keeb-etch-copper-top.png

Any PCB designer program will do but I used Fritzing for its simplicity

  1. 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].
  2. I made each switch connect to one pin since we have 27 switches and 40 pins [image 2, 3].
  3. After making sure the schematic makes sense, go on the PCB view and start making copper trails to connect each component.
  4. You can find a list of best practices to do when connecting each component to prevent short circuiting and all like this one.
  5. 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!
  6. The spacing between each switch is 1.75 mm (from their centers).
  7. I used M2.5 holes in the corner of my PCB
  8. You can change this depending on the size of screw you want to use to connect this with the switch plate.
  9. I did M2.5 but I suggest M3 because those screws are easier to find.
  10. Keep track of the spacing between your components to make the later steps easier.
  11. 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!
  12. 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.
  13. 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

IMG_2062.JPG
Screenshot 2023-05-22 170233.png
Screenshot 2023-05-22 165448.png
Screenshot 2023-05-22 170752.png
Screenshot 2023-05-22 171129.png

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

IMG_4161.JPG
IMG_4163.JPG
  1. Solder the Pico on the PCB with pins. I used male to male pins to connect them together.
  2. After creating the switch plate, connect the switches onto the plate and fit it onto the PCB.
  3. Solder the pins on the other side to hold them in place.
  4. Solder the reset switch.
  5. Screw together the switch plate onto the PCB using the hole you created for each (they should match if you did your measurements right).
  6. With the same screws, screw the bottom case onto the PCB.
  7. With the standoffs and nuts, prep the spot you will put the top case on.
  8. Tighten standoffs with nuts on the bottom of the board.
  9. 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:

  1. Download the package the guide offered. I linked the guides where I got the pieces of code from.
  2. Put the code given below as a code.py file on your Raspberry Pi using a text editor.
  3. I used Mu Editor to edit the file but any editor works.
  4. 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:

  1. You first need to select a root note using the first 12 switches. Then press the bottom right button to select.
  2. [ . . . . . . . ]
  3. [ . . . . . - - ]
  4. [ - - - - - - . ]
  5. After selecting a root note, choose a mode or musical scale using the top seven keys. Then press the bottom right button to select.
  6. 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

Photo 2023-05-26 15.00.40.jpg

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