For my final project, I decided to make a touch-capacitive MIDI keyboard that would also be equipped with a pitch bend sensor, octave shift buttons, and a velocity control knob.
This project used Arduino’s CapacitiveSensor library, which allows a user to establish a touch-capacitive sensor using the following code:
This indicates that there is a 1 Megohm resistor between digital pins 4 and 2 of the Teensy, and that digital pin 2 is the specific pin for the sensor in question. All the sensors (aka keys) of this keyboard also need to be connected to one specific pin in order for the code to function– in this case, all of the keys are connected to digital pin 4.
The analog hardware portion of this project was somewhat extensive, and required a fair amount of time to construct. For the base, I used a rectangular piece of cardboard. For the keys and the pitch bend sensor, I used strips of copper tape, which has capacitive properties and can be used as a touch sensor. I placed these keys in a typical keyboard formation, as you can see in the picture below.
Each key has a 1 megohm resistor soldered directly to the key, and then two wires are soldered to each resistor— one wire connects to pin 4, and the other wire connects to whichever pin is the particular sensor pin. This also applies to the pitch bend sensor. In addition, there are two mini pushbuttons for the octave shifters and a 10K potentiometer that controls the midi velocity.
Here’s a brief description of how the code works. I have included my code with all of my comments at the end of this report.
- I establish the values for playnote (the threshold at which the capacitive sensor keys respond to touch), currentMidiVelocity, constrainedMidiVelocity, initial_octave, and mappedpitchbend.
- I establish the pins that each octave shifter button is connected to (pins 17 and 18).
- I establish the array of midi frequencies (C4 to B4) in the variable midifreqs[].
- Using Arduino’s CapacitiveSensor library, I establish the digital pins that each key is connected to. I also do this for the pitch bend sensor.
- I establish the array capbuttons[], which contains the sensors for the keys. It does not contain the pitch bend sensor.
- I auto-calibrate the pitch bend sensor and the capbuttons array. This generates a more consistent playnote value. I also set the octave shifter buttons to receive input.
- I then proceed into my loop. It acts in the following way: if one of the key sensors is pressed, a midi note on is sent with the following attributes:
-the value of the midi note is equivalent to the value of midifreqs at the position of capbuttons (ie. midifreqs[h], capbuttons[h]) plus the value of initial_octave
-the value of the velocity is equivalent to the value of constrainedMidiVelocity, which is determined by the constrained value of analogRead(5)
-the current Midi channel is set to channel 3.
- After a delay of 1000 ms, a midi note off is then sent.
- Next, I establish the octave shift buttons. If the octave shift up button is pressed, the value of initial_octave increases by 12. If the octave shift down button is pressed, the value of initial_octave will decrease by 12.
- Finally, I establish my pitch bend sensor. First, I map value of mappedpitchbend to the appropriate values. Then, I establish that if the pitch bend sensor is pressed, I send a midi pitch bend to midi channel 3 with the value of mappedpitchbend. If the sensor is not pressed, I send a midi pitch bend of 8192 to midi channel 3 (this is the equivalent of a pitch bend of 0 for the values I’ve mapped).
And that’s pretty much it! If I built this project again, I would definitely build some kind of enclosure for the keys that would make the keyboard a bit more user-friendly. I would also try to make it polyphonic. Overall, even though building the hardware for it was definitely a bit more intensive than I’d originally anticipated, I had a great time making this final project.
Here’s the final code for my project:
#include <CapacitiveSensor.h> // set up CapacitiveSensor library int playnote = 3000; // this is the point (threshold) at which the cap sensor responds to touch int currentMidiVelocity = 0; // setting the midi velocity int constrainedMidiVelocity = 0; // setting the constrained midi velocity int currentMidiChannel = 3; // setting the midi channel int midifreqs[] = {60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71}; // these are the frequencies from C3 to B3 int initial_octave = 0; // establishing original value of initial_octave (important later on!) int octaveupButton = 17; // the octave up pushbutton is on pin 17 int octavedownButton = 18; // the octave down pushbutton is on pin 18 int mappedpitchbend = 0; // initialize value of mappedpitchbend (important later on!) //////////////// CAPACITIVE SENSOR KEYS ////////////////////////////////////////////////////////////////////////////////////////////////// CapacitiveSensor cs_4_2 = CapacitiveSensor(4,2); // 1M resistor between pins 4 & 2, pin 2 is sensor pin CapacitiveSensor cs_4_3 = CapacitiveSensor(4,3); // 1M resistor between pins 4 & 3, pin 3 is sensor pin CapacitiveSensor cs_4_5 = CapacitiveSensor(4,5); // 1M resistor between pins 4 & 5, pin 5 is sensor pin CapacitiveSensor cs_4_6 = CapacitiveSensor(4,6); // 1M resistor between pins 4 & 6, pin 6 is sensor pin CapacitiveSensor cs_4_7 = CapacitiveSensor(4,7); // 1M resistor between pins 4 & 7; pin 7 is sensor pin CapacitiveSensor cs_4_8 = CapacitiveSensor(4,8); // 1M resistor between pins 4 & 8; pin 8 is sensor pin CapacitiveSensor cs_4_9 = CapacitiveSensor(4,9); // 1M resistor between pins 4 & 9; pin 9 is sensor pin CapacitiveSensor cs_4_10 = CapacitiveSensor(4,10); // 1M resistor between pins 4 & 10; pin 10 is sensor pin CapacitiveSensor cs_4_11 = CapacitiveSensor(4,11); // 1M resistor between pins 4 & 11; pin 11 is sensor pin CapacitiveSensor cs_4_12 = CapacitiveSensor(4,12); // 1M resistor between pins 4 & 12; pin 12 is sensor pin CapacitiveSensor cs_4_14 = CapacitiveSensor(4,14); // 1M resistor between pins 4 & 15; pin 13 is sensor pin CapacitiveSensor cs_4_15 = CapacitiveSensor(4,15); // 1M resistor between pins 4 & 16; pin 14 is sensor pin CapacitiveSensor pitch_bend = CapacitiveSensor(4,16); // this is the pitch bend sensor CapacitiveSensor capbuttons[] = {cs_4_2, cs_4_3, cs_4_5, cs_4_6, cs_4_7, cs_4_8, cs_4_9, cs_4_10, cs_4_11, cs_4_12, cs_4_14, cs_4_15}; // keys array ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void setup() { for(int j=0; j <12; j++) { capbuttons[j].set_CS_AutocaL_Millis(0xFFFFFFFF); // this autocalibrates the sensors (gives a more consistent playnote value) } for(int i=0; i<1; i++) { pitch_bend.set_CS_AutocaL_Millis(0xFFFFFFFF); // this autocalibrates the pitch bend sensor } Serial.begin(9600); // baud rate -- not particularly important but good to know pinMode(octaveupButton, INPUT); // octave up button will receive input pinMode(octavedownButton, INPUT); // octave down button will receive input } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void loop() { for (int h = 0; h<12; h++) { if(capbuttons[h].capacitiveSensor(30) > playnote) { // if the sensor reads a value greater than playnote (the threshold) currentMidiVelocity = analogRead(5); // read value of currentMidiVelocity from analog pin 5 constrainedMidiVelocity = constrain(currentMidiVelocity, 0, 127); // constrain the read from pin 5 to values from 0 to 127 usbMIDI.sendNoteOn((midifreqs[h]+initial_octave), constrainedMidiVelocity, currentMidiChannel); // send note on delay(1000); usbMIDI.sendNoteOff((midifreqs[h]+initial_octave), 0, currentMidiChannel); // send note off //////////////////////////// OCTAVE SHIFTERS ///////////////////////////////////////////////////////////////////////////////////////// if(digitalRead(octaveupButton) == HIGH) { // if octave up button is pressed initial_octave = (initial_octave+12); // value of initial_octave increases by 12 } if(digitalRead(octavedownButton) == HIGH) { // if octave down button is pressed initial_octave = (initial_octave-12); // value of initial_octave decreases by 12 } //////////////////////// PITCH BEND /////////////////////////////////////////////////////////////////////////////////////////////// mappedpitchbend = map(mappedpitchbend,6000, 9000, 0, 16000); // this maps the pitchbend to the appropriate values if(pitch_bend.capacitiveSensor(30) > 500) { // if the sensor reads a value greater than 500 usbMIDI.sendPitchBend(mappedpitchbend, currentMidiChannel); // send the mapped pitch bend } else{ usbMIDI.sendPitchBend(8192, currentMidiChannel); // send a pitch bend of 0 (halfway between 0 and 16,000) } } } //Serial.println(mappedpitchbend); // not entirely necessary, but good to have for debugging purposes //Serial.println(constrainedMidiVelocity); // see comment above //delay(500); }
And here’s a video of it in action: