Project tutorial
Arduino: Continuous MIDI Controller / Keyboard

Arduino: Continuous MIDI Controller / Keyboard © CC BY-NC

This expression keyboard opens possibilities of playing continuous notes.

  • 5,204 views
  • 1 comment
  • 14 respects

Components and supplies

Apps and online services

Ide web
Arduino IDE
FL studio
Sound synthesis
LoopMIDI
HairlessMIDI
Serial to MIDI Bridge

About this project

In this tutorial Making a Continuous type MIDI controller / keyboard is explained. A very simple concept of capacitive sensing is used along with MIDI communication and sound synthesizer software. You may refer attached video for a quick demonstration or you may refer half-hour-long video to have a detailed understanding of working.

Demostration of KeyBoard

Video on working details:

Details on KeyBoard

1: Background

Playing continuous notes are very much an integral part of Hindustani music. There is a various expression that required to play such type of notes. There are instruments like Violin, Sitar, or Flute that is capable of doing so. A typical keyboard can only play discrete notes, while In a high-end keyboard a pitch bend wheel is provided that can bend the note to typically 2 semitones on the positive or negative side (this value is adjustable but setting it very high may make difficult to use it with accuracy). This is not a very intuitive way of playing music. There is a various keyboard available in the market like Haken Continuum Board, ROLI seaboard. These are a capable instrument that can do the job really well. More details you may find on the website of the respective instruments.

By inspiring from the mentioned instrument, I tried to make a simple Arduino based touch keyboard previously. more details of it can be found at instructable of the same. This project made use of around 14 nos of plates (low accuracy) and the sound produced using the Arduino tone library. As the sound of it is nothing but a square wave, it is not at all pleasing to hear.

These were the learning from my previous project that I tried to improve on here.

  • Sound generation of Tone library is way too the poor, so here I made use of MIDI massages of quality can be improved,
  • The positioning accuracy of the board was too low due to the bigger size and lower number of plates. Here I almost tripled the number of sensing plates and halved the width of it.
  • The old project also didn't make use of pressure data as the tone library is not capable of modulation volume. Using MIDI opens the door for all such possibility.
  • Capacitive sensing is not a very reliable method. it may get impacted due to earthing, humidity absorbed by the wooden board, and even shoes you were while playing. Here we need to live with this problem as other method makes is either complex and/or expensive.

2: Concept

The overall flow of working goes as follow:

Sensing:

I found two approaches that can be worked upon. One is hall effect sensor-based (used in Haken Continuum board), which is very accurate but relatively complex. this needs some precision mechanism to get the accurate output. However, as mentioned in the previous section (step), the method I found very convenient is to make use of capacitive sensing. If we go for capacitive sensing we don't need any sensor components, we can simply connect metallic plates to the any of Arduino pin and it becomes the capacitive sensor. There is also a library available for Capacitive sensing that can directly be used. More details on the working of this library are available on the above link. In summary, This method checks the time a metallic plate takes to get charged from 0V to 5V via a resistor. This time value represents the capacitance. One more advantage we get is that we can also measure the pressure by using the data. the harder we press, the More area of the finger will come in contact with plates and capacitance will increase. So that's how we will not be having only proximity sensing but we will be getting some number on how hard we press.

Processing the data:

Arduino capture and processes the data. Based on the preset values it calculates the touch positions, pressure values. It also applies the required smoothing to all these values. MIDI messages are nothing but serail messages we need to write. Arduino basically controllers four signals. First to are turning ON and Off any note. another two signals pitch bend and pressure value are continuously calculated and transmitted while any note is ON.

Sound Generation:

Transmitted data from Arduino is used in FL studio to generate the required sound. It needs various software to plug the MIDI signals in FL studio.

3: Preparing the Hardware

The Procedure of making can be directly copied from my previous project. Details are available on my old tutorial: https://www.instructables.com/DIY-Arduino-Based-Co....

Previously made continuous keyboard

However, there are a few changes that need to be considered while making it. The width of plates is reduced to 6mm (against the previous 12 mm). The reason behind choosing the dimension is that when we touch the surface with minimum pressure the patch created is having a size of somewhere around 8-9mm. so when I touch any key finger will be in contact with at least two plates.

Here I also needed to cover 2 octaves and each key consists of two aluminum foil plates. we need in total of 48 numbers of plates.

Electrical Connection:

As you can see from the above image, each plate is directly connected to the common pin (number 13). It is also connected to an individual pin via a resistor. there is not any restriction on how we connect all these pins to the sensing plate as both analog and digital pin support the capacitive sensing. Based on your connection it is required to modify the code.

Note:

While cutting the plates or soldering, you must ensure that all plates are electrically separated from each other.

4: Arduino Software/Code

  • All plates are declared as a capacitive sensor, all connection needs to be properly mapped on the code. For me pin 13 was the common pin.
CapacitiveSensor   p1 = CapacitiveSensor(13,12); CapacitiveSensor   p2 = CapacitiveSensor(13,11); 
CapacitiveSensor p3 = CapacitiveSensor(13,10);
.
.
.
CapacitiveSensor p48 = CapacitiveSensor(13,9);

2. Initially capacitance values of all the sensor is captured. The "raw_cap()" function will capture values and store in a globally declared array.

void raw_cap(){
raw[1]=p1.capacitiveSensor(resolution);
raw[2]=p2.capacitiveSensor(resolution);
raw[3]=p3.capacitiveSensor(resolution);
.
.
raw[48]=p48.capacitiveSensor(resolution);
}

3. Once complete data is captured, another function named "data_process()" executes. this not only process the data but also sends the MIDI message to the computer. this Function flows as follows.

  • Key with max amplitude identified,
  • The exact position of touch is calculated considering one key before and one key after the maximum amplitude value. (That's why we need to have finger touched at least two keys at any time).
  • The last 30 values of the key and pressure values are stored
  • If the touch pressure value exceeds a predefined value, Arduino sends data to turn on a note.
  • During the touch is ON, Arduino keeps sending the pitch bend and pressure values.
  • If Key releases, it sends signals to turn off the note.

This code also snaps the key touch. for example you touch at 7.25 (slightly right on key 7), it will play key 7 (pure Note) only. and during that touch session, it will be the base value for slides. This feature enables laying pure notes easily. The absence of this will make it extremely difficult to do the same.

The complete code is available to download. If you are planning t make a similar thing, you might need to spend a good amount of time in tuning up the code and various values (like resolution and touch threshold)

for the understanding of MIDI message you may refer to this link:

https://www.instructables.com/Send-and-Receive-MID...

5: Sound Generation

There is three software you will require to generate sound:

1. Hairless MIDI: this is serial to the MIDI bridge. whatever data transmitted by arduino has captured by this software.

2. LoopMIDI: this piece of software captures the data from hairlessMIDI and acts as a MIDI device for FL studio (or whatever software you using)

3.FL studio: In the setting of FL studio you might need to select the LoopMIDI as a device. This code by default gives pitch bend between +8 to -8 semitones (default value in software is +2 to -2). This you need to set manually in the setting of the instrument. Apart from that you also need to assign the pressure value to volume.

6: Common Issues

Here is the list of issues that I faced during the development.

  • A very common issue with capacitive sensing is with proper grounding. Sensitivity will increase if the user touches the ground barefooted. sensitivity also increases if we plug the laptop into mains. However, switching ON the charger may add noise to the data. So, if you set a threshold value for some condition it may or may not work for others.
  • connect between aluminum foil is critical and prone get loosen, many time device may misbehave if it gets loosen.
  • Directly touching in the plate, wire or Pin may give completely wrong data and also spoils the sampling rate. while playing, only partially covered by plastic should be used. All exposed metal should be properly insulated to avoid accidental touch.
  • if you paste aluminum foil directly to the wooden block (with a water-based adhesive), it may take a few hours or days to dry completely. In the wet condition, it will give the wrong output as plates are connected to each other via water channels.

Code

Code for Data Capture/Process and MIDI massage GenerationArduino
// Arduino Based Continuous MIDI controller
// Code by: Abhilash Patel
// Contact: abhilashpatel121@gmail.com
// More details on the project available at: 


#include <CapacitiveSensor.h>
float raw[50],pressure[33];
byte resolution=17;
float key[33];
float play_acc,play_key,play_pressure;
int play_stat[33], play_change;
int exp_val;
float exp_data[10]={22.63,16,11.31,8,5.66,4,2.83,2,1.41,1};  // Exponential smooothing
float exp_total;
long a,b,c;
int play_bend,play_note,play_at;

 int noteON = 144;//144 = 10010000 in binary, note on command
 int velocity = 120;//velocity of MIDI notes, must be between 0 and 127


// Edit this section as per your connection layout
CapacitiveSensor   p1 = CapacitiveSensor(13,12); 
CapacitiveSensor   p2 = CapacitiveSensor(13,11); 
CapacitiveSensor   p3 = CapacitiveSensor(13,10); 
CapacitiveSensor   p4 = CapacitiveSensor(13,9);
CapacitiveSensor   p5 = CapacitiveSensor(13,8); 

CapacitiveSensor   p6 = CapacitiveSensor(13,7); 
CapacitiveSensor   p7 = CapacitiveSensor(13,6); 
CapacitiveSensor   p8 = CapacitiveSensor(13,A1);
CapacitiveSensor   p9 = CapacitiveSensor(13,A2); 
CapacitiveSensor   p10 = CapacitiveSensor(13,A3); 


CapacitiveSensor   p11 = CapacitiveSensor(13,A4);
CapacitiveSensor   p12 = CapacitiveSensor(13,A5); 
CapacitiveSensor   p13 = CapacitiveSensor(13,A6); 
CapacitiveSensor   p14 = CapacitiveSensor(13,A7); 
CapacitiveSensor   p15 = CapacitiveSensor(13,A8);

CapacitiveSensor   p16 = CapacitiveSensor(13,A9);
CapacitiveSensor   p17 = CapacitiveSensor(13,A10); 
CapacitiveSensor   p18 = CapacitiveSensor(13,A11); 
CapacitiveSensor   p19 = CapacitiveSensor(13,A12); 
CapacitiveSensor   p20 = CapacitiveSensor(13,A13);


CapacitiveSensor   p21 = CapacitiveSensor(13,A14); 
CapacitiveSensor   p22 = CapacitiveSensor(13,42); 
CapacitiveSensor   p23 = CapacitiveSensor(13,44); 
CapacitiveSensor   p24 = CapacitiveSensor(13,51);
CapacitiveSensor   p25 = CapacitiveSensor(13,49); 

CapacitiveSensor   p26 = CapacitiveSensor(13,47); 
CapacitiveSensor   p27 = CapacitiveSensor(13,45); 
CapacitiveSensor   p28 = CapacitiveSensor(13,43);
CapacitiveSensor   p29 = CapacitiveSensor(13,41); 
CapacitiveSensor   p30 = CapacitiveSensor(13,39); 


CapacitiveSensor   p31 = CapacitiveSensor(13,37);
CapacitiveSensor   p32 = CapacitiveSensor(13,35); 
CapacitiveSensor   p33 = CapacitiveSensor(13,24); 
CapacitiveSensor   p34 = CapacitiveSensor(13,31); 
CapacitiveSensor   p35 = CapacitiveSensor(13,29);

CapacitiveSensor   p36 = CapacitiveSensor(13,27);
CapacitiveSensor   p37 = CapacitiveSensor(13,25); 
CapacitiveSensor   p38 = CapacitiveSensor(13,23); 
CapacitiveSensor   p39 = CapacitiveSensor(13,38); 
CapacitiveSensor   p40 = CapacitiveSensor(13,40);


CapacitiveSensor   p41 = CapacitiveSensor(13,22); 
CapacitiveSensor   p42 = CapacitiveSensor(13,2); 
CapacitiveSensor   p43 = CapacitiveSensor(13,26); 
CapacitiveSensor   p44 = CapacitiveSensor(13,28);
CapacitiveSensor   p45 = CapacitiveSensor(13,30); 

CapacitiveSensor   p46 = CapacitiveSensor(13,32); 
CapacitiveSensor   p47 = CapacitiveSensor(13,34); 
CapacitiveSensor   p48 = CapacitiveSensor(13,36);



void setup() {
Serial.begin(115200);
exp_val=sizeof(exp_data)/sizeof(exp_data[0]);
for(int i=0;i<10;i++){exp_total=exp_total+exp_data[i];}
raw_cap();
delay(100);
}


void loop() {

raw_cap();
data_process();

}


void raw_cap()
{
  raw[1]=p1.capacitiveSensor(resolution);
  raw[2]=p2.capacitiveSensor(resolution);
  raw[3]=p3.capacitiveSensor(resolution);
  raw[4]=p4.capacitiveSensor(resolution);
  raw[5]=p5.capacitiveSensor(resolution);

  raw[6]=p6.capacitiveSensor(resolution);
  raw[7]=p7.capacitiveSensor(resolution);
  raw[8]=p8.capacitiveSensor(resolution);
  raw[9]=p9.capacitiveSensor(resolution);
  raw[10]=p10.capacitiveSensor(resolution);


  raw[11]=p11.capacitiveSensor(resolution);
  raw[12]=p12.capacitiveSensor(resolution);
  raw[13]=p13.capacitiveSensor(resolution);
  raw[14]=p14.capacitiveSensor(resolution);
  raw[15]=p15.capacitiveSensor(resolution);

  raw[16]=p16.capacitiveSensor(resolution);
  raw[17]=p17.capacitiveSensor(resolution);
  raw[18]=p18.capacitiveSensor(resolution);
  raw[19]=p19.capacitiveSensor(resolution);
  raw[20]=p20.capacitiveSensor(resolution);


  raw[21]=p21.capacitiveSensor(resolution);
  raw[22]=p22.capacitiveSensor(resolution);
  raw[23]=p23.capacitiveSensor(resolution);
  raw[24]=p24.capacitiveSensor(resolution);
  raw[25]=p25.capacitiveSensor(resolution);

  raw[26]=p26.capacitiveSensor(resolution);
  raw[27]=p27.capacitiveSensor(resolution);
  raw[28]=p28.capacitiveSensor(resolution);
  raw[29]=p29.capacitiveSensor(resolution);
  raw[30]=p30.capacitiveSensor(resolution);


  raw[31]=p31.capacitiveSensor(resolution);
  raw[32]=p32.capacitiveSensor(resolution);
  raw[33]=p33.capacitiveSensor(resolution);
  raw[34]=p34.capacitiveSensor(resolution);
  raw[35]=p35.capacitiveSensor(resolution);

  raw[36]=p36.capacitiveSensor(resolution);
  raw[37]=p37.capacitiveSensor(resolution);
  raw[38]=p38.capacitiveSensor(resolution);
  raw[39]=p39.capacitiveSensor(resolution);
  raw[40]=p40.capacitiveSensor(resolution);


  raw[41]=p41.capacitiveSensor(resolution);
  raw[42]=p42.capacitiveSensor(resolution);
  raw[43]=p43.capacitiveSensor(resolution);
  raw[44]=p44.capacitiveSensor(resolution);
  raw[45]=p45.capacitiveSensor(resolution);

  raw[46]=p46.capacitiveSensor(resolution);
  raw[47]=p47.capacitiveSensor(resolution);
  raw[48]=p48.capacitiveSensor(resolution);
}


void data_process()
{
  
float avg,max_val,tt1,tt2;
int max_ind;
int bend_base;
avg=0;max_val=0;max_ind=0;tt1=0;tt2=0;


for(int i=1;i<49;i++) {avg=avg+raw[i];}
avg=avg/48;

for(int i=1;i<49;i++) 
{raw[i]=raw[i]-avg;}


for(int i=1;i<49;i++){if(raw[i]>max_val){max_ind=i; max_val=raw[i];}}

for(int i=30; i>0; i--)
{
  key[i]=key[i-1];
  pressure[i]=pressure[i-1];
  play_stat[i]=play_stat[i-1];
}

key[0]=(raw[max_ind-1]*(max_ind-1)+raw[max_ind]*(max_ind)+raw[max_ind+1]*(max_ind+1))/(raw[max_ind-1]+raw[max_ind]+raw[max_ind+1]);
pressure[0]=raw[max_ind];//(raw[max_ind]+raw[max_ind-1]+raw[max_ind+1])/3;
//Serial.println(raw[max_ind]);
//Serial.println(pressure[0]);



if(raw[max_ind]>=17){play_stat[0]=1;} else{play_stat[0]=0;}  // the value 17 decided by trial and error. 
//Serial.println(key[0]);

if(play_stat[0]==1 && play_stat[1]==0)
        {for(int i=0;i<15;i++)
            {
            key[i]=key[0]; 
            }
         exp_smooth();
         play_note=play_acc;
         if(play_acc-play_note>0.5){play_note=play_note+1;}
         bend_base=512*(play_acc-play_note); 
         MIDImessage(144, play_note, velocity);
         //Serial.print("ON");Serial.print("\n");
        }


if(play_stat[0]==0 && play_stat[1]==1)
       {MIDImessage(144, play_note, 0);
        //Serial.print("OFF");Serial.print("\n");
       }
       
if(play_stat[0]==1)
       {
        exp_smooth();
        play_bend=8192+512*(play_acc-play_note)-bend_base;
        if(play_bend>16384){play_bend=16383;}
        if(play_bend<0){play_bend=0;}
        play_at=play_pressure;
        if(play_at>127){play_at=126;}
        MIDImessage(224,(play_bend&127),(play_bend>>7));
        MIDImessage(208, 0, play_at);  //253 
        //Serial.print(play_at);Serial.print("\t");Serial.print(play_acc);Serial.print("\t");Serial.print(play_note);Serial.print("\t");Serial.print(play_bend);Serial.print("\n");
       
       }

//Serial.println(play_at);
/*
Serial.print(play_note);
Serial.print("\t");
Serial.print(play_stat[0]);
Serial.print("\t");
Serial.print(play_acc);
Serial.print("\t");
Serial.print(play_bend);
Serial.print("\t");
Serial.print(play_pressure);
Serial.print("\t");
Serial.print(max_val);
Serial.print("\t");
*/
}


void exp_smooth()
{
         play_acc=0;
         play_pressure=0;
             for(int i=0; i<10; i++)
                 { 
                   play_acc=play_acc+exp_data[i]*key[i];
                   play_pressure=play_pressure+exp_data[i]*pressure[i];
                 }
        play_acc=54.25+play_acc/(2*exp_total);
        play_acc=play_acc-1;
        play_pressure=play_pressure/exp_total; // scalling to match 127
}


void MIDImessage(int command, int data1, int data2) {
  Serial.write(command);//send command byte
  Serial.write(data1);//send data byte #1
  Serial.write(data2);//send data byte #2 
}
continuum_kryboard.inoArduino
No preview (download only).

Schematics

Wiring for plates
Circuit bwqvpmvqfx

Comments

Similar projects you might like

Ondes Martenot Style MIDI Controller

Project showcase by christophemagnanb

  • 2,803 views
  • 1 comment
  • 9 respects

Arduino LED MIDI Controller with FL Studio

Project tutorial by reyadeetopee

  • 9,173 views
  • 1 comment
  • 21 respects

MIDI Adobe Lightroom Controller

Project tutorial by mkey05

  • 10,263 views
  • 1 comment
  • 24 respects

Unopad - Arduino MIDI Controller with Ableton

Project tutorial by Stefan Vasic

  • 5,682 views
  • 5 comments
  • 10 respects

Arduino + LEDs + MIDI Keyboard + MuseScore = Piano Tutor

Project tutorial by tcucinotta

  • 22,781 views
  • 5 comments
  • 25 respects

Midi Wood Keyboard

Project tutorial by BERRU

  • 6,445 views
  • 3 comments
  • 13 respects
Add projectSign up / Login