For my final project, I threw together a little synth with an interface to change between types of waves, as well as add a lowpass filter, high pass filter, or bandpass filter.
The basic functionality of the synth is you can use your top row of keys to play a chromatic scale starting with Q, moving stepwise through the row (Q -> P) and then the last three half steps are in the next row down (ASD). You can adjust where the chromatic scale lies on the keyboard by pressing the arrow keys. The left and right arrow keys will shift the octave a half step up or down, and then the up and down arrow keys will shift the entire scale an octave up or down. You can do this while playing a note to have some cool effects. Another note, the keyboard is made to be able to play multiple notes at the same time, so play around with harmonies and stuff! The only issues come up when the notes frequencies become too close, and start phasing with each other… Also, if you are playing a perfect 6th and try to play a third note, the third note will not play. I’m not entirely sure how to fix these bugs.
Currently, the envelope for the filters are pre-set. Moving forward I’d like to implement some sliding-bar controls to control the envelopes. Also, the documentation on the p5.sound bandpass filter is very little, so I was not entirely sure how to control the range or location of the bandpass.
Other than these things the synth should work great! For maximum effect, use headphones. Put on a song you like on youtube or something, and try and jam out to it.
Enjoy.
//Jonathan Perez
//Section B
//jdperez@andrew.cmu.edu
//my music synth
//notes: could be much more efficient using objects and array that represents the keyboard. Consider initializing keyboard and having function calls
var keys = [] //holds all the keys in the diatonic scale including octave
//variable fill color for the buttons, allows selected button to highlitght
var fill1 = 255; //sine menu fill
var fill2 = 255; //triangle menu fill
var fill3 = 255; //square menu fill
var fill4 = 255; //sawtooth menu fill
var fill5 = 255; //lowPass menu fill
var fill6 = 255; //highPass menu fill
var fill7 = 255; //bandPass menu fill
//create a key object that holds all the information for the oscillator,
//the oscillator itself, and functions to adjust the oscillator
function synthKey(halfstep) {
this.halfstep = halfstep; //indicates where in the chromatic scale the note is
this.wave = "triangle" //type of wave the oscillator generates
this.osc = new p5.Oscillator();
this.env = new p5.Env();
this.lowPass = new p5.LowPass();
this.highPass = new p5.HighPass();
this.bandPass = new p5.BandPass();
this.fenv = new p5.Env(); //env for filters
//set the frequency and wave type for the oscillator as well as
//its envelope. Then start the oscillator so we can play the note anytime
this.setup = function() {
this.osc.setType(this.wave);
this.osc.freq((261.63 * pow(2, this.halfstep / 12)));
//this.env = new p5.Env();
this.env.setADSR(0.01, 1.2, 0.3, 0.1);
this.env.setRange(.5, 0);
this.osc.amp(this.env);
this.osc.start();
}
//now add some functionality to adjust where the diatonic scale
//starts and ends, both step-wise and by octaves
this.halfstepUp = function() {
this.halfstep += 1;
this.osc.freq((261.63 * pow(2, this.halfstep / 12)));
}
this.halfstepDown = function() {
this.halfstep -= 1;
this.osc.freq((261.63 * pow(2, this.halfstep / 12)));
}
this.octaveUp = function() {
this.halfstep += 12;
this.osc.freq((261.63 * pow(2, this.halfstep / 12)));
}
this.octaveDown = function() {
this.halfstep -= 12;
this.osc.freq((261.63 * pow(2, this.halfstep / 12)));
}
//function to change wave type
this.changeType = function(type) {
this.osc.setType(type);
}
//basic functions to tell the env to attack or release
this.attack = function() {
this.env.triggerAttack();
this.fenv.triggerAttack();
}
this.release = function() {
this.env.triggerRelease();
this.fenv.triggerRelease();
}
this.setFilter = function(filterType) { //filter type will be indicated by 0 or 1
if(filterType == 0) { // 0 maps to low pass filter
this.osc.disconnect();
this.osc.connect(this.lowPass);
this.lowPass.freq(this.fenv);
print("lowPass!")
} else if(filterType == 1) { // 1 maps to high pass
this.osc.disconnect();
this.osc.connect(this.highPass);
this.highPass.freq(this.fenv)
print("hightPass!")
} else if(filterType == 2) { // 2 maps to band pass
this.osc.disconnect();
this.osc.connect(this.bandPass);
this.bandPass.freq(this.fenv);
this.bandPass.res(10);
print("bandPass!")
}
}
this.setFilterEnv = function(A, D, S, R, startRange, endRange) {
this.fenv.setADSR(A, D, S, R);
this.fenv.setRange(startRange, endRange);
//connect envelope to the filters
this.lowPass.freq(this.fenv);
this.highPass.freq(this.fenv);
this.bandPass.freq(this.fenv);
}
}
function setup() {
createCanvas(400, 400);
background(200);
for(i = 0; i < 13; i++) {
keys.push(new synthKey(i));
keys[i].setup();
}
}
function draw() {
background(200);
var rectHeight = 45
var rectWidth = 90
masterVolume(.5);
//MAKE BUTTONS FOR WAVE TYPE
stroke(0);
strokeWeight(1);
//make boxes
rectMode(CENTER)
fill(fill1);
rect(width/2, height/2 - 2 * rectHeight, rectWidth, rectHeight);
fill(fill2);
rect(width/2, height/2 - rectHeight, rectWidth, rectHeight);
fill(fill3);
rect(width/2, height/2, rectWidth, rectHeight);
fill(fill4);
rect(width/2, height/2 + rectHeight, rectWidth, rectHeight);
fill(fill5);
rect(width/2 - rectWidth, height/2 + 150, rectWidth, rectHeight);
fill(fill6);
rect(width/2, height/2 + 150, rectWidth, rectHeight);
fill(fill7);
rect(width/2 + rectWidth, height/2 + 150, rectWidth, rectHeight);
//label boxes
fill(0);
textAlign(CENTER)
textSize(20)
text("sine", width/2, height/2 - 2 * rectHeight);
text("triangle", width/2, height/2 - rectHeight);
text("square", width/2, height/2);
text("sawtooth", width/2, height/2 + rectHeight)
text("lowPass", width/2 - rectWidth, height/2 + 150);
text("highPass", width/2, height/2 + 150);
text("bandPass", width/2 + rectWidth, height/2 + 150);
}
//MENU CONTROLS
function mouseClicked() {
//variables for all of the menu box locations
var rectHeight = 45;
var rectWidth = 90;
var rightWaveMenu = width/2 + rectWidth/2;
var leftWaveMenu = width/2 - rectWidth/2;
var topFilterMenu = height/2 + 150 - rectHeight/2;
var bottomFilterMenu = height/2 + 150 + rectHeight/2;
var lowPassLeft = width/2 - 1.5 * rectWidth;
var lowPassRight = width/2 - .5 * rectWidth;
var highPassRight = width/2 + .5 * rectWidth;
var bandPassRight = width/2 + 1.5 * rectWidth;
//WAVE FORM MENU CONTROLS
//if clicked within sine box
if(mouseX < rightWaveMenu & mouseX > leftWaveMenu
&& mouseY < height/2 - 1.5 * rectHeight && mouseY > height/2 - 2.5 * rectHeight) {
//make all keys sine waves
for(i = 0; i < keys.length; i++) {
keys[i].changeType("sine");
}
//highlight currently selected box
fill1 = color(150, 255, 255);
fill2 = 255;
fill3 = 255;
fill4 = 255;
} else if(mouseX < width/2 + rectWidth/2 & mouseX > width/2 - rectWidth/2
&& mouseY < height/2 - .5 * rectHeight && mouseY > height/2 - 1.5 * rectHeight) {
//make all keys triangle waves
for(i = 0; i < keys.length; i++) {
keys[i].changeType("triangle");
}
//highlight currently selected box
fill1 = 255;
fill2 = color(150, 255, 255);
fill3 = 255;
fill4 = 255;
} else if(mouseX < width/2 + rectWidth/2 & mouseX > width/2 - rectWidth/2
&& mouseY < height/2 + .5 * rectHeight && mouseY > height/2 - .5 * rectHeight) {
//make all keys square waves
for(i = 0; i < keys.length; i++) {
keys[i].changeType("square");
}
//highlight currently selected box
fill1 = 255;
fill2 = 255;
fill3 = color(150, 255, 255);
fill4 = 255;
} else if(mouseX < width/2 + rectWidth/2 & mouseX > width/2 - rectWidth
&& mouseY < height/2 + 1.5 * rectHeight && mouseY > height/2 + .5 * rectHeight) {
//make all keys sawtooth waves
for(i = 0; i < keys.length; i++) {
keys[i].changeType("sawtooth");
}
//highlight currently selected box
fill1 = 255;
fill2 = 255;
fill3 = 255;
fill4 = color(150, 255, 255);
//FILTER MENU CONTROLS
} else if(mouseY < bottomFilterMenu & mouseY > topFilterMenu &&
mouseX > lowPassLeft && mouseX < lowPassRight) {
for(i = 0; i < keys.length; i++) {
keys[i].setFilter(0);
keys[i].setFilterEnv(0.3, 1.2, 1000, 0.1, 8000, 400);
}
//highlight currently selected box
fill5 = color(190, 160, 255);
fill6 = 255
fill7 = 255
} else if(mouseY < bottomFilterMenu & mouseY > topFilterMenu &&
mouseX > lowPassRight && mouseX < highPassRight) {
for(i = 0; i < keys.length; i++) {
keys[i].setFilter(1);
keys[i].setFilterEnv(0.3, 1.2, 1000, 0.1, 8000, 400);
}
//highlight currently selected box
fill5 = 255
fill6 = color(190, 160, 255);
fill7 = 255
}else if(mouseY < bottomFilterMenu & mouseY > topFilterMenu &&
mouseX > highPassRight && mouseX < bandPassRight) {
for(i = 0; i < keys.length; i++) {
keys[i].setFilter(2);
keys[i].setFilterEnv(0.3, 1.2, 1000, 0.1, 8000, 400);
}
//highlight currently selected box
fill5 = 255
fill6 = 255
fill7 = color(190, 160, 255);
}
}
// --------- KEYBOARD CONTROLS, ONE CHROMATIC SCALE ----------
function keyPressed() {
if(key === 'Q') {
keys[0].attack();
} else if(key === 'W') {
keys[1].attack();
} else if(key === 'E') {
keys[2].attack();
} else if(key === 'R') {
keys[3].attack();
} else if(key === 'T') {
keys[4].attack();
} else if(key === 'Y') {
keys[5].attack();
} else if(key === 'U') {
keys[6].attack();
} else if(key === 'I') {
keys[7].attack();
} else if(key === 'O') {
keys[8].attack();
} else if(key === 'P') {
keys[9].attack();
} else if(key === 'A') {
keys[10].attack();
} else if(key === 'S') {
keys[11].attack();
} else if(key === 'D') {
keys[12].attack();
//KEYSIGNATURE CONTROLS
} else if(keyCode === LEFT_ARROW) {
for(i = 0; i < keys.length; i++) {
keys[i].halfstepDown();
}
} else if(keyCode === RIGHT_ARROW) {
for(i = 0; i < keys.length; i++) {
keys[i].halfstepUp();
}
} else if(keyCode === DOWN_ARROW) {
for(i = 0; i < keys.length; i++) {
keys[i].octaveDown();
}
} else if(keyCode === UP_ARROW) {
for(i = 0; i < keys.length; i++) {
keys[i].octaveUp();
}
}
}
function keyReleased() {
if(key === 'Q') {
keys[0].release();
} else if(key === 'W') {
keys[1].release();
} else if(key === 'E') {
keys[2].release();
} else if(key === 'R') {
keys[3].release();
} else if(key === 'T') {
keys[4].release();
} else if(key === 'Y') {
keys[5].release();
} else if(key === 'U') {
keys[6].release();
} else if(key === 'I') {
keys[7].release();
} else if(key === 'O') {
keys[8].release();
} else if(key === 'P') {
keys[9].release();
} else if(key === 'A') {
keys[10].release();
} else if(key === 'S') {
keys[11].release();
} else if(key === 'D') {
keys[12].release();
}
}