Hannah Cai—Final Project

(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!

Sophie Chen – Final Project

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:

  1. grab something red (any relatively small red object would work best, or just pull up a red image on your phone)

  2. hold it within range of the webcam, with the red facing the camera

  3. 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

sketch

// 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.

shape 3 with camera input on, drawn with a red pen as color marker
shape 1 with camera output hidden
shape 2 with camera output hidden

 

 

Joanne Lee – Term Project

Term Project

// 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 – Garden Game

Garden Game

// 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!

Mimi Jiao and Sophia Kim – Final Project

Wait a few seconds… it’s loading! 🙂
Click to start! (Click mouse to see next visual)

sketch

//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 🙂






Alexandra Kaplan – Final Project

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.

Turn on the TV!



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.

Christine Seo – Final Project

sketch

//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++;
    }
	}
}

Start screen has instructions
When you touch an object it changes the the color of the eyebrows to the corresponding color (i.e clover = green)
The elements on the face vibrate corresponding to the audio
You can record your files with clicking the button
You can save your music as it saves as an audio file!

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

Rachel Park – Final Project

Project Description & Instructions Below!!

finalproject

// 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

  1. Press the link below (you will only get access with CMU Andrew email).
  2. Download the file (it is safe to download & it will take a while).
  3. Open the folder Final Project.
  4. Open the index.html file.
  5. Read the instructions on the web page to play!

Choreography Generator Download Link

sketch

/*
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

  • select anywhere on the canvas to play the next song, thus changing the color, size and flow of bubbles

 

Justin Yook- Final Project

stageMaster

// 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.