creyes1 Final Project – Beat Machine
//Christopher Reyes
//creyes1@andrew.cmu.edu
//Section D
//Capstone Project
//This program is a reinterpretation of Patatap (www.Patatap.com) created by
//Jono Brandel (jonobr1.com) and Lullatone (www.lullatone.com).
//Animations and drumkit sounds play if a key is pressed on the keyboard
//To differentiate from Patatap, there is a beat loop functionality that
//records the users\'s actions, then plays them in sequence for beatmaking
//DIRECTIONS
//SPEAKERS ON
//Press SPACEBAR to begin loop recording, play drum sounds with KEYS 1-5,
//Press SPACEBAR AGAIN to stop recording and play recorded beat
//Press SPACEBAR TWICE to clear loop
//Note: Records/plays one loop at a time
var introTextX = 30; //Initalizes intro text position, sets to undefined to hide
//Color scheme - RGB values
var softYellow = [248, 249, 197]; //Background
var softPink = [252, 202, 199]; //For bigSlideH
var softViolet = [187, 170, 192]; //For bigSlideV
var lightCyan = [187, 252, 223]; //For rectSlide
var darkerTeal = [85, 200, 190]; //For ringPulse
var transWhite = [255, 255, 255, 100]; //Transparent white for bg rings
//Looping variables and arrays
var actions = []; //Lists actions performed
var actionFrames = []; //Frames on which actions were performed
var actionSounds = []; //Lists sounds to be played
var counter = 0; //Counts frames
var counterMax; //Cap on frame count, signals when to loop
var actionIndex; //Relationship between actions, actionFrames, actionSounds
//Drumkit sound samples: "Urban Drum Samples" by user Biochron of soundpacks.com
//Source Link: https://soundpacks.com/free-sound-packs/urban-drum-samples/
var snare;
var bass;
var hihat;
var hightom;
var lowtom;
function preload() {
snare = loadSound("https://courses.ideate.cmu.edu/15-104/f2017/wp-content/"
+ "uploads/2017/12/creyes1-snare.wav");
snare.setVolume(.3);
bass = loadSound("https://courses.ideate.cmu.edu/15-104/f2017/wp-content/"
+ "uploads/2017/12/creyes1-bass.wav");
bass.setVolume(1);
hihat = loadSound("https://courses.ideate.cmu.edu/15-104/f2017/wp-content/"
+ "uploads/2017/12/creyes1-hihat.wav");
hihat.setVolume(.6);
hightom = loadSound("https://courses.ideate.cmu.edu/15-104/f2017/"
+ "wp-content/uploads/2017/12/creyes1-hightom.wav");
hightom.setVolume(.5);
lowtom = loadSound("https://courses.ideate.cmu.edu/15-104/f2017/wp-content/"
+ "uploads/2017/12/creyes1-lowtom.wav");
lowtom.setVolume(.5);
}
function setup() {
createCanvas(480, 480);
background(softYellow);
}
function draw() {
background(softYellow);
//Background detail, layered circles
noStroke();
fill(transWhite);
ellipse(width/2, height/2, 400, 400);
fill(softYellow);
ellipse(width/2, height/2, 350, 350);
stroke(transWhite);
noFill();
strokeWeight(10);
ellipse(width/2, height/2, 320, 320);
strokeWeight(5);
ellipse(width/2, height/2, 500, 500);
noStroke();
fill(transWhite);
ellipse(width/2, height/2, 200, 200);
//Introductory Text & Instructions
//Disappears once user begins playing
noStroke();
fill(255);
rect(introTextX-10, 420, 440, 50, 10);
fill(100);
textStyle(BOLD);
textFont("Courier New", 12);
text('Press any key 1-5, press SPACEBAR to start/stop recording your loop. '
+ 'Double-tap SPACEBAR to stop loop. (Speakers up)',
introTextX, 430, 450, 50);
//Starts initial counter to track actions
if (recording == true) {
counter++;
}
//If in playback, and items are in array, play on a loop
if (recording == false & actionFrames.length > 0) {
counter++;
//Loops counter after hitting max
if (counter === counterMax) {
counter = 0;
}
//Plays action if counter is the same as the frame which action occurred
var success = false;
for (i = 0; i < actionFrames.length; i++) {
if (counter === actionFrames[i]) {
success = true;
actionIndex = i;
break;
}
}
//Plays the action if counter number matches the stored frame count
if (success == true) {
(actions[actionIndex])();
(actionSounds[actionIndex]).play();
}
}
//Functions check if anything is in their respective arrays, then draw
playSlideH();
playSlideV();
playRectSlide();
playRingPulse();
playDotSpray();
//Visual feedback
//Red dot when recording
if (recording == true) {
noStroke();
fill(204, 67, 67);
ellipse(30, 30, 10, 10);
}
//Playback icon if looping with actions in array
if (recording == false & actions.length > 0) {
noStroke();
fill(110, 142, 105);
triangle(25, 25, 25, 35, 35, 30);
}
}
var recording = false;
function keyPressed() {
//Hides intro text once user begins playing
if (keyCode == 32 || keyCode == 49 || keyCode == 50 ||
keyCode == 51 || keyCode == 52 || keyCode == 53) {
introTextX = undefined;
}
//Toggles a loop recorder
if (keyCode == 32 & recording == false) {
counter = 0;
actions = [];
actionFrames = [];
actionSounds = [];
recording = true;
print("start");
} else if (keyCode == 32 & recording == true) {
counterMax = counter; //Creates a cap for counter to loop
counter = 0;
recording = false;
print("stop");
}
//Triggers an animation and sound clip on press
//If recording, stores action, the frame that action occurred, and the sound
//into respective arrays - actions[], actionFrames[], actionSounds[]
//Excluding dotSpray, clears arrays on press
//Pressing 1 sprays dots while playing a highhat clip
if (keyCode == 49) {
hihat.play();
dotSpray();
if (recording == true) {
print('1');
actionFrames.push(counter);
actions.push(dotSpray);
actionSounds.push(hihat);
}
}
//Pressing 2 slides two rectangles while playing a bass drum clip
if (keyCode == 50) {
bass.play();
rectSliders = [];
rectSlide();
if (recording == true) {
print('2');
actionFrames.push(counter);
actions.push(rectSlide);
actionSounds.push(bass);
}
}
//Pressing 3 creates a scaling ring while playing a snare clip
if (keyCode == 51) {
snare.play();
rings = [];
ringPulse();
if (recording == true) {
print('3');
actionFrames.push(counter);
actions.push(ringPulse);
actionSounds.push(snare);
}
}
//Pressing 4 slides a large panel horizontally, plays a hightom clip
if (keyCode == 52) {
hightom.play();
bigSliderH = [];
bigSlideH();
if (recording == true) {
print('4');
actionFrames.push(counter);
actions.push(bigSlideH);
actionSounds.push(hightom);
}
}
//Pressing 5 slides a large panel vertically, plays a lowtom clip
if (keyCode == 53) {
lowtom.play();
bigSliderV = [];
bigSlideV();
if (recording == true) {
print('5');
actionFrames.push(counter);
actions.push(bigSlideV);
actionSounds.push(lowtom);
}
}
}
/*----Dot Spray Functions-----------------------------------------------------*/
//Dots fly out from a random position
var dots = []; //For dotSpray
//Places dots into array
function dotSpray() {
var dotOriginX = random(0, width);
var dotOriginY = random(0, height);
for (var i = 0; i < 10; i++) {
dots.push(makeDot(dotOriginX, dotOriginY));
}
}
//Creates dot object
function makeDot(inputX, inputY) {
var dot = {x: inputX,
y: inputY,
stepX: random(-20, 20),
stepY: random(-20, 20),
color: [random(0, 255), random(0, 255), random(0, 255)],
size: random(5, 10),
move: dotStep,
display: drawDot}
return dot;
}
//Render dot
function drawDot() {
noStroke();
fill(this.color);
ellipse(this.x, this.y, this.size);
}
//Moves dot
function dotStep() {
this.x += this.stepX;
this.y += this.stepY;
}
//If something is in the array, execute animation
function playDotSpray() {
//Checks if anything exists inside array
if (dots.length > 0) {
for (var i = 0; i < dots.length; i++) {
//Moves and renders
dots[i].move();
dots[i].display();
//If dot leaves canvas, remove it from dots array
if (dots[i].x < 0 || dots[i].x > width ||
dots[i].y < 0 || dots[i].y > height) {
dots.splice(i, 1);
}
}
}
}
/*----------------------------------------------------------------------------*/
/*----rectSlide Functions-----------------------------------------------------*/
//Two rectangles slide vertically across the screen, direction random
var rectSliders = []; //For rectSliders
//Places slider into array
function rectSlide() {
var startingY = 480;
var slideRate = 20;
var sliderY1; //Input for the first rectangle's Y position
var sliderStep1; //Input for the first rectangle's move speed
var sliderY2; //Input for the second rectangle's Y position
var sliderStep2; //Input for the first rectangle's move speed
//50/50 chance on which direction the sliders travel
var coin1 = coinToss();
if (coin1 == true) {
sliderY1 = -startingY;
sliderStep1 = slideRate;
} else {
sliderY1 = startingY;
sliderStep1 = -slideRate;
}
rectSliders.push(makeSlider(100, sliderY1, sliderStep1));
var coin2 = coinToss();
if (coin2 == true) {
sliderY2 = -startingY;
sliderStep2 = slideRate;
} else {
sliderY2 = startingY;
sliderStep2 = -slideRate;
}
rectSliders.push(makeSlider(300, sliderY2, sliderStep2));
}
//Creates slider object
function makeSlider(inputX, inputY, inputS) {
var slider = {x: inputX,
y: inputY,
w: 150,
h: 480,
color: lightCyan,
stepY: inputS,
move: slideStep,
display: drawSlider};
return slider;
}
//Renders slider
function drawSlider() {
noStroke();
fill(this.color);
rect(this.x, this.y, this.w, this.h);
}
//Moves slider
function slideStep() {
this.y += this.stepY;
}
//If something is in the array, execute animation
function playRectSlide() {
//Checks if anything exists inside array
if (rectSliders.length > 0) {
for (var i = 0; i < rectSliders.length; i++) {
rectSliders[i].move();
rectSliders[i].display();
//If rectangle leaves canvas, remove it from array
if (rectSliders[i].y < -500 || rectSliders[i] > 500) {
rectSliders.splice(i, 1);
}
}
}
}
/*----ringPulse Functions-----------------------------------------------------*/
//Creates a ring that grows or shrinks in size from center
var rings = [];
//Places ring into array
function ringPulse() {
var scaleRate = 50;
var startingSize; //Input for ring's starting size
var scaleTick; //Input for ring scale rate
var coin = coinToss();
//Determines direrction ring is scaling
if (coin == true) {
//Ring explodes outwards
startingSize = 0;
scaleTick = scaleRate;
} else {
//Ring collapses inwards
startingSize = width*1.5;
scaleTick = -scaleRate;
}
rings.push(makeRing(startingSize, scaleTick));
}
//Creates ring object
function makeRing(inputSize, inputStep) {
var ring = {x: width/2,
y: height/2,
size: inputSize,
step: inputStep,
color: darkerTeal,
weight: 50,
scale: ringStep,
display: drawRing};
return ring;
}
//Renders ring
function drawRing() {
strokeWeight(this.weight);
stroke(this.color);
noFill();
ellipse(this.x, this.y, this.size);
}
//Scales ring
function ringStep() {
this.size += this.step;
}
//If something is in the array, execute animation
function playRingPulse() {
if (rings.length > 0) {
//Renders and scales ring
for (var i = 0; i < rings.length; i++) {
rings[i].scale();
rings[i].display();
//If ring gets too big or too small, remove it from array
if (rings[i].size > width*2 || rings[i].size < 0) {
rings.splice(i, 1);
}
}
}
}
/*----------------------------------------------------------------------------*/
/*----bigSlideH Functions-----------------------------------------------------*/
//Slides a big block of color horizontally across the screen
var bigSliderH = [];
//Places slider into array
function bigSlideH() {
var startingX = 580;
var moveRate = 50;
var sliderHX; //Input for X position of slider
var sliderHS; //Input for movement rate of slider
var coin = coinToss();
//50/50 chance of coming in from either side of canvas
if (coin == true) {
sliderHX = -startingX;
sliderHS = moveRate;
} else {
sliderHX = startingX;
sliderHS = -moveRate;
}
bigSliderH.push(makeSliderH(sliderHX, sliderHS));
}
//Creates slider object
function makeSliderH(inputX, inputS) {
var sliderH = {x: inputX,
y: 0,
w: width+100,
h: height,
speed: inputS,
col: softPink,
move: sliderHStep,
display: drawSliderH};
return sliderH;
}
//Renders slider
function drawSliderH() {
noStroke();
fill(this.col);
rect(this.x, this.y, this.w, this.h);
}
//Moves slider
function sliderHStep() {
this.x += this.speed;
}
//If something is in the array, execute animation
function playSlideH() {
if (bigSliderH.length > 0) {
for (var i = 0; i < bigSliderH.length; i++) {
bigSliderH[i].display();
bigSliderH[i].move();
//Clear array if sliders leave canvas
if (bigSliderH[i].x < -580 || bigSliderH[i].x > 580) {
bigSliderH = [];
}
}
}
}
/*----------------------------------------------------------------------------*/
/*----bigSlideV Functions-----------------------------------------------------*/
//Slides a big block of color horizontally across the screen
var bigSliderV = [];
//Places slider into array
function bigSlideV() {
var startingY = 580;
var moveRate = 50;
var sliderVY; //Input for Y position of slider
var sliderVS; //Input for movement rate of slider
var coin = coinToss();
//50/50 chance of coming in from either side of canvas
if (coin == true) {
sliderVY = -startingY;
sliderVS = moveRate;
} else {
sliderVY = startingY;
sliderVS = -moveRate;
}
bigSliderV.push(makeSliderV(sliderVY, sliderVS));
}
//Creates slider object
function makeSliderV(inputY, inputS) {
var sliderV = {x: 0,
y: inputY,
w: width,
h: height + 100,
speed: inputS,
col: softViolet,
move: sliderVStep,
display: drawSliderV};
return sliderV;
}
//Renders slider
function drawSliderV() {
noStroke();
fill(this.col);
rect(this.x, this.y, this.w, this.h);
}
//Moves slider
function sliderVStep() {
this.y += this.speed;
}
//If something is in the array, execute animation
function playSlideV() {
if (bigSliderV.length > 0) {
for (var i = 0; i < bigSliderV.length; i++) {
bigSliderV[i].display();
bigSliderV[i].move();
//Clear array if sliders leave canvas
if (bigSliderV[i].y < -580 || bigSliderV[i].y > 580) {
bigSliderV = [];
}
}
}
}
/*----------------------------------------------------------------------------*/
/*----------------------------------------------------------------------------*/
//Returns a Boolean value with a 50/50 chance
function coinToss() {
var coin = floor(random(0, 2));
if (coin === 0) {
return true;
} else {
return false;
}
}
DIRECTIONS: Press number keys 1-5 to play various drum sounds, hit spacebar to record your beat, then spacebar once more to play it back – double-tap spacebar to clear your loop. A red dot in the upper-left indicates that you are recording, and an arrow indicates a loop is being played. Please turn on your speakers.
This program is a reinterpretation of Patatap created by Jono Brandel and Lullatone. While I really enjoyed my experience with Patatap, I felt that it was lacking a system to create a looping beat capability seen on percussion pads or Launchpads. Because of this, I wanted to focus less on creating a complete keyboard’s worth of complex animation and moreso on creating that kind of robust looping system.
The program works by having a running frame counter once recording begins, and storing the action with its respective animation and sound, as well as the frame that it was performed into separate arrays. To loop, the counter resets, and whenever the counter number is the same as a stored frame number, the associated action is executed. For me, the biggest challenge was having all of these arrays relate to each other and keeping everything organized for a clean loop, however once I figured out the core system down, it was just a matter of adding more content in terms of possible actions.
In the future, I’d like to develop this further to have a full keyboard’s worth of sound and animation, as well as being able to perform several loops at once (likely involving storing arrays within arrays), but for now, I’m really pleased with the final result. I’ve played percussion for several years, so it was really enjoyable to get the chance to translate that interest over to a different medium.
Sounds are from Urban Drum Samples on SoundPacks.com, by user Biochron.