(The audio seems to only work in wordpress on Chrome).
instructions:
hover over points to hear their pitches.
click points to activate them.
click points again to deactivate them.
clicked points that are close enough to each other will link to each other. (try not to link too many or the program will lag lol)
explore and enjoy the soundscape!
/* Hannah Cai
Section C
hycai@andrew.cmu.edu
Final Project
*/
//particle position arrays
var particleNumber = 200; //number of particles
var psize = 1.5; //particle size
var px = []; //particle x position
var py = []; //particle y position
var pz = []; //particle z position
var distanceToPoint; //dist from (mouseX, mousY) to (px, py)
var amplitude = 3.14 * 3; //amplitude of bobbing animation
var waveSpeed; //speed of bobbing animation
var theta = 0; //plugin for sin()
//particle sound arrays
var threshold = 100; //minimum distance between mouse and particle to trigger glow/sound
var notes = [130.81, 146.83, 164,81, 174.61, 196, 220, 246.94, //pitches of whole notes from C3
261.63, 293.66, 329.63, 349.23, 392.00, 440, 493.88,
523.25, 587.33, 659.25, 698.46, 783.99, 880, 987.77,
1046.5, 1174.66, 1318.51, 1396.91, 1567.98, 1760, 2093]; //to C7
var ppitch = []; //pitch values for each particle
var pOsc = []; //oscillator for each particle
var pvolume = 0; //volume of each particle
var pOscOn = []; //array of booleans for if the oscillators are on
//misc other particle arrays
var pClicked = []; //array of booleans for if the particle was clicked
var glowSize; //size of particle glow
//arrays for cursor
var xarray = [0, 10, 20, 30, 40, 50];
var yarray = [0, 10, 20, 30, 40, 50];
var s;
//arrays for camera and perspective
var camX;
var camY;
var camZ;
var rotateX;
var rotateY;
//arrays for lines between particles
var connect = [];
var connectionThreshold = 500;
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL); //fit canvas to window size
//set up variables; store in arrays
for (i = 0; i < particleNumber; i++) {
px.push(random(-width * 0.8, width * 0.8));
py.push(random(-height, height));
pz.push(random(-width, height / 2));
ppitch.push(notes[floor(random(0, notes.length))]);
pOscOn.push(false);
pClicked.push(false);
makeOsc(i);
}
}
function makeOsc(index) {
myOsc = new p5.SinOsc();
myOsc.freq(ppitch[index]);
pOsc.push(myOsc); //store oscillators in pOsc array
}
function playOsc(index) {
var maxVolume = 0.01;
pvolume = constrain(pvolume, 0, maxVolume);
//turn clicked particles permanently on
if (pClicked[index] === true) {
pvolume = maxVolume;
} else {
//unclicked particles get louder as the mouse gets closer
pvolume = map(distanceToPoint, threshold, 0, 0, maxVolume);
}
//make particles with lower pitches louder, so all ranges are heard clearly
var factor = map(ppitch[index], ppitch[0], ppitch[ppitch.length - 1], 5, 1);
pvolume *= factor;
pOsc[index].amp(pvolume);
}
function stopOsc(index) {
pOsc[index].stop();
}
function draw() {
background(0);
noStroke(); //get rid of default black stroke
//map camera position to mouse position to simulate orbit control
camX = map(mouseX, 0, width, -width / 2, width / 2);
camY = map(mouseY, 0, height, -height / 2, height / 2);
camZ = (height/2.0) / tan(PI*30.0 / 180.0);
camera(camX, camY, camZ, 0, 0, 0, 0, 1, 0);
//set up particles
for (i = 0; i < particleNumber; i++) {
drawLines(i); //draw lines between clicked particles
//create bobbing movement
waveSpeed = map(pz[i], -width, height, 20000, 70000); //create parallax effect
theta += (TWO_PI / waveSpeed);
if (theta > amplitude) {
theta = -theta;
}
py[i] += sin(theta);
push();
translate(px[i], py[i], pz[i]);
drawGlow(i); //draw glow of each particle
//draw each particle
fill(255);
smooth();
sphere(psize);
pop();
//play a particle's oscillator if the mouse's
//distance is less than the threshold
if (distanceToPoint <= threshold) {
if (pOscOn[i] == false) {
pOsc[i].start();
pOscOn[i] = true;
}
playOsc(i);
}
//stop a particle's oscillator if the mouse's
//distance is greater than the threshold
if (distanceToPoint > threshold & pClicked[i] == false) {
stopOsc(i);
pOscOn[i] = false;
}
}
//cursor
noCursor(); //turn off the cursor icon, display below instead
//this is basically the code from the snake lab we did
for (var i = 0; i < xarray.length; i++) {
fill(255, 255, 200);
s = 8 - (xarray.length - i);
ellipse(xarray[i], yarray[i], s, s);
}
xarray.push(mouseX - width / 2);
yarray.push(mouseY - height / 2);
if (xarray.length > 8) {
xarray.shift();
yarray.shift();
}
}
function drawGlow(index) {
push();
noStroke();
//rotate the (flat) ellipses to face the cameras to simulate 3d glow
rotateX(radians(map(camY, -height / 2, height / 2, 40, -40)));
rotateY(radians(map(camX, -width / 2, width / 2, -45, 45)));
//calculate distance from mouse to each point
distanceToPoint = dist(mouseX - width / 2, mouseY - height / 2, px[index], py[index]);
//clicked particles have a pulsing glow;
//unclicked particles glow when the mouse hovers close to them
if (pClicked[index] === true) {
glowSize = map(sin(theta), TWO_PI, 0, psize, 100);
} else {
glowSize = map(distanceToPoint, 100, psize, psize, 100);
}
//draw the actual glow (a radial alpha gradient)
for (r = psize; r < glowSize; r += 1.5) {
fill(255, 255, 200, map(r, psize, glowSize, 2, 0));
ellipse(0, 0, r);
}
pop();
}
function drawLines(index) {
push();
//push the indices of clicked particles in the "connect" array;
//turn off/remove particles from the array if clicked again
if (pClicked[index] == true & ! connect.includes(index)) {
connect.push(index);
} else if (pClicked[index] == false) {
connect.splice(index, 1);
}
//connect groups of particles that are clicked if the distance between is less than the threshold
stroke(255);
strokeWeight(1);
noFill();
for (i = 0; i < connect.length; i++) {
for (j = i + 1; j < connect.length; j++) {
if (dist(px[connect[i]], py[connect[i]], pz[connect[i]],
px[connect[j]], py[connect[j]], pz[connect[j]]) < connectionThreshold) {
beginShape(LINES);
vertex(px[connect[i]], py[connect[i]], pz[connect[i]]);
vertex(px[connect[j]], py[connect[j]], pz[connect[j]]);
endShape();
}
}
}
noStroke();
pop();
}
//if window is resized, refit the canvas to the window
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
}
function mouseClicked() {
for (i = 0; i < particleNumber; i++) {
distanceToPoint = dist(mouseX - width / 2, mouseY - height / 2, px[i], py[i]);
//toggle pClicked on and off if mouse clicks within 10 pixels of a particle
if (distanceToPoint < 10 & pClicked[i] == false) {
pClicked[i] = true;
} else if (distanceToPoint < 10 & pClicked[i] == true) {
pClicked[i] = false;
}
}
}
Here’s a zip file for the fullscreen version.
final project fin
There are still a few bugs I’m aware of that I don’t know how to fix:
1. sometimes the links will flicker, adding another grouped point sometimes fixes it
2. sometimes the volume is louder than it should be upon refreshing/starting the program. I constrained the volume to try and avoid this but it didn’t seem to help
3. sometimes all the oscillators start off turned on upon refreshing/starting the program (if you move your mouse close to a point, the sine wave will start and stop, instead of fading in and out).
Generally, refreshing the page fixes all of these bugs, so please refresh the page if you notice any of the above!
I enjoyed this project a lot. Even though I spent a lot of time struggling and debugging, I feel like I learned a lot about both WEBGL and using sound/oscillators. I’m pretty satisfied in the final visual effects as well, although unfortunately, the program will start to lag if too many linked points are formed. Also unfortunately, my aim with this project was to familiarize myself more with objects, but I was completely stuck trying to format things in objects so I made everything with a ton of arrays instead. I definitely want to revisit this project in the future and format it properly with objects. In general, I definitely want to keep adding to this project because it’s still pretty clunky and buggy right now. I was planning to add a start screen, instructions, the ability to record audio, and different modes (eg a “wander” mode where the cursor moves around on its own), but I didn’t have time to even try implementing most of those before the deadline. In the future, though, I definitely want to try and make this something that could be a standalone interactive website (and add it to my portfolio, lol).
In general, I loved this class and I learned a lot! Thank you to Dannenberg and all the TAs!
]]>My final project uses the webcam to generate simple animations that will follow motion/gestures of the user through a red color marker. For the best experience, make sure to only have one red object in front of the camera at a time.
Instructions:
grab something red (any relatively small red object would work best, or just pull up a red image on your phone)
hold it within range of the webcam, with the red facing the camera
wave/draw/play
*to see the animations alone, you can turn the camera image on/off by pressing camera button
press on shape 1, shape 2, and shape 3 to see different shapes
works best in a brightly lit room, so the computer can recognize the red object
// Sophie Chen
// sophiec@andrew.cmu.edu
// 15-104 Final Project
// Dec 2018
// This program uses the webcam and generates a simple animation that will
// follow a red color marker. For best user experience, make sure to only have
// one red object in front of the camera at a time.
// declare variables
var myCaptureDevice;
var xarray = []; // array to store the value of x positions
var yarray = []; // array to store the value of y positions
var limitSize = 50; // length limit of x and y arrays
var cameraOn = 1; // camera switch - default on
var shapeOne = 1; // default starts with shape one
var shapeTwo = -1; // shape two starts false
var shapeThree = -1; // shape three starts false
var turtle; // define turtle graphics variable
// setup loads the webcam input and initializes turtle graphics
function setup() {
createCanvas(600, 430);
myCaptureDevice = createCapture(VIDEO);
myCaptureDevice.size(600, 430);
myCaptureDevice.hide();
turtle = makeTurtle(0, 0);
turtle.penDown();
turtle.setColor(255);
}
// this function tests for color as a condition
function isColor(c) {
return (c instanceof Array);
}
// this function draws the animations on top of the camera image
function draw() {
myCaptureDevice.loadPixels();
// if cameraOn is true, load camera output
// if cameraOn is false, camera output is not visible, load black background
if (cameraOn === 1){
image(myCaptureDevice, 0, 0);
} else {
background(0);
}
//call functions that draw the buttons
drawCamButton();
drawShapeButton();
drawShapeTwoButton();
drawShapeThreeButton();
//declare variables used to calculate centerpoint of red object/marker
var xMin = 0; // x value minimum
var xMax = 600; // x value maximum
var yMin = 0; // y value minimum
var yMax = 430; // y value maximum
// for loop that draws the shape animations
for (var a = 0; a < xarray.length; a++){
// declare color and size variables based on forloop
var size = (50 / xarray.length) * a;
var r = map(a, 0, xarray.length, 0, 255);
var g = map(a, 0, xarray.length, 0, 255);
var b = map(a, 0, xarray.length, 0, 255);
// Shape 1: filled ellipse
if (shapeOne === 1){
shapeTwo = -1;
shapeThree = -1;
noStroke();
fill(r, g, 255);
ellipse(xarray[a], yarray[a], size, size);
}
// Shape 2: outlined ellipse
if (shapeTwo === 1) {
shapeOne = -1;
shapeThree = -1;
noFill();
stroke(r, g, b);
strokeWeight(1);
ellipse(xarray[a], yarray[a], size, size);
}
// Shape 3: turtle graphics
if (shapeThree === 1) {
shapeOne = -1;
shapeTwo = -1;
turtle.setColor(color(205, 255, 10));
turtle.goto(xarray[a], yarray[a]);
turtle.forward(25);
turtle.right(90);
}
}
// get the color value of every 5 pixels of webcam output
for (var i = 0; i < width; i += 5){
for (var j = 0; j < height; j+= 5) {
var currentColor = myCaptureDevice.get(i, j);
// targetColor: color(255, 0, 0);
// calculate the difference between current color and target color
var dr = red(currentColor) - 255;
var dg = green(currentColor) - 0;
var db = blue(currentColor) - 0;
// if current color is close enough to target color (~120), calculate
// center point of the red area
if (isColor(currentColor)){
var dist = sqrt(sq(dr) + sq(dg) + sq(db));
if (dist < 120) {
// find center point of red marker
if (i > xMin){
xMin = i;
}
if (i < xMax){
xMax = i;
}
if (j > yMin){
yMin = j;
}
if (j < yMax){
yMax = j;
}
}
}
}
}
// push the newly discovered x, y into the array
xarray.push((xMin + xMax) / 2);
yarray.push((yMin + yMax) / 2);
// if array is full, pop something out from the beginning
while (xarray.length > limitSize) {
xarray.shift();
yarray.shift();
}
}
// functions to trigger responses of buttons pressed
function mouseClicked(){
// if camera button is pressed, toggle on/off
if (mouseX > 10 & mouseX < 60 && mouseY > 10 && mouseY < 30){
cameraOn = -cameraOn;
}
// if shape 1 button is pressed, show shape 1, disable shape 2 & 3
if (mouseX > 10 && mouseX < 60 && mouseY > 20 && mouseY < 60){
shapeOne = 1;
shapeTwo = -1;
shapeThree = -1;
}
// if shape 2 button is pressed, show shape 2, disable shape 1 & 3
if (mouseX > 10 && mouseX < 60 && mouseY > 60 && mouseY < 90){
shapeTwo = 1;
shapeOne = -1;
shapeThree = -1;
}
// if shape 3 button is pressed, show shape 3, disable shape 1 & 2
if (mouseX > 10 && mouseX < 60 && mouseY > 90 && mouseY < 120){
shapeThree = 1;
shapeTwo = -1;
shapeOne = -1;
}
}
// camera button
function drawCamButton(){
fill(255);
stroke(0);
rect(10, 10, 50, 20);
noStroke();
fill(0);
text('camera', 15, 23);
}
// shape 1 button
function drawShapeButton(){
fill(255);
stroke(0);
rect(10, 40, 50, 20);
noStroke();
fill(0);
text('shape 1', 15, 54);
}
// shape 2 button
function drawShapeTwoButton(){
fill(255);
stroke(0);
rect(10, 70, 50, 20);
noStroke();
fill(0);
text('shape 2', 15, 85);
}
// shape 3 button
function drawShapeThreeButton(){
fill(255);
stroke(0);
rect(10, 100, 50, 20);
noStroke();
fill(0);
text('shape 3', 15, 116);
}
//////////////////////////////////////////////////////////////////////////////
function turtleLeft(d){this.angle-=d;}function turtleRight(d){this.angle+=d;}
function turtleForward(p){var rad=radians(this.angle);var newx=this.x+cos(rad)*p;
var newy=this.y+sin(rad)*p;this.goto(newx,newy);}function turtleBack(p){
this.forward(-p);}function turtlePenDown(){this.penIsDown=true;}
function turtlePenUp(){this.penIsDown = false;}function turtleGoTo(x,y){
if(this.penIsDown){stroke(this.color);strokeWeight(this.weight);
line(this.x,this.y,x,y);}this.x = x;this.y = y;}function turtleDistTo(x,y){
return sqrt(sq(this.x-x)+sq(this.y-y));}function turtleAngleTo(x,y){
var absAngle=degrees(atan2(y-this.y,x-this.x));
var angle=((absAngle-this.angle)+360)%360.0;return angle;}
function turtleTurnToward(x,y,d){var angle = this.angleTo(x,y);if(angle< 180){
this.angle+=d;}else{this.angle-=d;}}function turtleSetColor(c){this.color=c;}
function turtleSetWeight(w){this.weight=w;}function turtleFace(angle){
this.angle = angle;}function makeTurtle(tx,ty){var turtle={x:tx,y:ty,
angle:0.0,penIsDown:true,color:color(128),weight:1,left:turtleLeft,
right:turtleRight,forward:turtleForward, back:turtleBack,penDown:turtlePenDown,
penUp:turtlePenUp,goto:turtleGoTo, angleto:turtleAngleTo,
turnToward:turtleTurnToward,distanceTo:turtleDistTo, angleTo:turtleAngleTo,
setColor:turtleSetColor, setWeight:turtleSetWeight,face:turtleFace};
return turtle;}
I really enjoyed the camera interaction aspect of the text rain assignment, which is why I wanted to work more with live cam for this project and create something where the user has even more direct control. Overall this project was a lot more challenging than I expected, but that also made it more rewarding when I finally got it to work. I’m glad I went with color recognition for the marker because it allows for precise control and is more forgiving towards the user in terms of what environment they should be in when using this program. The most time-consuming and unexpected challenge was the lagging and freezing that comes with working with so many pixels, so trying to figure out what was causing the freezing took a lot longer than changing the code to fix it. Since the animations are on the simple side, I decided to include 3 different options. Ideally the animations would’ve been more complex, that’s something I hope to keep working on in the future. Below are screenshots from me using the program to give an idea of how it would look like when it’s working.
]]>
// Joanne Lee
// Section C
// joannele@andrew.cmu.edu
// Term Project
// Visual Quadrant Map:
// Q1 Q2 Q3
// Q4 Q5 Q6
// Q7 Q8 Q9
// logistics
var game = "inactive"
var fCount = 150;
var playBoardW = 270;
// playing board variables
var boardWeight = 10;
var playBoxW = 270;
var effectCounter = 6;
// white, orange, gray box variables
var boxW = playBoardW / 4;
var boxX = [165.625, 250, 334.375];
var boxY = [240.625, 325, 409.375];
var grayBoxes = [];
var newOrange = false;
var whiteX = 0;
var whiteY = 2;
var orangeX = 2;
var orangeY = 0;
// score keeper
var score = 0;
var bestScore = 0;
// sound variables
var coin;
var boing;
function preload() {
coin = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/coin.wav");
coin.setVolume(0.5);
boing = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/boing.wav");
boing.setVolume(0.5);
}
function setup() {
createCanvas(500,650);
frameRate(100);
}
function draw() {
background(255,211,222);
// "growth" effect seen when white + gray boxes collide
if (effectCounter < 2) { // staggered growth effect
playBoxW = 280;
}
if (effectCounter > 2 & effectCounter < 4) { // staggered growth effect
playBoxW = 290;
}
if (effectCounter > 4 & effectCounter < 6) { // staggered growth effect
playBoxW = 280;
}
if (effectCounter > 6) { // staggered growth effect
playBoxW = 270;
}
effectCounter += 1;
drawPlayingBoard();
drawPlayerBox();
drawGoalBox();
displayScore();
if (game == "inactive") {
displayStarterText();
}
if (game == "active") {
drawGrayBox();
// control speed that new boxes are generated
if (frameCount % fCount == 0 & frameCount != 0) {
generateGrayBox();
}
updateGrayBox();
checkCollision();
}
}
function displayScore() {
fill(255,80); // display current score
textSize(200);
textStyle(BOLD);
textFont('Helvetica');
if (score < 10) {
text(score, width - 120, height - 10);
}
else if (score < 100) { // shift over one spot for 2 digit score
text(score, width - 225, height - 10);
}
else if (score < 999) {
text(score, width - 330, height - 10 );
}
if (score >= bestScore) { // record the best score
bestScore = score
}
fill(255); // display the best score
textSize(20);
textStyle(BOLD);
textFont('Helvetica');
text("High Score " + bestScore, 110, height-160);
}
function displayStarterText() {
fill(40,99);
textSize(20);
textStyle(BOLD);
textFont('Helvetica');
text("Press enter to begin", 155, height/2);
}
function drawPlayingBoard() { // light pink w/ thick white border
fill(255,211,222);
stroke(255);
strokeWeight(boardWeight);
rectMode(CENTER);
rect(width / 2, height / 2, playBoxW, playBoxW);
}
function drawPlayerBox() { // white box, starts in Q7
strokeWeight(0);
fill(255);
rect(boxX[whiteX], boxY[whiteY], boxW, boxW);
}
function drawGoalBox() { // orange box, starts in Q3
fill(255,170,107);
rect(boxX[orangeX], boxY[orangeY], boxW*0.7, boxW*0.7);
}
function randomOrangeXY() { // generate random position for orange box
var randX = int(random(0,3));
var randY = int(random(0,3));
// make sure orange x & y are in same line or column as previous position
while (randX == whiteX || randY == whiteY) {
randX = int(random(0,3));
randY = int(random(0,3));
}
orangeX = randX;
orangeY = randY;
}
function checkBoard() {
// if white box position = orange box, you need a new orange box
if (whiteX == orangeX & whiteY == orangeY) {
coin.play();
newOrange = true; // indicate need for new orange box
score += 1; // increment score
// increase speed at which new boxes spawn, cap at 90 frame count
if (score % 10 == 0 & score != 0 && fCount > 90) {
fCount -= 20;
}
}
// if board needs new orange box, randomly generate
if (newOrange == true) {
randomOrangeXY();
newOrange = false;
}
}
function generateGrayBox() { // will create a box with random attributes
if (int(random(0,2)) == 0) { // create a box going left or right
if (int(random(0,2)) == 0) { // create box going right
createBox(-23.625, boxY[int(random(0,3))], 1, 0);
}
else { // create box going left
createBox(523.625, boxY[int(random(0,3))], -1, 0);
}
}
else { // create a box going up or down
if (int(random(0,2)) == 0) { // create box going down
createBox(boxX[int(random(0,3))], -23.625, 0, 1);
}
else { // create box going up
createBox(boxX[int(random(0,3))], 673.625, 0, -1);
}
}
}
function createBox(tx, ty, dx, dy) { // store as an object in the array
var grayBox = { x: tx, y: ty,
dx: dx, dy: dy,
speed: 2.5, state: 'active'
}
grayBoxes.push(grayBox);
}
function updateGrayBox() { // moves the box along
for (var a = 0; a < grayBoxes.length; a ++) {
grayBoxes[a].x += grayBoxes[a].dx * grayBoxes[a].speed;
grayBoxes[a].y += grayBoxes[a].dy * grayBoxes[a].speed;
}
}
function drawGrayBox() {
for (var b = 0; b < grayBoxes.length; b ++) {
fill(107,105,107);
rect(grayBoxes[b].x, grayBoxes[b].y, boxW*0.7, boxW*0.7);
}
}
function checkGrayBox() {
for (var c = 0; c < grayBoxes.length; c ++) {
// delete box from array once it is off the screen
if (grayBoxes[c].x < -24 || (grayBoxes[c].x > 524) ||
(grayBoxes[c].y < -24) || (grayBoxes[c].y > 674)) {
count += 1;
grayBoxes.splice(c,1);
}
}
}
function checkCollision() {
for (var d = 0; d < grayBoxes.length; d ++) {
if ((grayBoxes[d].x + boxW * 0.35) > (boxX[whiteX] - boxW * 0.5) &
(grayBoxes[d].x - boxW * 0.35) < (boxX[whiteX] + boxW * 0.5) &&
(grayBoxes[d].y - boxW * 0.35) < (boxY[whiteY] + boxW * 0.5) &&
(grayBoxes[d].y + boxW * 0.35) > (boxY[whiteY] - boxW * 0.5)) {
gameReset();
break;
}
}
}
function gameReset() {
boing.play();
score = 0;
grayBoxes = [];
fCount = 150;
effectCounter = 0;
game = "inactive";
}
function keyPressed() {
if (game == "active") {
if (keyCode == UP_ARROW & whiteY > 0) {
whiteY -= 1;
}
if (keyCode == DOWN_ARROW & whiteY < 2) {
whiteY += 1;
}
if (keyCode == LEFT_ARROW & whiteX > 0) {
whiteX -= 1;
}
if (keyCode == RIGHT_ARROW & whiteX < 2) {
whiteX += 1;
}
}
if (game == "inactive" & keyCode == ENTER) {
game = "active"
generateGrayBox();
}
// CHECK BOARD: to see if white box "ate" orange box
checkBoard();
}
The objective of the game is quite simple: collect the orange box by using your arrow keys to move the white box while avoiding the incoming gray boxes!
As the game progresses, you’ll find that the gray boxes will appear quicker, creating more obstacles to dodge. The game was based off of a game I saw a while ago, but was unable to find again on the internet — so the idea is not original, however, the interface is!
Although seemingly simple, there were a lot of technical obstacles I had to overcome with the main one being having to make sure that too many gray boxes did not appear at once. I ran into some bugs along the way, but I believe that my current code is clean and efficient!
Something I also tried to focus on was simple, but meaningful UI. Attention to detail was put in to small things such as a visual (and audio) cue when you mess up or the placement of the score. Hopefully this is a soothing yet addictive game that will get people through finals week! It was fun to create something of my own.
]]>// Catherine Coyle
// Final Project
// Section C
// ccoyle@andrew.cmu.edu
var flowers = [];
// number of the tool determines which tool the mouse is
var tool = 0;
var seedType = 0;
var SEEDTYPES = ['daisy', 'sunflower', 'tulip', 'violet'];
// 0 = seed pack
// 1 = watering can
// 2 = shovel
var filenames = [];
var images = [];
var menu = false;
var points = 0;
var flowerCount = 0;
// will be used for animation
var frames = 0;
// loading in all my image assets
function preload() {
filenames[0] = 'https://i.imgur.com/WFbRW0R.png' // grown daisy
filenames[1] = 'https://i.imgur.com/KfHyjYc.png' // grown sunflower
filenames[2] = 'https://i.imgur.com/f5Naph6.png' // tulip grown
filenames[3] = 'https://i.imgur.com/RjLTKmz.png' // violet grown
filenames[4] = 'https://i.imgur.com/v4QTgQ2.png' // watering can
filenames[5] = 'https://i.imgur.com/Rj3iuaG.png' // watering can pouring
filenames[6] = 'https://i.imgur.com/1emAAfx.png' // daisy seed
filenames[7] = 'https://i.imgur.com/Sjj5ezu.png' // sunflower seed
filenames[8] = 'https://i.imgur.com/1HzYXus.png' // tulip seed
filenames[9] = 'https://i.imgur.com/cKFWiib.png' // violet seed
filenames[10] = 'https://i.imgur.com/z2DQqJT.png' // seeds plant
filenames[11] = 'https://i.imgur.com/NBEkiuR.png' // daisy sapling
filenames[12] = 'https://i.imgur.com/FVmfFxU.png' // sunflower sapling
filenames[13] = 'https://i.imgur.com/9tXiQKK.png' // tulip sapling
filenames[14] = 'https://i.imgur.com/irNCdQr.png' // violet sapling
filenames[15] = 'https://i.imgur.com/pvEMQE2.png' // shovel up
filenames[16] = 'https://i.imgur.com/WJ2MWlw.png' // shovel down
for (var i = 0; i < filenames.length; i++) {
images[i] = loadImage(filenames[i]);
}
}
function setup() {
createCanvas(480,480);
}
function draw() {
background(173, 214, 156);
// flowerCount is used to calculate points
flowerCount = 0
for (var i = 0; i < flowers.length; i++) {
// flowers stop being watered after about 10 seconds
if ((frames - flowers[i].startingF > 600) & (flowers[i].watered)){
flowers[i].watered = false;
flowers[i].startingF = frames;
}
// if they are not watered for 10 seconds, they wilt
else if ((frames - flowers[i].startingF > 600) &
(flowers[i].watered == false)) {
flowers[i].wilted = true;
}
// these if statements are just delegating how long it takes a flower to grow
if ((flowers[i].status == 'seed') &
(frames - flowers[i].statusF - flowers[i].wiltedFrames > 700) &&
(flowers[i].wilted == false)) {
flowers[i].status = 'sapling';
flowers[i].statusF = frames;
}
else if ((flowers[i].status == 'sapling') &
(frames - flowers[i].statusF - flowers[i].wiltedFrames > 1200) &&
(flowers[i].wilted == false)) {
flowers[i].status = 'grown';
flowers[i].statusF = frames;
}
// only non-wilted flowers are considered for points
if (flowers[i].wilted == false) {
flowerCount++;
}
flowers[i].draw();
}
// points increase every half-second
if (frames % 30 == 0) {
points += flowerCount;
}
// menu and points display
fill(87, 77, 221);
rect(0, 0, 200, 60);
fill(255);
textSize(30);
text('POINTS: ' + str(points), 0, 30);
textSize(10);
text('Press m to display the menu', 0, 50);
fill(0);
noStroke();
// different images are shown on the mouse based on the different tools
if (tool == 0) {
image(images[6 + seedType], mouseX, mouseY - 40);
}
else if (tool == 1) {
if (mouseIsPressed){
image(images[5], mouseX, mouseY - 40);
}
else {
image(images[4], mouseX, mouseY - 40);
}
}
else if (tool == 2) {
if (mouseIsPressed){
image(images[16], mouseX, mouseY - 40);
}
else {
image(images[15], mouseX, mouseY - 40);
}
}
// menu text
if (menu) {
fill(87, 77, 221);
rect(20, 20, 440, 440);
fill(255);
textSize(12);
text('-Grow a cute garden and gain points! \n \n \
-Use the left and right arrows to cycle through tools \n \n \
-The up and down arrows cycle through different types of flowers! \n \n \
-Blue circles mean that your plant is currently watered \n \n \
-Brown circles mean that it has wilted and you need to water it again! \n \n \
-Points only increase for non-wilted flowers \n \n \
-Press m again to go back to the game!', 30, 130);
}
// the continuous counter increases every time draw is called, time events are based on this
frames++;
}
function keyPressed() {
// right and left arrow commands switch between tools
if (keyCode == RIGHT_ARROW) {
tool++;
tool = tool % 3;
}
else if ((keyCode == LEFT_ARROW) & (tool > 0)) {
tool--;
tool = Math.abs(tool);
tool = tool % 3;
}
else if ((keyCode == LEFT_ARROW) & (tool == 0)) {
tool = 2;
}
// up and down arrows switch between flower types
// this only occurs if the user is currently using the seed tool
if ((tool == 0) & (keyCode == UP_ARROW)) {
seedType++;
seedType = seedType % SEEDTYPES.length;
}
else if ((tool ==0) & (keyCode == DOWN_ARROW) && (seedType > 0)) {
seedType--;
seedType = seedType % SEEDTYPES.length;
}
else if ((tool ==0) & (keyCode == DOWN_ARROW) && (seedType == 0)) {
seedType = 3;
}
if ((key == 'm') & (menu == false)) {
menu = true;
}
//pressing m opens the menu
else if ((key == 'm') & (menu)) {
menu = false;
}
}
function mousePressed() {
// clicking with the seed tool will plant a seed
if (tool == 0) {
newFlower = makeFlower(SEEDTYPES[seedType], mouseX, mouseY, seedType);
flowers.push(newFlower);
}
// clicking with the watering can waters the flower
if (tool == 1) {
for(var i = 0; i < flowers.length; i++) {
if ((dist(mouseX, mouseY, flowers[i].x, flowers[i].y) < 20) &
(flowers[i].wilted)) {
flowers[i].wilted = false;
flowers[i].wiltedFrames = 0;
flowers[i].startingF = frames;
}
else if ((dist(mouseX, mouseY, flowers[i].x, flowers[i].y) < 20)) {
flowers[i].watered = true;
flowers[i].startingF = frames;
}
}
}
// clicking with the shovel digs up and removes the flower
if (tool == 2) {
for (var i = 0; i < flowers.length; i++) {
if (dist(mouseX, mouseY, flowers[i].x, flowers[i].y) < 20) {
flowers.splice(i, 1);
}
}
}
}
// flower class
function makeFlower(type, x, y, typeNum) {
flower = {
type: type,
x: x,
y: y,
status: 'seed',
wilted: false,
draw: drawFlower,
watered: false,
imageNum: typeNum,
startingF: frames,
statusF: frames,
wiltedFrames: 0,
}
return flower
}
function drawFlower() {
fill(255);
// blue circle indicates flower has been watered
// circle size decreases as time watered runs out
if (this.watered) {
stroke('blue');
strokeWeight(.25);
noFill();
ellipse(this.x - 20, this.y - 20, 10, 10);
var waterTime = map(frames - this.startingF, 600, 0, 0, 10);
fill('blue');
noStroke();
ellipse(this.x - 20, this.y - 20, waterTime, waterTime);
}
// brown circles indicate a wilted flower
if (this.wilted) {
fill('brown')
ellipse(this.x - 20, this.y - 20, 10, 10);
this.wiltedFrames++;
}
// below if statements delegate which image to be drawn
if (this.status == 'seed') {
image(images[10], this.x - 20, this.y - 20);
}
else if (this.status == 'sapling') {
image(images[11 + this.imageNum], this.x - 20, this.y - 20);
}
else if (this.status == 'grown') {
image(images[this.imageNum], this.x - 20, this.y - 20);
}
}
I had so much fun with this!!!!!!
The instructions are all viewable in-game by going to the menu. It basically involves clicking and selecting tools with the arrow keys.
I really like idyllic and peaceful kind of games so I thought making a gardening game would be fun! I had originally wanted to have random interactions with animals but it turned out to be too much to do in the time frame (maybe I’ll do it on my own).
The graphics are not the best as I didn’t really realize how many I would have to draw going into the project but I think at the least they get the point across.
I was really happy with my time-based animations for this project as I feel like we didn’t do too much with those this semester. Additionally I took advantage of objects to make all my flowers.
I hope you like the game!
]]>Wait a few seconds… it’s loading!
Click to start! (Click mouse to see next visual)
//variables to load sound
var sound1;
var sound2;
var sound3;
var sound4;
var sound5;
var sound6;
//variable to switch between shapes and songs
var toggle = 0;
//variable for drawing astroid (toggle 2 shape)
var power = 33;
var r = 255;
function setup() {
createCanvas(500, 500, WEBGL);
amplitude = new p5.Amplitude();
frameRate(40);
}
function preload() {
sound1 = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/york.mp3");
sound1.setVolume(1);
sound2 = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/prettiestvirgin.mp3");
sound2.setVolume(1);
sound3 = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/purity.mp3");
sound3.setVolume(1);
sound4 = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/pizza.m4a");
sound4.setVolume(1);
sound5 = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/Siamese_Sea.mp3");
sound5.setVolume(1);
sound6 = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/perth.mp3");
sound6.setVolume(1);
}
function draw() {
background(0);
noFill();
stroke(255, 0 ,0);
//retrieves amplitude of song playing
var level = amplitude.getLevel();
//maps the amplitudes across values for use in shape later on
var twist = map(level, 0, .6, 0, .3);
var twist2 = twist * 15000;
//adjusts the size of the astroids
var twistSize = map(level, 0, .3, 75, 300);
//sophia's twists
var twisty1 = map(level, 0, 1, 0, .3);
var twisty2 = twisty1 * 5;
var twisty3 = twisty1 * 4;
//MIMI JIAO'S CODE
//first shape - Mimi's code
if (toggle === 1) {
rotateY(frameCount * twist / 100);
rotateX(frameCount * twist / 100);
rotateZ(frameCount * twist / 100);
for (var i = 0; i < twist2; i++) {
fill(i * sin(i), i * cos(i), 255);
beginShape();
vertex(i * cos(i), i, i - 1000);
vertex(i * .01, i * 0.1, i * .01);
vertex(i * sin(i), i * cos(i), i);
endShape(CLOSE);
}
}
//second shape (astroid) - Mimi's code
if (toggle === 2) {
rotateX(twist);
rotateY(twist);
rotateZ(twist);
//randomizes value so astroid will randomly be bright
var randomBright;
randomBright = random(255);
//first astroid
beginShape();
noFill();
for(var i = 0; i < twist2; i++) {
if (randomBright > 250) {
stroke(255, 0, 0);
} else {
stroke(twist * 900 * sin(i), twist * 300, sin(i) * twist * 900);
}
vertex(twistSize * (cos(i) ** power),
twistSize * (sin(i) ** power));
}
endShape();
//second astroid
push();
rotateZ(5);
rotateX(3);
rotateY(4);
beginShape();
noFill();
for(var i = 0; i < twist2; i++) {
stroke(twist * 300, twist * 900 * sin(i), sin(i) * twist * 900);
vertex(twistSize * (cos(i) ** power),
twistSize * (sin(i) ** power));
}
endShape();
pop();
//third astroid
push();
rotateZ(3);
rotateX(4);
rotateY(5);
beginShape();
noFill();
for(var i = 0; i < twist2; i++) {
if (randomBright > 250) {
stroke(255, 0, 0);
} else {
stroke(twist * 900 * sin(i), twist * 300, sin(i) * twist * 900);
}
vertex(twistSize * (cos(i) ** power),
twistSize * (sin(i) ** power));
}
endShape();
pop();
//fourth astroid
push();
rotateZ(4);
rotateX(3);
rotateY(5);
beginShape();
noFill();
for(var i = 0; i < twist2; i++) {
if (randomBright > 250) {
stroke(255, 0, 0);
} else {
stroke(twist * 900 * sin(i), twist * 300, sin(i) * twist * 900);
}
vertex(twistSize * (cos(i) ** power),
twistSize * (sin(i) ** power));
}
endShape();
pop();
//fifth astroid
push();
rotateZ(4);
rotateX(3);
rotateY(5);
beginShape();
noFill();
for (var i = 0; i < 250 * TWO_PI; i++) {
vertex(300 * (cos(i) ** power),
300 * (sin(i) ** power));
}
endShape();
pop();
}
//third shape - Mimi's code
if (toggle === 3) {
beginShape();
noFill();
//x and y coordinates
var x;
var y;
var t = TWO_PI;
var a = map(twist2, 0, width, 2, 10);
var n = map(twist2, 0, height, QUARTER_PI, HALF_PI);
var ma = map(a, 0, 200, 0, QUARTER_PI);
//shape1
push();
beginShape();
for(var i = 0; i < twist2; i++) {
noStroke();
fill(cos(twist2) * 100, cos(twist2) * 100, sin(twist2) * 100);
x = a * sin(ma) * ((n - 1) * cos(t) + cos((n - 1) * t)) / n;
y = a * sin(ma) * ((n - 1) * sin(t) - sin((n - 1) * t)) / n;
vertex(-i * sin(i), i * cos(i), i);
vertex(x, y);
t += QUARTER_PI;
}
endShape();
pop();
//shape2
push();
beginShape();
for(var i = 0; i < twist2; i++) {
noStroke();
fill(sin(twist2) * 100, cos(twist2) * 100, sin(twist2) * 100);
x = a * sin(ma) * ((n - 1) * cos(t) + cos((n - 1) * t)) / n;
rotateZ(-4);
y = a * sin(ma) * ((n - 1) * sin(t) - sin((n - 1) * t)) / n;
vertex(x, y);
vertex(i * sin(i) , i * cos(i), i);
t += HALF_PI;
}
endShape();
pop();
//accent shape3
push();
rotateX(frameCount * .003);
rotateY(frameCount * .004);
rotateZ(frameCount * .005);
beginShape();
for(var i = 0; i < twist2; i++) {
noStroke();
fill(sin(twist2) * 255, cos(twist2) * 255, sin(twist2) * 255);
x = a * sin(ma) * ((n - 1) * cos(t) + cos((n - 1) * t)) / n;
rotateZ(-4);
y = a * sin(ma) * ((n - 1) * sin(t) - sin((n - 1) * t)) / n;
vertex(x, y);
vertex(i * sin(i) , i * cos(i), i);
t += QUARTER_PI;
}
endShape();
pop();
}
//SOPHIA KIM's code below
// first "slide" for Sophia's Code - sphere
push();
if (toggle === 4) {
var radiusSphere1 = twisty2 * 200;
fill(232, 0, 0);
noStroke();
rotateY(frameCount * twisty2 / 1000);
rotateX(frameCount * twisty2 / 1000);
rotateZ(frameCount * twisty2 / 1000);
sphere(radiusSphere1);
var constrainR = constrain(mouseX, radiusSphere1, radiusSphere1);
fill('yellow');
noStroke();
rotateY(frameCount * twisty2 / 500);
rotateX(frameCount * twisty2 / 500);
rotateZ(frameCount * twisty2 / 500);
sphere(constrainR);
}
pop();
//first "slide" - lines behind the sphere
push();
if (toggle === 4) {
for (var i = 0; i < twisty2 * 1000; i++) {
stroke('red');
beginShape();
vertex(i * cos(i), i, i - 2000);
vertex(i * .01, i * 0.09, i * .1);
vertex(i * sin(i) , i * cos(i), i / 100);
endShape(CLOSE);
stroke('orange');
beginShape();
vertex(i * cos(i), i, i - 2000);
vertex(i * .01, i * 0.05, i * .1);
vertex(i * sin(i), i * cos(i), i / 500);
endShape(CLOSE);
}
}
pop();
//2nd "slide" for Sophia's code - lines
push();
if (toggle === 5) {
var Rfor2 = random(twisty2 * 140, 255);
var Gfor2 = random(twisty2 * 140, 255);
for (var i = 0; i < twisty2 * 3000; i++) {
stroke(Rfor2, Gfor2, 230);
strokeWeight(.4);
beginShape();
vertex(i * sin(i / 10), tan(sin(i / 20)) * 10);
vertex(i * sin(i / 20), sin(i / 100) * 20, cos(i / 50));
vertex(tan(i / 10), cos(i / 100), cos(i * 100));
vertex(sin(i / 20), tan(i / 50) * 40, sin(i * 5) / 20);
endShape(CLOSE);
}
}
pop();
//3rd "slide" for Sophia's code -
//multiple circles moving around
push();
if (toggle === 6) {
for(var j = 0; j < 4; j++){
var Rfor3 = random(twisty3 * 200, 255);
stroke(Rfor3, 100, 240);
for(var i = 0; i < twisty3 * 3000; i++){
translate(sin(twisty3 * 0.4 + j) * 20,
sin(twisty3 * 0.1 + j) * 20, i * 3);
rotateX(frameCount * .3 / 5000 / twisty3);
rotateY(frameCount * .2 / twisty3 / 100);
rotateZ(frameCount * .5 / twisty3 / 300);
push();
sphere(14, 7, 5);
pop();
}
}
}
pop();
}
function mousePressed() {
//reset to first shape/song after 6th song
if (toggle < 6) {
toggle ++;
} else {
toggle = 1;
}
//play songs based on mouse click sequence
if (toggle === 1) {
sound1.play();
sound6.stop();
}
if (toggle === 2) {
sound2.play();
sound1.stop();
}
if (toggle === 3) {
sound3.play();
sound2.stop();
}
if (toggle === 4) {
sound4.play();
sound3.stop();
}
if (toggle === 5) {
sound5.play();
sound4.stop();
}
if (toggle === 6) {
sound6.play();
sound5.stop();
}
}
For the final project, we created various types of visuals that respond to different songs’ amplitude levels. We were interested in exploring how sound can be translated visually and wanted to challenge ourselves and try something new. So instead of using 2D, we decided to explore the basics of 3D with WEBGL.
We wanted to further explore sound and graphics as one, so we wanted to directly tie the image of the graphics to the amplitude of the music playing. We used shapes like spheres, and beginShape/endShape to create the visuals and played around with implementing trigonometric functions to create curves and other shapes. We wanted to create something that the viewer could flip through, so we made this click-through visual presentation. By clicking on the mouse, the user is able to see different visuals each with its own song.
Have fun and we hope you enjoy it
Instructions:
To operate the television, click the power button and use the up and down buttons to cycle through the channels. Come back at a different time of the day to see what else is on! Make sure your webcam access is allowed as well.
var power = false; // power starts off
var h; // hour
var gCLickCount; // track mouse clicks
var channelIsCurrently = 0; // what index of the array is at
var channel = []; // array to store current videos
var myCaptureDevice; // laptop camera
var chanDistUp; // distance to channel up
var chanDistDown; // distance to channel down
var powDist; // distance to power button
var videos;
var buttonsPositionX = 485;
var powerbutton = {x: buttonsPositionX, y: 230, d: 60, render:drawPowerButton,
strokecol:'burlywood', col:'lightyellow',
labelcol:'burlywood'};
var channelUp = {x: buttonsPositionX, y:390 , w:60 , h:60 , render:drawChannelUp,
strokecol:'burlywood', col:'lightyellow'};
var channelDown = {x: buttonsPositionX, y:450 , w:60 , h:60 , render:drawChannelDown,
strokecol:'burlywood', col:'lightyellow'};
function setup() {
createCanvas(550, 600);
frameRate(30);
myCaptureDevice = createCapture(VIDEO);
myCaptureDevice.size(350, 308); // attempt to size the camera.
myCaptureDevice.hide(); // this hides unnecessary extra view.
h = hour(); // what time is it?
//load videos, assign each video a variable
var mVid1 = createVideo('https://i.imgur.com/6OBMn2v.mp4');
var mVid2 = createVideo('https://i.imgur.com/X73HsOP.mp4');
var mVid3 = createVideo('https://i.imgur.com/AHmztFm.mp4');
var aVid1 = createVideo('https://i.imgur.com/wNWUrAi.mp4');
var aVid2 = createVideo('https://i.imgur.com/5nEKwzC.mp4');
var aVid3 = createVideo('https://i.imgur.com/FbpKnv0.mp4');
var eVid1 = createVideo('https://i.imgur.com/ziaEsYx.mp4');
var eVid2 = createVideo('https://i.imgur.com/4kGyLnf.mp4');
var eVid3 = createVideo('https://i.imgur.com/arD9T0D.mp4');
var nVid1 = createVideo('https://i.imgur.com/5IfBxXm.mp4');
var nVid2 = createVideo('https://i.imgur.com/ziyI0g4.mp4');
var nVid3 = createVideo('https://i.imgur.com/fPyKK17.mp4');
videos = [mVid1, mVid2, mVid3, aVid1, aVid2, aVid3, eVid1,
eVid2, eVid3, nVid1, nVid2, nVid3]; // array of video variables
for (var i = 0; i < videos.length; i++){
videos[i].hide(); //hide off canvas videos
videos[i].loop(); // play videos on loop
}
// Which videos are on the different channels
// if the hour is before 6 am
if(h <= 6){
channel = [mVid1, mVid2, mVid3];
// if hour is between 6am and 12 pm
}else if (h > 6 & h <= 12){
channel = [aVid1,aVid2, aVid3];
// if hour is between 12apm and 6pm
}else if(h > 12 & h <= 18){
channel = [eVid1, eVid2, eVid3];
// if hour is after 6pm
}else{
channel = [nVid1,nVid2, nVid3];
}
}
function draw() {
scale(0.8, 0.8)
// distance between mouse click and the different buttons
chanDistUp = dist(mouseX, mouseY, channelUp.x * 0.8, channelUp.y * 0.8);
chanDistDown = dist(mouseX, mouseY, channelDown.x * 0.8, channelDown.y * 0.8);
powDist = dist(mouseX, mouseY, powerbutton.x * 0.8, powerbutton.y *0.8);
television(0, 40);
// if the tv is on, show the video on the current channel
if(power === true){
image(channel[channelIsCurrently % channel.length],
buttonsPositionX - 444, 192, buttonsPositionX - 138, 305);
}
//if tv is off, you see your reflection on the screen
if(power === false){
myCaptureDevice.loadPixels(); // this must be done on each frame.
push();
tint(100, 50); // Display at half opacity
image(myCaptureDevice, buttonsPositionX - 440, 190); // draw the camera
pop();
}
//random tv noise, higher mouseY and mouseX is more noise
if(power === true){
for (var i = 0; i < mouseY + mouseX * 2; i++) {
var r = random(0, 255);
var g = random(0, 255);
var b = random(0, 255);
stroke(r, g, b);
point(random(buttonsPositionX - 445, 388),
random(buttonsPositionX - 298, 497));
}
}
}
function mousePressed(){
//click the channel up/down buttons when the power is on,
//the video will change
if(chanDistUp < channelUp.w / 2 & power === true) {
channelIsCurrently += 1;
}
if(chanDistDown < channelDown.w / 2 & power === true){
channelIsCurrently -= 1;
if(channelIsCurrently < 0){ // channels cycle, never goes above 2
channelIsCurrently = 2;
}
}
// if you click the power button, the tv will turn on
if(powDist < 30 & power === false){
power = true;
} else if (powDist < 30) {
power = false;
}
}
function drawPowerButton() {
ellipseMode(CENTER);
strokeWeight(10);
stroke(powerbutton.strokecol);
fill(powerbutton.col);
ellipse(powerbutton.x, powerbutton.y, powerbutton.d, powerbutton.d);
// power symbol
strokeWeight(3);
// hovering over the symbol changes its color
if(powDist < 30){
stroke(150);
}else{
stroke(powerbutton.labelcol);
}
// power symbol
noFill();
arc(buttonsPositionX, 232, 25, 25, PI + 2.5, PI + 7);
line(buttonsPositionX, 220, buttonsPositionX, 232);
}
function drawChannelUp() {
rectMode(CENTER);
strokeWeight(10);
stroke(channelUp.strokecol);
fill(channelUp.col);
rect(channelUp.x, channelUp.y, channelUp.w, channelUp.h, 5, 5);
strokeWeight(0);
// hovering over the symbol changes its color
if(chanDistUp < 30){
fill(150);
}else{
fill(channelUp.strokecol)
}
textAlign(CENTER);
textSize(40);
text('+', channelUp.x, channelUp.y + 10);
}
function drawChannelDown(){
rectMode(CENTER);
strokeWeight(10);
stroke(channelDown.strokecol);
fill(channelDown.col);
rect(channelDown.x, channelDown.y, channelDown.w, channelDown.h,
5, 5);
strokeWeight(0);
// hovering over the symbol changes its color
if(chanDistDown < 30){
fill(150);
}else{
fill(channelDown.strokecol);
}
textAlign(CENTER);
textSize(40);
text('-', channelDown.x, channelDown.y + 10);
}
function television(x, y){
rectMode(CORNER);
//tv body
stroke(63, 44, 35);
strokeWeight(5);
fill(63,44,35);
rect(0, y + 65, width, y + 440, 30, 30);
//tv body
fill('lightyellow');
rect(x + 10, y + 80, x + 425, y + 410, 10, 10);
// screen
fill(131, 123, 105);
stroke(63, 44, 35);
rect(x + 40, 190, 350, 308);
// place for tv knobs
fill(101,69,56);
strokeWeight(15);
rect(x + 425, y + 85, x + 115, y + 400);
strokeWeight(10);
stroke(63, 44, 35);
line(x + 320, 5, x + 390, 105);
line(x + 420, 5, x + 400, 105);
drawPowerButton(); // power button
drawChannelUp(); // channel up
drawChannelDown(); // channel down
}
Statement:
For my final project, I wanted to create a television that had different “programs” on at different times of the day. I was able to create this with a set of 12 different videos, three each for four different times of day (12am-6am, 6am-12pm, 12pm-6pm, 6pm-12am). I also had the buttons on the tv be mouse responsive: when hovering over the buttons they change a different color. As stated in my proposal, I was able to add a webcam component for when the tv is ‘off’ as if you were reflected in the screen like a glass tv. I added some noise on top of the videos controlled by mouse x and mouse y positions.
I feel like this project solidified a lot of the concepts we have learned throughout this semester. I definitely have a better grasp on how I can use p5.js in the future on my own projects as well as the core concepts behind computer programming.
]]>//Chrisitne Seo
//mseo1@andrew.cmu.edu
//Section C
//Final Project
//items, where they are placed
var smileX = 200, smile, smileY = 0;
var clover, cloverX = 100, cloverY = 0;
var drop, dropX = 250, dropY = 0;
var cat, catX = 495, catY = 0;
var fire, fireX = 380, fireY = 0;
var flower, flowerX = 430, flowerY = 0;
var heart, heartX = 330, heartY = 0;
var mad, madX = 20, madY = 0;
var star, starX = 140, starY = 0;
var thunder, thunderX = 150, thunderY = 0;
var startY = 0;
var beat1, beat2, beat3, beat4, beat5, beat7, beat8, beat9, beatS;
//speed that the items want to go
var directionY = 1, directionX = 0, direction1Y = 0.7, direction2Y = 2,direction3Y = 2.5, direction3Y = 2.5, direction4Y = 2.1, direction5Y = 0.4, direction6Y = 0.2, direction7Y = 1.5, direction8Y = 1.2, direction9Y = 0.2;
//no repetition of sounds when touched once
var lastFrameTouch, lastFrameTouch2, lastFrameTouch3, lastFrameTouch4, lastFrameTouch5, lastFrameTouch6, lastFrameTouch7, lastFrameTouch8, lastFrameTouch9;
//sound trackers
var ctracker, mic;
var volume = 0;
var currScene = 0;
//recording
var recorder;
var soundFile;
var state = 0;
//color of eyebrows
var r = 176;
var g = 196;
var b = 222;
function preload(){ //beats that each item plays
beat1 = loadSound("beats/1.mp3");
beat2 = loadSound("beats/2.mp3");
beat3 = loadSound("beats/3.mp3");
beat4 = loadSound("beats/4.mp3");
beat5 = loadSound("beats/5.mp3");
beat7 = loadSound("beats/7.mp3");
beat8 = loadSound("beats/8.mp3");
beat9 = loadSound("beats/9.mp3");
beatS = loadSound("beats/swoosh.mp3");
}
function setup() {
// setup camera capture
var videoInput = createCapture(VIDEO);
videoInput.size(550, 500);
videoInput.position(0, 0);
// setup canvas
var cnv = createCanvas(550, 500);
cnv.position(0, 0);
// setup tracker
ctracker = new clm.tracker();
ctracker.init(pModel);
ctracker.start(videoInput.elt);
// Create an Audio input
mic = new p5.AudioIn();
mic.start();
//recording
recorder = new p5.SoundRecorder();
recorder.setInput(mic);
soundFile = new p5.SoundFile();
//loading the items
smile = loadImage("images/smile.png");
clover = loadImage("images/clover.png");
drop = loadImage("images/drop.png");
cat = loadImage("images/cat.png");
fire = loadImage("images/fire.png");
heart = loadImage("images/heart.png");
flower = loadImage("images/flower.png");
mad = loadImage("images/mad.png");
star = loadImage("images/star.png");
//making sure each item is false when it didn't touch the face
lastFrameTouch = false;
lastFrameTouch2 = false;
lastFrameTouch3 = false;
lastFrameTouch4 = false;
lastFrameTouch5 = false;
lastFrameTouch6 = false;
lastFrameTouch7 = false;
lastFrameTouch8 = false;
lastFrameTouch9 = false;
}
function draw() {
//playing through by "scenes"
if(currScene == 0){
startScene();
} else if (currScene == 1){
camScene();
}
}
function startScene(){
//frame
push();
noFill();
stroke("PaleVioletRed");
strokeWeight(50);
rect(0,0,width,height);
pop();
//start button
stroke("LightSteelBlue");
strokeWeight(5);
fill(0);
rect(200,365,170,100,60);
push();
fill("PaleVioletRed");
strokeWeight(3);
textSize(50);
textStyle(BOLD);
stroke("PapayaWhip");
text("start",232,430);
//intstructions
fill(0);
textSize(20);
text("Catch each item",310,70);
text("with your eyebrows",310,100);
text("in order to make",310,130);
text("your own soundtrack!",310,160);
textSize(13);
stroke("LightSteelBlue");
text("*fyi: need to refresh if",350,190);
text("face detection jitters",350,210);
stroke("PaleVioletRed");
text("*play with speakers",350,230);
pop();
}
function camScene(){
//face tracker codes
clear();
//getting mic
var v = mic.getLevel();
//getting volume
volume += (v-volume)/3;
var detectionScore = ctracker.getScore();
if (detectionScore > 0) {
//location points of the faces
var positions = ctracker.getCurrentPosition();
var leftBrowX = positions [20][0];
var leftBrowY = positions [20][1];
var rightBrowX = positions [16][0];
var rightBrowY = positions [16][1];
var faceLeftX = positions [1][0];
var faceLeftY = positions [1][1];
var faceRightX = positions [13][0];
var faceRightY = positions [13][1];
var noseX = positions [62][0]
var noseY = positions [62][1];
var eyeLeftX = positions [27][0];
var eyeLeftY = positions [27][1];
var eyeRightX = positions [32][0];
var eyeRightY = positions [32][1];
//corresponding the face feature sizes by volume
var mouthSize = map(volume, 0,0.3, 70, 100);
var eyeSize = map(volume,0,0.3,45,75);
var eyeballSize = map(volume,0,0.3,40,70);
var noseSize = map(volume,0,0.3,25,55);
var eyebrowSize = map(volume,0,0.3,20,30);
rect(5,5,230,40);
fill(0);
textSize(15);
text('Double click to pause & record', 18, 25);
//smile sounds
if (smileX > leftBrowX - 50 & smileX < leftBrowX + 50 && smileY > leftBrowY - 20 && smileY < leftBrowY + 20){
directionY = 0; //stop when it touches the eyebrow until brow moves again
r = 255; //change colors of eyebrows in relation to item
g = 255;
b = 0;
if(! lastFrameTouch2){
beat2.loop();
lastFrameTouch2 = true; //play sound once
r = 255; //change colors of eyebrows in relation to item
g = 255;
b = 0;
} //same as about but for right brow placements
} else if (smileX > rightBrowX - 50 & smileX < rightBrowX + 50 && smileY > rightBrowY - 20 && smileY < rightBrowY + 20){
directionY = 0;
if(! lastFrameTouch2){
beat2.loop();
lastFrameTouch2 = true;
} //if item stops touching the face, continue falling down
} else {
directionY = 1.5;
}
//fire sounds
if (fireX > leftBrowX - 50 & fireX < leftBrowX + 50 && fireY > leftBrowY - 20 && fireY < leftBrowY + 20){
direction4Y = 0; //stop when it touches the eyebrow until brow moves again
r = 255; //change colors of eyebrows in relation to item
g = 125;
b = 65;
if(! lastFrameTouch){
beat1.loop();
lastFrameTouch = true; //play sound once
} //same as about but for right brow placements
} else if (fireX > rightBrowX - 50 & fireX < rightBrowX + 50 && fireY > rightBrowY - 20 && fireY < rightBrowY + 20){
direction4Y = 0;
r = 255;
g = 125;
b = 65;
if(! lastFrameTouch){
beat1.loop();
lastFrameTouch = true;
} //if item stops touching the face, continue falling down
} else {
direction4Y = 1.6;
}
//clover sounds
if (cloverX > leftBrowX - 50 & cloverX < leftBrowX + 50 && cloverY > leftBrowY - 20 && cloverY < leftBrowY + 20){
direction1Y = 0; //stop when it touches the eyebrow until brow moves again
r = 75; //change colors of eyebrows in relation to item
g = 185;
b = 105;
if(! lastFrameTouch3){
beat3.loop();
lastFrameTouch3 = true; //play sound once
} //same as about but for right brow placements
} else if (cloverX > rightBrowX - 50 & cloverX < rightBrowX + 50 && cloverY > rightBrowY - 20 && cloverY < rightBrowY + 20){
direction1Y = 0;
r = 75;
g = 185;
b = 105;
if(! lastFrameTouch3){
beat3.loop();
lastFrameTouch3 = true;
} //if item stops touching the face, continue falling down
} else {
direction1Y = 0.9;
}
//drop sounds
if (dropX > leftBrowX - 50 & dropX < leftBrowX + 50 && dropY > leftBrowY - 20 && dropY < leftBrowY + 20){
direction2Y = 0; //stop when it touches the eyebrow until brow moves again
r = 130; //change colors of eyebrows in relation to item
g = 200;
b = 255;
if(! lastFrameTouch4){
beat4.loop();
lastFrameTouch4 = true; //play sound once
} //same as about but for right brow placements
} else if (dropX > rightBrowX - 50 & dropX < rightBrowX + 50 && dropY > rightBrowY - 20 && dropY < rightBrowY + 20){
direction2Y = 0;
r = 130;
g = 200;
b = 255;
if(! lastFrameTouch4){
beat4.loop();
lastFrameTouch4 = true;
} //if item stops touching the face, continue falling down
} else {
direction2Y = 0.73;
}
//cat sounds
if (catX > leftBrowX - 50 & catX < leftBrowX + 50 && catY > leftBrowY - 20 && catY < leftBrowY + 20){
direction3Y = 0; //stop when it touches the eyebrow until brow moves again
r = 160; //change colors of eyebrows in relation to item
g = 160;
b = 160;
if(! lastFrameTouch5){
beat5.loop();
lastFrameTouch5 = true; //play sound once
} //same as about but for right brow placements
} else if (catX > rightBrowX - 50 & catX < rightBrowX + 50 && catY > rightBrowY - 20 && catY < rightBrowY + 20){
direction3Y = 0;
r = 160;
g = 160;
b = 160;
if(! lastFrameTouch5){
beat5.loop();
lastFrameTouch5 = true;
} //if item stops touching the face, continue falling down
} else {
direction3Y = 0.65;
}
//flower sounds
if (flowerX > leftBrowX - 50 & flowerX < leftBrowX + 50 && flowerY > leftBrowY - 20 && flowerY < leftBrowY + 20){
direction5Y = 0; //stop when it touches the eyebrow until brow moves again
r = 250; //change colors of eyebrows in relation to item
g = 250;
b = 250;
if(! lastFrameTouch6){
beat8.loop();
lastFrameTouch6 = true; //play sound once
} //same as about but for right brow placements
} else if (flowerX > rightBrowX - 50 & flowerX < rightBrowX + 50 && flowerY > rightBrowY - 20 && flowerY < rightBrowY + 20){
direction5Y = 0;
r = 250;
g = 250;
b = 250;
if(! lastFrameTouch6){
beat8.loop();
lastFrameTouch6 = true;
} //if item stops touching the face, continue falling down
} else {
direction5Y = 0.8;
}
//heart sounds
if (heartX > leftBrowX - 50 & heartX < leftBrowX + 50 && heartY > leftBrowY - 20 && heartY < leftBrowY + 20){
direction6Y = 0; //stop when it touches the eyebrow until brow moves again
r = 200; //change colors of eyebrows in relation to item
g = 0;
b = 50;
if(! lastFrameTouch7){
beat7.loop();
lastFrameTouch7 = true; //play sound once
} //same as about but for right brow placements
} else if (heartX > rightBrowX - 50 & heartX < rightBrowX + 50 && heartY > rightBrowY - 20 && heartY < rightBrowY + 20){
direction6Y = 0;
r = 200;
g = 0;
b = 50;
if(! lastFrameTouch7){
beat7.loop();
lastFrameTouch7 = true;
} //if item stops touching the face, continue falling down
} else {
direction6Y = 1.65;
}
//mad sounds
if (madX > leftBrowX - 50 & madX < leftBrowX + 50 && madY > leftBrowY - 20 && madY < leftBrowY + 20){
direction7Y = 0; //stop when it touches the eyebrow until brow moves again
r = 250; //change colors of eyebrows in relation to item
g = 0;
b = 0;
if(! lastFrameTouch8){
beat9.loop();
lastFrameTouch8 = true; //play sound once
} //same as about but for right brow placements
} else if (madX > rightBrowX - 50 & madX < rightBrowX + 50 && madY > rightBrowY - 20 && madY < rightBrowY + 20){
direction7Y = 0;
r = 250;
g = 0;
b = 0;
if(! lastFrameTouch8){
beat9.loop();
lastFrameTouch8 = true;
} //if item stops touching the face, continue falling down
} else {
direction7Y = 1.3;
}
//star sounds
if (starX > leftBrowX - 50 & starX < leftBrowX + 50 && starY > leftBrowY - 20 && starY < leftBrowY + 20){
direction8Y = 0; //stop when it touches the eyebrow until brow moves again
r = 240; //change colors of eyebrows in relation to item
g = 220;
b = 50;
if(! lastFrameTouch9){
beatS.loop();
lastFrameTouch9 = true; //play sound once
} //same as about but for right brow placements
} else if (starX > rightBrowX - 50 & starX < rightBrowX + 50 && starY > rightBrowY - 20 && starY < rightBrowY + 20){
direction8Y = 0;
r = 240;
g = 220;
b = 50;
if(! lastFrameTouch9){
beatS.loop();
lastFrameTouch9 = true;
} //if item stops touching the face, continue falling down
} else {
direction8Y = 0.45;
}
//eyebrow
stroke(r,g,b);
fill("PapayaWhip");
strokeWeight(7);
rectMode(RADIUS);
rect(leftBrowX,leftBrowY,eyebrowSize + 20,eyebrowSize-20);
rect(rightBrowX,rightBrowY,eyebrowSize + 20,eyebrowSize-20);
//eyes
stroke("PapayaWhip");
strokeWeight(2);
fill(0);
ellipse(eyeLeftX,eyeLeftY,eyeSize,eyeSize);
ellipse(eyeRightX,eyeRightY,eyeSize,eyeSize);
ellipse(positions[27][0],positions[27][1],eyeballSize,eyeballSize);
ellipse(positions[32][0],positions[32][1],eyeballSize,eyeballSize);
//nose
stroke("PaleVioletRed");
fill("LightSteelBlue");
ellipse(positions[62][0],positions[62][1],noseSize,noseSize+20);
//mouth
stroke("PapayaWhip");
fill("PaleVioletRed");
arc(positions[47][0],positions[47][1],mouthSize,mouthSize,0,PI);
}
//placing item and moving it downwards and back up when it reaches bottom
image(smile,smileX,smileY,50,50);
smileX += directionX;
smileY += directionY;
if (smileY > 460){
smileY = 0;
}
//placing item and moving it downwards and back up when it reaches bottom
image(clover,cloverX,cloverY,50,50);
cloverX += directionX;
cloverY += direction1Y;
if (cloverY > 460){
cloverY = 0;
}
//placing item and moving it downwards and back up when it reaches bottom
image(drop,dropX,dropY,50,50);
dropX += directionX;
dropY += direction2Y;
if (dropY > 460){
dropY = 0;
}
//placing item and moving it downwards and back up when it reaches bottom
image(cat,catX,catY,40,40);
catX += directionX;
catY += direction3Y;
if (catY > 460){
catY = 0;
}
//placing item and moving it downwards and back up when it reaches bottom
image(fire,fireX,fireY,50,55);
fireX += directionX;
fireY += direction4Y;
if (fireY > 460){
fireY = 0;
}
//placing item and moving it downwards and back up when it reaches bottom
image(flower,flowerX,flowerY,35,35);
flowerX += directionX;
flowerY += direction5Y;
if (flowerY > 460){
flowerY = 0;
}
//placing item and moving it downwards and back up when it reaches bottom
image(heart,heartX,heartY,40,40);
heartX += directionX;
heartY += direction6Y;
if (heartY > 460){
heartY = 0;
}
//placing item and moving it downwards and back up when it reaches bottom
image(star,starX,starY,40,40);
starX += directionX;
starY += direction8Y;
if (starY > 460){
starY = 0;
}
//placing item and moving it downwards and back up when it reaches bottom
image(mad,madX,madY,40,40);
madX += directionX;
madY += direction7Y;
if (madY > 460){
madY = 0;
}
}
function mousePressed(){
//when mouse pressed, it switches scenes
if (currScene == 0){
if (mouseX > 100 & mouseX < 500 && mouseY > 100 && mouseY < 300);
currScene =1;
} else if (currScene == 1){
if (mouseX > 0 & mouseX < width && mouseY > 0 && mouseY < height);
currScene = 2;
} else if (currScene == 2){
if (state === 0) {
// Tell recorder to record to a p5.SoundFile which we will use for playback
recorder.record(soundFile);
//buttons
fill("LightSteelBlue");
rect(5,5,230,40);
fill(0);
textSize(15);
text('Recording now! Click to stop.', 18, 25);
state++;
} else if (state === 1) {
recorder.stop(); // stop recorder, and send the result to soundFile
rect(5,5,288,40);
fill("PapayaWhip");
noStroke();
text('Recording stopped. Click to play & save', 18, 25);
state++;
} else if (state === 2) {
soundFile.play(); // play the result!
saveSound(soundFile, 'mySound.wav'); // save file
state++;
}
}
}
Caption: Video documentation of the final project
I realized that the WordPress does not run my project, so I provided a live online link to run my project: https://editor.p5js.org/mseo1/full/rkD_gLl0Q
For my final project, I was successfully able to perform what I wanted to display by adding audio (beats) and creating an instrument. This project is a fun game like instrument that lets you play beats (which I made my own beats using Garageband) with the items that are falling down. The beats are played when the items are touched with your eyebrows, which change colors corresponding with the color of the items. Additionally, the face detector adds an interesting visual addition to the project. The elements on the face (eyes, brows, nose, and mouth) increase/decrease in correlation to the sound of the audio, so you must run it with speakers, not headphones. The face recognition element was found on GitHub’s Javascript Library. Finally, you can record the audio by clicking the button on the top of the screen, and download the file. Overall, this was a fun project to perform, as well as an interesting element to my portfolio.
Zip File: Seo Final
]]>Project Description & Instructions Below!!
// Name: Rachel Park
// Andrew ID: rjpark
// 15-104 Section C
var i;
var currentkey = 'c';
var choreography = [];
var contemporary = [];
var groovy = [];
var footwork = [];
var isolation = [];
var pretty = [];
var swaggySassy = [];
var tutting = [];
var wavesWacking = [];
function preload() {
// database of dance moves
// loading the videos
contemporary = ['C1.MOV', 'C2.MOV', 'C3.MOV', 'C4.MOV', 'C5.MOV',
'C6.MOV', 'C7.MOV', 'C8.MOV', 'C9.MOV', 'C10.MOV', 'C11.MOV', 'C12.MOV',
'C13.MOV', 'C14.MOV', 'C15.MOV', 'C16.MOV', 'C17.MOV', 'C18.MOV',
'C19.MOV', 'C20.MOV', 'C21.MOV', 'C22.MOV', 'C23.MOV', 'C24.MOV',
'C25.MOV', 'C26.MOV', 'C27.MOV', 'C28.MOV', 'C29.MOV', 'C30.MOV',
'C31.MOV', 'C32.MOV', 'C33.MOV', 'C34.MOV', 'C35.MOV', 'C36.MOV',
'C37.MOV', 'C38.MOV',];
for (var j = 0; j < contemporary.length; j ++) {
contemporary[j] = './Moves/C/' + contemporary[j];
}
groovy = ['G1.MOV', 'G2.MOV', 'G3.MOV', 'G4.MOV', 'G5.MOV', 'G6.MOV',
'G7.MOV', 'G8.MOV', 'G9.MOV', 'G10.MOV', 'G11.MOV', 'G12.MOV',
'G13.MOV', 'G14.MOV'];
for (var j = 0; j < groovy.length; j ++) {
groovy[j] = './Moves/G/' + groovy[j];
}
footwork = ['F1.MOV', 'F2.MOV', 'F3.MOV', 'F4.MOV', 'F5.MOV', 'F6.MOV',
'F7.MOV', 'F8.MOV', 'F9.MOV', 'F10.MOV', 'F11.MOV', 'F12.MOV',
'F13.MOV', 'F14.MOV', 'F15.MOV', 'F16.MOV', 'F17.MOV', 'F18.MOV',
'F19.MOV', 'F20.MOV', 'F21.MOV'];
for (var j = 0; j < footwork.length; j ++) {
footwork[j] = './Moves/F/' + footwork[j];
}
isolation = ['I1.MOV', 'I2.MOV', 'I3.MOV','I4.MOV', 'I5.MOV', 'I6.MOV',
'I7.MOV', 'I8.MOV', 'I9.MOV', 'I10.MOV', 'I11.MOV', 'I12.MOV',
'I13.MOV', 'I14.MOV', 'I15.MOV', 'I16.MOV', 'I17.MOV', 'I18.MOV',
'I19.MOV', 'I20.MOV', 'I21.MOV', 'I22.MOV', 'I23.MOV', 'I24.MOV',
'I25.MOV', 'I26.MOV', 'I27.MOV', 'I28.MOV', 'I29.MOV', 'I30.MOV',
'I31.MOV', 'I32.MOV', 'I33.MOV', 'I34.MOV', 'I35.MOV'];
for (var j = 0; j < isolation.length; j ++) {
isolation[j] = './Moves/I/' + isolation[j];
}
pretty = ['P1.MOV', 'P2.MOV', 'P3.MOV', 'P4.MOV', 'P5.MOV', 'P6.MOV',
'P7.MOV', 'P8.MOV', 'P9.MOV', 'P10.MOV', 'P11.MOV', 'P12.MOV',
'P13.MOV', 'P14.MOV', 'P15.MOV', 'P16.MOV', 'P17.MOV', 'P18.MOV',
'P19.MOV', 'P20.MOV', 'P21.MOV', 'P22.MOV', 'P23.MOV', 'P24.MOV',
'P25.MOV', 'P26.MOV', 'P27.MOV', 'P28.MOV', 'P29.MOV'];
for (var j = 0; j < pretty.length; j ++) {
pretty[j] = './Moves/P/' + pretty[j];
}
swaggySassy = ['S1.MOV', 'S2.MOV', 'S3.MOV', 'S4.MOV', 'S5.MOV', 'S6.MOV',
'S7.MOV', 'S8.MOV', 'S9.MOV', 'S10.MOV', 'S11.MOV','S12.MOV', 'S13.MOV',
'S14.MOV', 'S15.MOV', 'S16.MOV', 'S17.MOV', 'S18.MOV', 'S19.MOV',
'S20.MOV', 'S21.MOV', 'S22.MOV', 'S23.MOV', 'S24.MOV', 'S25.MOV',
'S26.MOV', 'S27.MOV', 'S28.MOV', 'S29.MOV', 'S30.MOV', 'S31.MOV',
'S32.MOV'];
for (var j = 0; j < swaggySassy.length; j ++) {
swaggySassy[j] = './Moves/S/' + swaggySassy[j];
}
tutting = ['T1.MOV', 'T2.MOV', 'T3.MOV', 'T4.MOV', 'T5.MOV', 'T6.MOV',
'T7.MOV', 'T8.MOV', 'T9.MOV', 'T10.MOV', 'T11.MOV', 'T12.MOV',
'T13.MOV', 'T14.MOV', 'T15.MOV', 'T16.MOV', 'T17.MOV', 'T18.MOV',
'T19.MOV', 'T20.MOV', 'T21.MOV', 'T22.MOV', 'T23.MOV', 'T24.MOV',
'T25.MOV', 'T26.MOV', 'T27.MOV', 'T28.MOV', 'T29.MOV', 'T30.MOV',
'T31.MOV', 'T32.MOV', 'T33.MOV', 'T34.MOV', 'T35.MOV', 'T36.MOV',
'T37.MOV', 'T38.MOV'];
for (var j = 0; j < tutting.length; j ++) {
tutting[j] = './Moves/T/' + tutting[j];
}
wavesWacking = ['W1.MOV', 'W2.MOV', 'W3.MOV', 'W4.MOV', 'W5.MOV', 'W6.MOV',
'W7.MOV', 'W8.MOV', 'W9.MOV', 'W10.MOV', 'W11.MOV', 'W12.MOV',
'W13.MOV'];
for (var j = 0; j < wavesWacking.length; j ++) {
wavesWacking[j] = './Moves/W/' + wavesWacking[j];
}
}
function setup() {
createCanvas(700, 355);
background(0);
fill(255);
noStroke();
rect(600, 0, 100, 355);
}
function draw() {
// title
fill("pink");
stroke(0);
strokeWeight(3);
textAlign(CENTER);
textSize(30);
text("Make Your Own Choreography!!", 300, 45);
// box for reference
fill(255);
stroke("pink");
strokeWeight(3);
rectMode(CENTER);
rect(85, 170, 120, 140);
// text in reference box
fill(0);
noStroke();
textAlign(CENTER);
textSize(12);
text("c = contemporary", 85, 117);
text("f = footwork", 85, 133);
text("g = groovy", 85, 149);
text("i = isolation", 85, 165);
text("p = pretty/feminine", 85, 181);
text("s = swaggy/sassy", 85, 197);
text("t = tutting", 85, 213);
text("w = wavy/wacking", 85, 229);
fill(255);
stroke(0);
strokeWeight(1);
text("Letter Key Chart", 85, 90);
// box for which letter key is pressed
fill(255);
stroke("pink");
strokeWeight(3);
rectMode(CENTER);
rect(205, 127, 55, 55);
// display the letter key that's pressed
fill(0);
noStroke();
textAlign(CENTER);
textSize(40);
text(currentkey, 205, 140);
fill(255);
stroke(0);
strokeWeight(1);
textSize(12);
text("Last Key Pressed", 205, 90);
// box for play button
fill(255);
stroke("pink");
strokeWeight(3);
rectMode(CENTER);
rect(205, 213, 55, 55);
// display the play sign
fill(0);
noStroke();
triangle(195, 200, 195, 230, 220, 215);
fill(255);
stroke(0);
strokeWeight(1);
textSize(12);
text("Play Button", 205, 175);
// box for instructions
fill(255);
stroke("pink");
strokeWeight(3);
rectMode(CENTER);
rect(400, 170, 260, 140);
// text in instructions box
fill(0);
noStroke();
textAlign(LEFT);
textSize(10);
text("1. Look at the letter key chart & choose a letter to press",
280, 115);
text("2. Press the letter key (displayed in the box to the right)",
280, 134);
text("3. Keep pressing desired letter keys", 280, 153);
text("4. When finished pressing letter keys, click the play", 280, 172);
text("button to watch your choreography", 280, 191);
text("5. Repeat steps 1-4 to keep adding to choreography", 280, 210);
text("6. Refresh page to start over", 280, 229)
fill(255);
stroke(0);
strokeWeight(1);
textAlign(CENTER);
textSize(12);
text("Instructions", 400, 90);
// side note & credit/shoutout to dancers
fill(255);
stroke(0);
strokeWeight(1);
textAlign(LEFT);
textSize(10);
text("** The objective of this project is to help you create a sequence" +
"of dance moves (choreography) that can be adjusted", 30, 280);
text("and modified to match a song you choose when actually" +
"choreographing.", 30, 295);
text("** Also, shoutout to the featured dancers (by letter key chart);" +
"Léann Bahi, Elizabeth Kuo, Jackie Jiang, Chris Shon,", 30, 320);
text("Maggie Lyu, Emily Wu, Newton Xie, and Yuyan Sun.", 30, 335);
}
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
function keyTyped() {
document.getElementById("danceVideo").muted = true;
// assigning pressed keys to a dance move video
// playing that dance move video
// adding each dance move video into a new array called choreography
if (key === 'c') {
currentkey = key;
i = getRandomInt(0, contemporary.length);
choreography.push(contemporary[i]);
document.getElementById("video").src = str(contemporary[i]);
document.getElementById("danceVideo").load();
document.getElementById("danceVideo").play();
}
if (key === 'g') {
currentkey = key;
i = getRandomInt(0, groovy.length);
choreography.push(groovy[i]);
document.getElementById("video").src = str(groovy[i]);
document.getElementById("danceVideo").load();
document.getElementById("danceVideo").play();
}
if (key === 'f') {
currentkey = key;
i = getRandomInt(0, footwork.length);
choreography.push(footwork[i]);
document.getElementById("video").src = str(footwork[i]);
document.getElementById("danceVideo").load();
document.getElementById("danceVideo").play();
}
if (key === 'i') {
currentkey = key;
i = getRandomInt(0, isolation.length);
choreography.push(isolation[i]);
document.getElementById("video").src = str(isolation[i]);
document.getElementById("danceVideo").load();
document.getElementById("danceVideo").play();
}
if (key === 'p') {
currentkey = key;
i = getRandomInt(0, pretty.length);
choreography.push(pretty[i]);
document.getElementById("video").src = str(pretty[i]);
document.getElementById("danceVideo").load();
document.getElementById("danceVideo").play();
}
if (key === 's') {
currentkey = key;
i = getRandomInt(0, swaggySassy.length);
choreography.push(swaggySassy[i]);
document.getElementById("video").src = str(swaggySassy[i]);
document.getElementById("danceVideo").load();
document.getElementById("danceVideo").play();
}
if (key === 't') {
currentkey = key;
i = getRandomInt(0, tutting.length);
choreography.push(tutting[i]);
document.getElementById("video").src = str(tutting[i]);
document.getElementById("danceVideo").load();
document.getElementById("danceVideo").play();
}
if (key === 'w') {
currentkey = key;
i = getRandomInt(0, wavesWacking.length);
choreography.push(wavesWacking[i]);
document.getElementById("video").src = str(wavesWacking[i]);
document.getElementById("danceVideo").load();
document.getElementById("danceVideo").play();
}
}
function mouseClicked() {
// plays through all videos in choreography array
var nextVideos = function(i) {
document.getElementById('video').src = choreography[i];
var videoElement = document.getElementById('danceVideo');
videoElement.load();
videoElement.play();
// checks to see if there are videos left to play (i < array length)
// checks to see if current video has ended to play next video in array
if (i < choreography.length) {
videoElement.onended = function() {
nextVideos(i + 1);
}
}
}
// clicking the play button will play back entire choreography
if (mouseX > 205 - (55 / 2) & mouseX < 205 + (55 / 2)) {
if (mouseY > 213 - (55 / 2) && mouseY < 213 + (55 / 2)) {
nextVideos(0);
}
}
}
Statement
As stated in my final project proposal, I wanted to create something that was going to be personally interesting to me. So, I decided to create a visual and interactive computer keyboard dance generator. The objective of my project was to allow the user to compile a bunch of dance moves that are generated based off of the letter key that they pressed. This would help the user create their own choreography that can be adjusted and modified to fit a song they choose when they actually choreograph.
In order to create this, I had to collect a database of dance moves. So, I recorded various dancers on campus dancing in a specific dance style, and, from this, I gathered videos of 220 unique dance moves. Once I collected my videos, I loaded them into code and assigned specific dancers/dance style to different arrays. Next, I essentially paired certain letter keys to arrays so that, when the user presses a letter key, a random dance video (dance move) from the paired array would play. In addition, as the user presses a letter key, the corresponding dance videos are added to a “choreography” array which contains the entire choreography that the user makes and can be played by pressing the play button at the end.
My greatest struggle with this project was getting all the videos in the “choreography” array to play in succession. I had to do a lot of outside research to figure out how to see if one video has ended in the array for another one to start. I used document.getElementById().src, load, play, and onended to make this happen.
Below is a screenshot of what the project looks like as well as instructions on how to install and run it.
Screenshots
** can’t show the interactiveness of the project through screenshots; click the link below and follow instructions to use it!
Instructions
/*
Connor McGaffin
Section C
cmcgaffi@andrew.cmu.edu
Assignment 11-B
*/
var option = 3; //starting option
var w = 500; //width
var h = 300; //height
var w2 = w / 2; //width / 2
var h2 = h / 2; //height / 2
var x = []; //bubble starting pos
var y = [];
var dx = []; //bubble direction
var dy = [];
var col = []; //bubble color
var np = 50; // how many particles
var nb = 50; //how many bubbles
var title = ["Valerie","Hound Dog", "Step"];
var artist = ["Amy Winehouse", "Elvis Presley", "Vampire Weekend"];
var currentSpot = 200; //current spot and lengths are to be later configured with p5.js addons and Spotify connectivity
var songLength = 300; // placeholder
var tempo = [96,175,78,0]; //stored tempo of songs
var amp = new p5.Amplitude(); //get amplitude
var particles = [];
function preload() {
img = loadImage("https://i.imgur.com/K3YQPRm.png"); //load spotify logo
amy = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/amy.mp3"); //"Valerie" by Amy Winehouse
elvis = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/elvis.mp3"); //"Hound Dog" by Elvis Presley
vw = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/vw.mp3"); //"Step" by Vampire Weekend
}
function particleStep() {
this.x += this.dx;
this.y += this.dy / 5;
}
function particleDraw() { //draw particle
strokeWeight(this.s);
point(this.x, this.y);
}
function makeParticle(px, py, pdx, pdy, ps) { //create particle with starting pos and velocity
p = {x: px, y: py,
dx: pdx, dy: pdy, s: ps,
step: particleStep,
draw: particleDraw
}
return p;
}
function explode() {
for (var i = 0; i < np; i++) {// make a particle
fill(255);
stroke(255);
var p = makeParticle(w2, h2, random(-30,50), random(-30,50), random(5,10)); //initial location x/y quadrant velocity x/y, size
particles.push(p); // push the particle onto particles array
}
for (var i = 0; i < np; i++) { // for each particle
var p = particles[i];
p.step();
p.draw();
}
}
function record () {
noStroke();
fill(0);
ellipse (w2, h2 , 200); //vinyl
for (i = 0; i < 8; i++){
push();
noFill();
stroke(90);
strokeWeight(1);
ellipse(w2, h2, 190 - i * 30);
pop(); //texture
}
fill(200); // blue
ellipse(w2, h2, 75); //label
push();
translate(w2,h2);
rotate(frameCount * 5);
fill(150);
ellipse(15, 0, 5); //label spin
pop();
fill(255);
ellipse (w2, h2, 10); // peg
}
function tonearm () {
angleMode(DEGREES);
translate(w2 + 100, h2 - 100);
fill(255);
ellipse(0, 0, 30); //swivel
var tonearmPosition = map(currentSpot, 0, songLength, 0, 28);
push();
rotate(tonearmPosition);
push();
rectMode(CENTER);
rect(0,125, 12, 25); //cartridge
pop();
strokeWeight(3);
stroke(255);
line(0, 0, 0, 125); //bar
pop();
}
function header () {
image(img,10,10,20,20);
fill(255);
textSize(12);
text(title[option], 35, 20); //display title 12pt
textSize(9);
text(artist[option], 35, 30); //display artist 9pt
}
function setup() {
createCanvas(w, h);
for (i = 0; i < nb; i++) { //generate bubbles
x[i] = random(width); //randomly
y[i] = height; //at the bottom
dx[i] = random(-.5,.5); //that float sideways randomly
dy[i] = random(-2,-1); // and float virtically faster
col[i] = color(random(255)); //randomly filled in greyscale
}
}
function draw() {
background(10,0,0);
stroke(0);
strokeWeight(10);
explode();
var vol = amp.getLevel();
var bdiam = map(vol, 0, 1 , 5, 18);
var vdiam = map(vol, 0, 1, 200, 250); //vinyl amp diameter
for (i = 0; i < nb; i++) {
fill(col[i]);
noStroke();
ellipse(x[i], y[i], bdiam);
x[i] += dx[i];
y[i] += dy[i];
if (x[i] > width) { //bounce off right wall
x[i] = width - (x[i] - width);
dx[i] = -dx[i];
} else if (x[i] < 0) { //bounce off left wall
x[i] = -x[i];
dx[i] = -dx[i];
}
if (y[i] > height) { //bounce off bottom
y[i] = height - (y[i] - height);
dy[i] = -dy[i];
} else if (y[i] < 0) { //float off top, appear at bottom
y[i] = height;
}
}
push();
noStroke();
fill(180);
ellipse(w2,h2,vdiam,vdiam);
pop();
header();
record();
tonearm();
}
function mouseClicked() {
particles = []; //clear particles array
explode(); //create particle explosion
option ++; //next option when clicked
if(option > 3){
option = 0;
}
if(option == 0){
for(i = 0; i < nb; i++){
col[i] = color(0, 0, random(255)); //random blue
dx[i] = tempo[option] * random(-.1,.1) / 10; //horiz to tempo
}
vw.stop(); //stop "step"
amy.play(); // play "valerie"
}
if(option == 1){
for(i = 0; i < nb; i++){
col[i] = color(random(255),0,0); //random red
dx[i] = tempo[option] * random(-.1,.1) / 10; // horiz to tempo
}
amy.stop(); //stop "valerie"
elvis.play(); //play "hound dog"
}
if(option == 2){
for(i = 0; i < nb; i++){
col[i] = color(random(255)); //random greyscale
dx[i] = tempo[option] * random(-.1,.1) / 10; //horiz to tempo
}
elvis.stop(); //stop "hound dog"
vw.play(); //play "step"
}
if(option == 3){
vw.stop(); //stop "step"
for(i = 0; i < nb; i++){
col[i] = color(255); //bubbles fill white
dx[i] = random(-.1,.1); //horiz moment unrelated to tempo
}
}
}
Redesigning Spotify Chromecast
This projects is a brief redesign of Spotify’s current Chromecast interface. Pictured below is the current state of the Spotify Chromecast player, which displays the current song, some controls, its information, its album cover, and the album covers of tracks adjacent to it in the queue.
With Spotify Chromecast so frequently being used while hosting a social event, I would argue that the current interface is distracting from the event, or redundant at the least. The current layout would easily enable guests to continuously check which song is coming next, and which songs have already played, freely guiding listeners out of the enjoying the music in the moment. In addition to this, much of the information on the screen is only necessary for the DJ to know, and it is already being provided on their phone’s interface.
With this being said, I looked to create a new interface for Spotify when used on Chromecast that would allow listeners to stay in the moment of the music, while still providing an complimentary atmosphere to that the of the social event.
As the user plays songs, the generated bubbles behind the record and one large bubble behind the record itself jump to the amplitude of the music. The small floating bubbles move horizontally on their upward waltz in speed relation to their tempo.
This project functions as a mock-up of my Chromecast Spotify player as a living environment. Unfortunately, I was not able to configure the p5.js with the Spotify API, as it was out of the scope of 15-104, and thus only these three preloaded songs may be queued. These tracks were chosen with the intent of providing a brief range of visual possibilities this program can generate.
Controls
]]>
// Justin Yook
// jyook@andrew.cmu.edu
// Section C
// Final Project: Stage Master
var dancerArr = []; // Array of dancer objects
var siz = 20; // Diameter of dancers (fixed)
var cFrame = 0; // Current frame = current count (index)
var tFrames = 8; // Total number of frames = eight count
var c; //Current canvas
function setup() {
c = createCanvas(480, 360);
}
function draw() {
background(255);
// Create Grid
// Horizontal lines
for (var i = 0; i < height; i+=30) {
stroke(200);
strokeWeight(1);
line(0, i, width, i);
}
// Vertical lines
for (var i = 0; i < width; i+=30) {
stroke(200);
strokeWeight(1);
line(i, 0, i, height);
}
// Create bottom rectangle
fill(65, 105, 225);
rect(0, 300, width, height);
// Create count circles
for (var i = 0; i < 8; i++) {
fill(255);
ellipse(i * 50 + 66, 330, 2 * siz, 2 * siz);
fill(0);
textSize(14);
text(i + 1, i * 50 + 62, 335);
}
// Create upper rectangle
fill(65, 105, 225);
rect(0, 0, width, height - 300);
// Check and display dancer objects
for (var i = 0; i < dancerArr.length; i++) {
dancerArr[i].draw();
}
// Display title
fill(255);
textSize(20);
text("STAGE MASTER", width / 3, height / 8);
// Indicate which eight count you are currently on with green ellipse
if (cFrame == 0) {
fill(0, 255, 0);
ellipse(66, 330, 40, 40);
fill(0);
textSize(14);
text("1", 62, 335);
}
if (cFrame == 1) {
fill(0, 255, 0);
ellipse(116, 330, 40, 40);
fill(0);
textSize(14);
text("2", 112, 335);
}
if (cFrame == 2) {
fill(0, 255, 0);
ellipse(166, 330, 40, 40);
fill(0);
textSize(14);
text("3", 162, 335);
}
if (cFrame == 3) {
fill(0, 255, 0);
ellipse(216, 330, 40, 40);
fill(0);
textSize(14);
text("4", 212, 335);
}
if (cFrame == 4) {
fill(0, 255, 0);
ellipse(266, 330, 40, 40);
fill(0);
textSize(14);
text("5", 262, 335);
}
if (cFrame == 5) {
fill(0, 255, 0);
ellipse(316, 330, 40, 40);
fill(0);
textSize(14);
text("6", 312, 335);
}
if (cFrame == 6) {
fill(0, 255, 0);
ellipse(366, 330, 40, 40);
fill(0);
textSize(14);
text("7", 362, 335);
}
if (cFrame == 7) {
fill(0, 255, 0);
ellipse(416, 330, 40, 40);
fill(0);
textSize(14);
text("8", 412, 335);
}
}
function keyPressed() {
// Add new dancer at (mouseX, mouseY) when pressing spacebar
if (key == ' ') {
dancerArr.push(makeDancer(mouseX, mouseY, siz));
}
// Move to next eight count when pressing 'd'
if (key == 'd') {
cFrame += 1;
cFrame = cFrame % tFrames;
for (var i = 0; i < dancerArr.length; i++) {
if (dancerArr[i].posArr.length < cFrame) {
//x
dancerArr[i].posArr[cFrame][0] = dancerArr[i].posArr[cFrame - 1][0];
//y
dancerArr[i].posArr[cFrame][1] = dancerArr[i].posArr[cFrame - 1][1];
}
}
}
// Move to previous eight count when pressing 'a'
if (key == 'a') {
cFrame -= 1;
if (cFrame < 0) {
cFrame += 8;
}
}
// Download screenshot of current formation
if (key == "s") {
saveCanvas(c, 'formation', 'jpg');
}
}
// Click and drag dancer object
function mousePressed() {
for (var i = 0; i < dancerArr.length; i++) {
if (dist(mouseX, mouseY, dancerArr[i].posArr[cFrame][0], dancerArr[i].posArr[cFrame][1]) <= (dancerArr[i].ps / 2)) {
dancerArr[i].drag = true;
}
}
}
function mouseDragged() {
for (var i = 0; i < dancerArr.length; i++) {
if (dancerArr[i].drag == true) {
for (var j = cFrame; j < tFrames; j++) {
dancerArr[i].posArr[j][0] = mouseX;
dancerArr[i].posArr[j][1] = mouseY;
}
}
}
}
function mouseReleased() {
for (var i = 0; i < dancerArr.length; i++) {
dancerArr[i].drag = false;
}
}
//----------------------------------------------------------------------
// DANCER OBJECT
function makeDancer(x, y, s) {
var dancer = {"px": x, "py": y, "ps": s, "drag": false};
var posArr = [];
for (var i = 0; i < tFrames; i++) {
posArr.push([x, y]);
}
dancer.posArr = posArr;
dancer.draw = dancerDisplay;
return dancer;
}
function dancerDisplay() {
fill(0);
var cpos = this.posArr[cFrame];
ellipse(cpos[0], cpos[1], this.ps, this.ps);
}
Instructions:
‘space’ key: Add a new dancer at (mouseX, mouseY)
‘d’ key: Move to the next count
‘a’ key: Move to the previous count
’s’ key: Save a screenshot of the current formation
Mouse: Click, hold, and drag inside dancer to move the dancer
First add a dancer on the canvas, at your mouse position; click and drag the dancer to move them around. If you are satisfied with the placement of your dancers, then move to the next count by pressing ‘d’. If you want to change any previous formations, press ‘a’ to move to the previous count. You can also save a screenshot of the current formation by pressing ’s’.
Statement:
“Stage Master” is a tool for choreographers and dancers to visualize dance formations. I really enjoyed creating this project, because it is something that can help me make formations easily, and in an organized manner. The most challenging part of the project was developing how dancer objects were updated for each count because I had to understand nested arrays and objects very well. The current version of the program only works for a single set of eight counts, but in the future, I would want to add a feature that allows the user to navigate through sets of eight counts. In addition, it would be better if I were to include a feature where the user can play and pause music. Overall, the visuals are not that great, and the amount of code is not a lot, but I believe that the function of the program is useful.
]]>