Creating a Virtual Piano and Drum Sequencer

I remember going from Chopin’s dainty melodies in my living room to Drake’s aggressive baseline in the car, pondering about the difference between the two. For starters, I don’t think Drake and piano have ever been in the same sentence. But seriously, it’s hard to identify classical instruments—like the piano—in genres like Pop, R&B, Rap, and EDM. So how exactly do producers like DJ Khaled or Daft Punk mix those sick beats? Enter Digital Audio Workstations—or DAWs for short (maybe before that enter computers and before that electricity). A DAW is essentially software for making music, and today we are going to try to make some simplified versions of DAW components, like a virtual piano and drum sequencer. Pictured is an example DAW, Garageband! If you are curious about DAWs and more generally how computers make sound, be sure to check out my other blog post “A Few Notes on DAWS” which gives some more background. So, all we need to do to make our piano is make each of our keyboard keys correspond to a particular note. We then see if a keyboard key is pressed and then play that note. To make the sketch more visually appealing, we draw the piano keys—both the white and black—and highlight them if pressed. In only 50 lines, we have a fully functional piano—take a look!
import arb.soundcipher.*;
import arb.soundcipher.constants.*;
char[] keys = {'q','2','w','3','e','r','5','t','6','y','7','u','i','9','o','0','p'};
int[] black_key_indexes = {1,3,6,8,10,13,15}; 
int[] white_key_indexes = {0,2,4,5,7,9,11,12,14,16}; 
int[] highlightFrames = new int[keys.length];

SoundCipher sc;
void setup() {
	background(0);
	size(800,600);
	sc = new SoundCipher(this);
}

void draw() {
		int w_index = 0; 
		int b_index = 0; 
		for(int i = 0; i < 10; i++) {
				int white_i = white_key_indexes[w_index];
				fill(255); 
				if(highlightFrames[white_i] > 0){
					highlightFrames[white_i]-= 30;
					fill(23,133,23); 
				}
				rect(55+i*70,120,60,400);
				w_index++;
				if(i == 1 || i == 2 || i == 4 || i == 5 || i == 6 || i == 8 || i == 9){  
					//convert to index in array 
				 int black_i =  black_key_indexes[b_index];
				 fill(0); 
				 if(highlightFrames[black_i] > 0){
						highlightFrames[black_i]-= 30;
						fill(23,133,23); 
				}
				 rect(55+i*70-20,120,30,320);
				 b_index++; 
				}
		}
}
void keyPressed() {
	int k = (int)((char)key), index = -1;
	for(int i = 0; i < keys.length; i++) {
		int curKey = (int)keys[i];
		if(curKey == k) {
			sc.playNote(i + 60, 100, 0.5);
			highlightFrames[i] = 300; 
		}
	}
}


If you’d like to run the sketch click here! The drum machine code is just as simple. We draw a grid where each row corresponds to a different instrument (various drums). Then we detect to see if a certain instrument is selected to be played at a certain point in time (each column refers to a specific time frame). Thus, when a user presses play, we go through each time frame playing the corresponding notes. The drum machine code is only a bit longer because we render a visual timer that helps us see when each note is played. 
import arb.soundcipher.*;
import arb.soundcipher.constants.*;
boolean[][] grid = new boolean[4][7];
float gs = 50; float sp = 20;
float left = 200, top = 100;
boolean play_presed; 
int iterator = 0; 
int framecounter = 0;
SoundCipher[] ciphers = {new SoundCipher(this),new SoundCipher(this),new SoundCipher(this),new SoundCipher(this) }; 
float[] sounds = new float[]{ciphers[0].SNARE,ciphers[0].BASS_DRUM,ciphers[0].HIGH_TOM,ciphers[0].CRASH}; 
float[] pitch = new float[]{20,20,30,100};
float[] duration = new float[]{.5,.5,.5,.5}; 

void setup() {
		size(800,500);
		background(0);  
}
void draw() { 
		background(0);
		fill(0,234,0);
		noStroke();
		ellipse(100,100,40,40);
	
		for(int row = 0; row < grid.length; row++) {
				for(int col = 0; col < grid[row].length; col++) {
						float x = left + (gs + sp) * col;
						float y = top + (gs + sp) * row;
						
						if(grid[row][col]) {
								fill(255,0,0);
						}else {
								fill(255,255,255);
						}
						rect(x,y,gs,gs);
				}
		}
		
		if(play_presed){
			 if(iterator == grid[0].length){
				 play_presed = false;          
				 fill(255); 
				 rect(190,30,10,500); 
			 }else{         
				 fill(255); 
				 rect(190+iterator*(sp+gs),0,10,500); 
				 
				 if(framecounter%30 == 0){
					for (int i = 0; i < grid.length; i++){
							if(grid[i][iterator]){
								 ciphers[i].instrument = sounds[i]; 
								 ciphers[i].playNote(pitch[i],127,duration[i]);
								 println(pitch[i]); 
							}
					}
					iterator++; 
				 }
			 }
		}else{
			fill(255); 
			rect(190,0,10,500); 
		}
		framecounter++;   
}
void mousePressed(){
	 if(mouseX > left && mouseY > 100){
		 int col =  int((mouseX-left)/(gs+sp)); 
		 int row =  int((mouseY-top)/(gs+sp)); 
		 if(row < grid.length && col < grid[row].length){
				grid[row][col] = !grid[row][col];
			 }
		 }
	 
	 if(dist(mouseX, mouseY, 100,100) < 30){
		 play_presed = true; 
		 iterator = 0; 
		 framecounter = 0;
	 }
}
If you’d like to run this sketch copy the code into Processing (make sure to get the SoundCipher library). There you have it! The basis for making some cool sounds with a few lines of code. I know you may be wondering—but how can I play Rachmaninoff’s 2nd Piano Concerto with only 10 notes? This is an important lesson for programming in general: start simple then improve. Once you have a basic outline, you can always make code more complex and add more interesting features that are important to you.  I hope you now have a deeper appreciation for the power of programming. I know growing up, I always thought programming was only used for brute-forcing a bunch of nasty calculations, but I hope you’ve seen the elegance and practical purpose it has. It’s one of the few things that allows you to explore in a new way some of your pre-existing passions. For me for instance, it allowed me to explore my passion for music and problem solving! And don’t forget that old maxim your piano teacher told you, “Practice makes perfect,” it applies to programming too!  [wpforms id=”2359″ description=”true”]    

Leave a Reply

Your email address will not be published. Required fields are marked *