Final Project: Video Effects with a Pseudo Half Tone

I wanted to do something having to do with video effects and I eventually settled on this version. Ok for starters, I came across several challenges–> the first of which was, how am I going to embed this into my website? With some aggressive internet scouting, I dove deeper into javascript and jQuery learned about new functions like getContext() and offsetWidth()–> These were straight JS functions that I learned I could integrate nicely with html and css. I also edited in codepen so I could play around with html, css, and js at the same time.

Also with some experimenting I used const instead of var because I discovered that when I used var, the blocks became much too cumbersome.

Halftone large, no restrictions
halftone, with restrictions

This in addition to some other changes, I was able to create a much clearer video image.

The whole project can be found here on codePen.

Jason Zhu & Miranda Luong – Final Project

****Note: Project does not properly run on Safari. Run on Chrome.****
If you’d like, we’ve uploaded this zip file containing all assets to our project. Open this program like any other p5js sound sketch using a local server. To do so make sure you follow the instructions noted in https://courses.ideate.cmu.edu/15-104/f2018/lab-week-11/ under Task B: Triggering sound file playback.

sketch

/*
Issho, an audio visualizer by Miranda Luong and Jason Zhu.

Our final project is an audio visualizer named Ishho, derived from the Sino-Japanese
word for "impression". To say that Ishho is just an audio visualizer would be an 
understatement. Our high level goal was to give music its own unique visual identity
––similar to some of the work of Neil Harbisson who has created color portraits of various 
songs. Our secondary goal was to create this unique visual identity in real time.
*/

// Global audio variables.
var PREFIX = "https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/moi-je-bouge.mp3"
var song;
var amplitude;
var fft;
var peakDetect;

// Global line variables.
var nLines = 88;
var linesArray = [];
var nforces = 4;
var nParticles = 88;
var forcesArray = [];
var fillScreen = true;
var strokeW = 1;

// Preload song.
function preload(){
  song = loadSound(PREFIX);
}

function setup() {
  createCanvas(500, 500);
  frameRate(60);

  // Audio setup.
  amplitude = new p5.Amplitude();
  amplitude.setInput(song);
  fft = new p5.FFT();
  fft.setInput(song);
  peakDetect = new p5.PeakDetect(20,20000,.06,.00694444444);
  song.play();

  // Setup line and force particles.
  initialize();
}

function draw() {
  if (song.isPlaying()){

    // Start display with noFill(). Toggle between white and black backgrounds by
    // pressing 'spacebar' key and changing var fillScreen's boolean value.
    noFill();
    if (fillScreen){
      background(0);
    } else {
      background(255);
    }

    // Update audio analyzer.
    fft.analyze();
    peakDetect.update(fft);

    for (var i = 0; i < nforces; i++) {
      forcesArray[i].move();
    }

    // Standard radius
    var radius = 75 * cos(frameCount / 80);

    //If beat is detected, enlargen radius based on level of amplitude.
    if (peakDetect.isDetected){
      var radius = map(amplitude.getLevel(),.06, .3, 10, 150) * cos(frameCount/80);
    }

    // Setup a range of two colors for the gradient coloring of lines 
    // and have the gradient change as the song and animation progress.
    var highRed = map(song.currentTime()* 3.5, 0, song.duration() * 3, 255, 0);
    var highGreen = map(song.currentTime()*3.5, 0, song.duration() * 3, 0, 255);

    // Setup rate of gradient change between colors depending on amplitude 
    // of the song at that current time.
    var low = 30;
    var high = map(amplitude.getLevel(), 0, .125, 0, 255);
    
    for (var i = 0; i < linesArray.length; i++) {
      // Create interaction for every line with every force particle.
      linesArray[i].interact(radius, forcesArray[0].position.x, forcesArray[0].position.y);
      linesArray[i].interact(radius, forcesArray[1].position.x, forcesArray[1].position.y);
      linesArray[i].interact(-radius, forcesArray[2].position.x, forcesArray[2].position.y);
      linesArray[i].interact(-radius, forcesArray[3].position.x, forcesArray[3].position.y);
    
      // Color lines using a gradient.
      var col = lerp(low, high, i / linesArray.length);
      stroke(highRed, highGreen, col);

      // Change strokeweight of lines depending on amplitude of song at the given time.
      if (strokeW >= 5){
        strokeW = 1;
      }
      strokeWeight(strokeW);

      linesArray[i].draw();
    }
  }
}

function initialize() {
  // Create and store Lines into linesArray.
  for (var i = 0; i < nLines; i++) {
    linesArray[i] = new Line(42 + 4.8* i);
    linesArray[i].addParticles();
  }
  // Create and store force particles in forcesArray.
  for (var i = 0; i < nforces; i++) {
    if (i== 0){
    forcesArray[i] = new Particle(30+ (1) * 470 / 3, 42 + (1) * 423 / 3);
    }
    if (i == 1){
      forcesArray[i] = new Particle(30+ (2) * 470 / 3, 42 + (2) * 423 / 3);
    }
    if (i == 2){
      forcesArray[i] = new Particle(30+ (1) * 470 / 3, 42 + (2) * 423 / 3);
    }
    if (i == 3){
      forcesArray[i] = new Particle(30+ (2) * 470 / 3, 42 + (1) * 423 / 3);
    }

    // Start force particles with random velocities.
    var angle = random(0, TWO_PI);
    forcesArray[i].velocity.set(cos(angle), sin(angle));
  }
}

// Click to play and pause animation and song.
function mousePressed() {
  if (song.isPlaying()){
    song.pause();
  } else {
    song.play();
  }
}

function keyPressed() {
  // Toggle between black or white backgrounds by pressing 'spacebar'.
  if (key === ' ') {
    fillScreen = !fillScreen;
  }
  // Press 's' to increase strokeWeight or later reset to 1
  if (key === 's') {
    strokeW += 1;
  }
}

// Line class.
var Line = function(y){
  this.y = y;
  this.particlesArray = [];
}

// Add particles to lines particlesArray.
Line.prototype.addParticles = function(){
  for (var i = 0; i < nParticles; i++){
    this.particlesArray.push(new Particle(30 + 5 * i, this.y));
  }
}

// Connect all particles in line's particleArray to draw line.
Line.prototype.draw = function(){    
  beginShape();
    for (var i = 0; i < this.particlesArray.length; i++) {
      curveVertex(this.particlesArray[i].position.x, this.particlesArray[i].position.y);
    }
  endShape();
}


// Interact line with force particles by having all of 
// line's particles individually interact with force particles.
Line.prototype.interact = function(radius, xpos, ypos) { 
  for (var i = 0; i < this.particlesArray.length; i++) {
    this.particlesArray[i].interact(radius, xpos, ypos);
  }

  // Change size of line when necessary to make for smooth texture.
  for (var i = 0; i < this.particlesArray.length-1; i++) {
    var d = dist(this.particlesArray[i].position.x, this.particlesArray[i].position.y, 
                 this.particlesArray[i+1].position.x, this.particlesArray[i + 1].position.y);
    
    // Add a new Particle to particleArray when two neighbor particles are too far apart.
    if (d > 5) {
      var x = ((this.particlesArray[i].position.x + this.particlesArray[i + 1].position.x) / 2);
      var y = ((this.particlesArray[i].position.y + this.particlesArray[i + 1].position.y) / 2);
      this.particlesArray.splice(i + 1, 0, new Particle(x, y));
    }

    // Remove a particle when 2 neighbor particles are too close.
    if (d < 1) {
      this.particlesArray.splice(i, 1);
    }
  }     
}

// Particle class.
var Particle = function(x, y){
  this.position = createVector(x, y);
  this.velocity= createVector(0, 0);
  this.acceleration = createVector(0, 0);
}

// Updates force particles' positions.
Particle.prototype.move = function(){
  // Change direction of force particles sometimes.
  if (random(1) > .97){
    var angle = random(-PI, PI);
    this.acceleration.set(cos(angle), sin(angle));
    var mod = this.acceleration.angleBetween(this.velocity);
    mod = map(mod, 0, PI, 0.1, 0.001);
    this.acceleration.mult(mod); 
  }

  // Change pace of force particle's position change
  this.velocity.add(this.acceleration);

  // Stop if current amplitude reaches or surpasses 0.675.
  // Force particle to increase impact of interaction with lines.
  if (amplitude.getLevel() > .675){
      this.velocity.set(0, 0);
  }

  // Move force particle
  this.position.add(this.velocity);

  // Check edges.
  this.position.x = (this.position.x + width)%width;
  this.position.y = (this.position.y + height)%height;
}

// Force particle to line particle interaction.
Particle.prototype.interact = function(radius, xpos, ypos){
  var dir = radius/abs(radius);
  var radius = abs(radius);

  var r = dist(this.position.x, this.position.y, xpos, ypos);
  var angle = atan2(this.position.y - ypos, this.position.x - xpos);

  // If line particle is within radius of force particle,
  // change velocity to change position of line particle.
  if (r <= radius) {
    // If cuerrent amplitude is greater than .05, generate wider,
    // radial movement from particles to highlight song's beats.
    if (amplitude.getLevel() > .05){
      var radius = 2 * dir * (radius - r) / radius;
    }
    else{ 
      var radius = .3 * dir * (radius - r) / radius;
    }
    this.velocity.set(radius * cos(angle), radius * sin(angle));
  } else {
    this.velocity.set(0, 0);
  }
  this.position.add(this.velocity);
}

 

Preface
Our final project is an audio visualizer which we have named Ishho, derived from the Sino-Japanese word for “impression”. Our original proposal was to have ripples radiate from a central node that would affect geometry in order to create complex forms. While this still holds true to some extent, feedback in our project proposal in addition to further research, particularly the work of Japanese design studio, teamLab, led us to reconsider.

To say that Ishho is just an audio visualizer would be an understatement. Our high-level goal was to give music its own unique visual identity-similar to some of the work of Neil Harbisson who has created color portraits of various songs. Our secondary goal was to create this unique visual identity in real time.

Project Description
We accomplished our high-level and secondary goals by breaking down the audio into its duration, peaks, amplitudes. We used these variables to affect various aspects of our base canvas. The base canvas starts with straight lines that run across the page. These lines are colored with a gradient that changes according to the relation of the song’s current time to its overall duration. The rate of which the gradient changes from its bottom color to its top is governed by the amplitude of the song at that given time. So impactful beats are displayed onto these lines. In addition to this visualization, we used music to imprint the terrain that our base lines created. From the start, hidden force particles lightly push and interact with the lines, creating minimal impressions on our canvas, but when a beat is detected, the force particles’ effects are magnified. This effect is calculated by the amplitude of that beat and has the potential to create large valleys, dips and ridges in our canvas terrain. 

Division of Work
As per the guidelines and to make the project more feasible, we decided to divide the work into what we felt was an equitable 50-50 split. Miranda did the research on particle systems and from there coded the basis  for our audio visualizer. She setup the Line and Particle classes, defined the individual force particles and created their respective arrays. Update and interaction functions were also reasoned using her math. Jason focused on integrating music into the code and adding interactive elements such as toggling backgrounds and changing stroke weights. With his research on the sound library, he considered ways to best visualize the beats in our song with the use of line movement and negative space.

sketch

/*Carley Johnson
Section E
cbjohsno@andrew.cmu.edu
Final Project
*/

platform = [];
var x, y, y1, y2, y3;
var startScreen = 0;
var platformNumber = 50;
var platformGap = 70;
var cat;
var r = 0;

function preload() {
    var cloudPic = "https://i.imgur.com/veId7W2.jpg"
    cloudImage = loadImage(cloudPic);
    var catPic = "https://i.imgur.com/ooPSMZU.jpg"
    catImage = loadImage(catPic);
    var titlePic = "https://i.imgur.com/6ehrfne.jpg"
    titleScreen =  loadImage(titlePic);
}

function Cat() {
  this.x = 10;
  this.y = 10;
}

function Platform() {
  this.x = 10;
  this.y = 10;
  this.height = 10;
  this.width = 100;
}

function setup() {
  createCanvas(600, 500);

  //cloud placements
  x = width / 2;
  y = height;
  y1 = y + 100;
  y2 = y - 75;
  y3 = y - 300;

  //title screen setup
  if (startScreen == 0) {
    image(titleScreen, -30, 0, titleScreen.width/2, titleScreen.height/2);
    noStroke();
    textFont("MV Boli");
    fill(230, 181, 224);
    textSize(48);
    textAlign(CENTER);
    text("Move Cat With Mouse,", width/2, height - 95);
    text("Click To Start!", width/2, height - 50);
  }

  angleMode(DEGREES);
  
  //setup platforms
  for (i = 0; i < platformNumber; i++) {
    platform[i] = new Platform();
    platform[i].x = random(0, 400);
    platform[i].y = 500 + i * platformGap;
  }
  
  //start the platform in the right place
  platform[0].x = mouseX;
  cat = new Cat();
  cat.x = platform[0].x + 50;
  cat.y = platform[0].y - 5;
}

function draw() {
  if (startScreen == 0) {
    }
  else if (startScreen == 1) {
        //background sky
        background(88, 179, 236);
        image(cloudImage, x, y, cloudImage.width * 1.5, cloudImage.height * 1.5);
        image(cloudImage, x - 200, y1, cloudImage.width * 1.5, cloudImage.height * 1.5);
        image(cloudImage, x - 150, y2, cloudImage.width * 1.5, cloudImage.height * 1.5);
        image(cloudImage, x - 300, y3, cloudImage.width * 1.5, cloudImage.height * 1.5);
        y = y - 1;
        y1 = y1 - 1;
        y2 = y2 - 1;
        y3 = y3 - 1;

        //Gameplay
        noStroke();
        drawPlatform();
        drawCat();

        //cloud resets
        if (y < 0) {
            y = height;
        }

        if (y1 < 0) {
            y1 = height;
        }

        if (y2 < 0) {
            y2 = height;
        }

        if (y3 < 0) {
            y3 = height;
        }
    }
    
    //Cat controls
  cat.x = mouseX;
  if (mouseX < platform[r].x || mouseX > platform[r].x + 100) {
    cat.y = cat.y + 5;

    if (cat.y > platform[r].y + 10) {
      r++;
    }
  } else {
    cat.y = platform[r].y - 5;
  }
}

function mousePressed() {
  if (startScreen == 0) {
    startScreen = 1;
  }
}

function drawPlatform() {
 
  fill(147, 100, 15);
  for (i = 0; i < platformNumber; i++) {
    rect(platform[i].x, platform[i].y, platform[i].width, platform[i].height);
    platform[i].y = 500 + i * platformGap - (frameCount / 0.7 % (500 + i * platformGap));

    //Score counter
  textSize(20);
  stroke(147, 100, 15);
  textFont("MV Boli");
  fill(147, 100, 15);
  text('SCORE:', 475, 30);
  var score = parseInt(frameCount / 42) + 1;
  text(score, 565, 30);
  }
}

function drawCat() {
  push();
  translate(cat.x, cat.y);
  image(catImage, -150, -140, catImage.width/5, catImage.height/5)
  pop();

    //Game over
  if (cat.y < 0 || cat.y > 500) {
    stroke(204, 229, 242);
    fill(204, 229, 242);
    rect(130, 200, 350, 60);
    stroke(227, 116, 214);
    textFont("MV Boli");
    fill(230, 181, 224);
    textSize(60);
    textAlign(CENTER);
    text('Game Over!', 300, 250);
    noLoop();
    noFill();
    noStroke();
  }
}

A few things to play:

Click on the start screen quickly – a bug I was unsure how to fix causes the game to start play even while the start screen is up!

Keep your mouse to the left of the screen- this is where the first platform starts!

To play again, reload the page!

Otherwise, I enjoyed working on this project! I like how it looks (I drew the title card, the cat avatar, and the cloud image myself). I wanted it to be cute and slightly doodle-y in style. I picked the colors to go with this feeling. I’m proud of this because I feel like this is the first “experience” I’ve coded. It’s short and sweet, without a ton of major mechanics, but it’s a full experience nonetheless and I’m proud of it. I feel like I was able to tick off more goals of mine they I expected, so overall I’d say it is a personal win. I ended up combining my two possible game play ideas (an upwards platformer or downwards scroller) into a downwards platformer. I picked the cat theme because I was feeling homesick for my own, so I imagined that this was my cat (Soupy) trying to get home!

Eunice Choe – Final Project

sketch

/*Eunice Choe
Section E
ejchoe@andrew.cmu.edu
Final Project*/

// options variable
var option = 1;

// scene 1
var Px = [];
var Py = [];
var Pdx = [];
var Pdy = [];
var newP = [];

// scene 2
var r = 220;
var g = 247;
var b = 255;
var cloud = [];
var landscape = 0.002;
var spot = [];
var flowers = [];

// scene 3
var img;

// spring constants (blinds pulling bar)
var springHeight = 32;
var left;
var right;
var maxHeight = 100;
var minHeight = 200;
var over = false;
var move = false;

// spring constants (main blinds)
var mass = 0.8;
var sConstant = 0.2;
var damping = 0.92;
var rest = 40;

// spring movement variables
var ps = rest;   // position
var vs = 0.0; // velocity
var as = 0;   // acceleration
var f = 0;    // force

//scene 4
var img4;
var input;
var analyzer;


function preload() {
    img = loadImage("https://i.imgur.com/IdD3GJq.png");
    img4 = loadImage("https://i.imgur.com/sR307j6.png?2");
}

function setup(){
    createCanvas(480, 300);
// initializing floating particles
    for (i = 0; i < 100; i++) {
        Px[i] = random(480);
        Py[i] = random(300);
        Pdx[i] = random(-5, 5);
        Pdy[i] = random(-5, 5);
    }
    frameRate(10);

    push();
    angleMode(DEGREES);
    // initial collection of flowers and clouds
    for (var i = 0; i < 10; i++) {
        var cloudX = random(width);
        var rx = random(width);
        cloud[i] = makeCloud(cloudX);
        flowers[i] = makeFlowers(rx);
    }

// initializing lefts and rights for spring
    left = width/2 - 150;
    right = width/2 + 150;

// initializing audio input
    input = new p5.AudioIn();
    input.start();
}

function draw() {
    background(220, 247, 255);
    noStroke();
// scene 1
    if (option == 1){
        scene1();
    }
// scene 2
    else if (option == 2) {
        scene2();
    }
// scene 3
    else if (option == 3) {
        scene3();
    }
// scene 4
    else if (option == 4) {
        scene4();
    }
}

function scene1() {
    translate(width / 2, height / 2);
// flower petals
    for (var i = 0; i < 20; i ++) {
        fill(255, 224, 122, 90);
        ellipse(0, 30, 100, 500);
        rotate(PI * 8);
  }
// flower center
    for (var i = 0; i < 10; i ++) {
        fill(84, 46, 13, 80);
        ellipse(0, 0, 100, 200);
        rotate(PI * 8);
  }
// floating particles
    for (i = 0; i < 500; i++) {
        fill(255, 90);
        ellipse(Px[i], Py[i], 10, 10);
        Px[i] += Pdx[i];
        Py[i] += Pdy[i];
    }
}

function scene2(){
    background(r, g, b);
    if (mouseX > 0 & mouseX < width) {
        r = 229 - mouseX / 20;
        g = 247 - mouseX / 50;
        b = 224 + mouseX / 20;
    }
// grass in the back; randomizes when page refreshed
    beginShape();
    fill(139, 189, 125);
    vertex(0, height);
    for (var x = 0; x < width; x++) {
        var t = x * landscape;
        var y = map(noise(t), 0, 1, 0, height / 2);
        vertex(x, y + 100);
    }
    vertex(width, height);
    endShape();
    updateFlowers();
// rain when mouse is pressed
    if (mouseIsPressed) {
        frameRate(90);
        fill(255);
        spot.x = random(width);
        spot.y = random(height);
        ellipse(spot.x, spot.y, 5, 70);
    }
// clouds
    for (var i = 0; i < cloud.length; i++) {
        cloud[i].draw();
        cloud[i].move();
    }
}

function scene3() {
    image(img, 0, 0);
    fill(156, 38, 27);
    rect(0, 0, 100, height);
    rect(380, 0, 100, height);
    rect(100, 0, 280, 40);
    rect(100, 260, 280, 40);
    fill(242, 235, 202);
    rect(90, 270, 300, 10);
    fill(201, 196, 168);
    quad(100, 260, 380, 260, 390, 270, 90, 270);
    stroke(74, 89, 79);
    noFill();
    strokeWeight(10);
// sky gets darker when mouse is in window
    if ((mouseX > 100) & (mouseX < 380) &&
        (mouseY > 40) && (mouseY < 260)) {
        fill(43, 29, 133, 50);
    }
    rect(100, 40, 280, 220);
    updateSpring();
    drawSpring();
}

function scene4() {
    image(img4, 0, 0);
    push();
    var volume = input.getLevel();
// if the volume of the sound goes above threshold, then a firefly will appear
// fireflies are at random positions and their sizes reflect the volume
    var threshold = 0.05;
    if (volume > threshold) {
        push();
        frameRate(10);
        noStroke();
        fill(199, 255, 57);
        ellipse(random(width), random(170, height), volume * 50, volume * 50);
        pop();
    }
    fill(161, 156, 125);
    rect(40, 120, 60, 145);
    pop();
}

function updateFlowers() {
    // Update the flowers positions; random when page refreshed
    for (var i = 0; i < flowers.length; i++){
        flowers[i].display();
    }
}

function flowersDisplay() {
    var floorHeight = 10;
    var bHeight = this.nFloors * floorHeight * 2;
    noStroke();
    push();
    translate(this.x, height);
    fill(204, 187, 145);
    rect(0, -bHeight, this.breadth, bHeight);
    noStroke();
    translate(0, -bHeight);
    for (var i = 0; i < 20; i ++) {
        fill(255, 224, 122, 90);
        ellipse(0, 30, 20, 50);
        rotate(PI * 8);
    }
    fill(84, 46, 13, 90);
    ellipse(0, 0, 30, 30);
    pop();

}

function makeFlowers(birthLocationX) {
    var fl = {x: birthLocationX,
             breadth: 5,
             nFloors: round(random(2,8)),
             display: flowersDisplay}
    return fl;
}

function cloudDraw() {
    push();
    translate(this.xPos, this.yOffset);
    stroke(255, 255, 255, 70);
    strokeWeight(this.cHeight);
    line(0, 0, this.cSize, 0);
    pop();
}

function cloudMove() {
    this.xPos += this.speed;
    if(this.xPos < 0 - this.cSize - 30) {
        this.cHeight = random(10, 50);
        this.cSize = random(30, 150);
        this.xPos = width + this.cSize + random(-25, 25);
    }
}

function makeCloud() {
    var cloud = {xPos: random(width), //, width*4
                speed: random(-3, -1),
                cSize: random(30, 150),
                cHeight: random(20, 60),
                yOffset: random(50, height),
                draw: cloudDraw,
                move: cloudMove};
    return cloud;
}

function drawSpring() {
// draw main cream blinds
    noStroke();
    fill(255, 251, 243);
    rect(100, ps + springHeight, 280, - height);
    var baseWidth = 20;
    push();
    rectMode(CORNERS);
// if mouse is over gray bar, turn white
    if (over || move) {
        fill(255);
    } else {
        fill(204);
    }

    rect(left, ps, right, ps + springHeight);
    pop();
}

function updateSpring() {
// update the spring position
    if (!move) {
        f = -sConstant * ( ps - rest );
        as = f / mass; // acceleration
        vs = damping * (vs + as); // velocity
        ps = ps + vs;        // updated position
    }

    if (abs(vs) < 0.1) {
        vs = 0.0;
    }

  // see if mouse is over bottom bar
    if (mouseX > left & mouseX < right && mouseY > ps && mouseY < ps + springHeight) {
        over = true;
    } else {
        over = false;
    }

// constrain position of bottom bar
    if (move) {
        ps = mouseY - springHeight/2;
        ps = constrain(ps, minHeight, maxHeight);
    }
}

function mousePressed() {
    if (over) {
        move = true;
    }
}

function mouseReleased() {
    move = false;
}

// move through 4 slides
function keyPressed() {
    option++;
    if (option > 4) option = 1;
}

My final project is based off of a book I enjoyed from childhood called Zoom, by Istvan Banyai. I tried to replicate a small glimpse of the premise of the book, which is to keep zooming out as each page is turned. My project starts off with a zoomed in sunflower and it eventually zooms out to a house. In my project, I wanted to make the zoom outs seem less static, so I incorporated movement and interactions in each scene. Some interactions include clicking for a spring effect, pressing for rain, moving the mouse around for color changes, and making sounds for objects to appear.

Instructions:
Press any key to move on to the next scene.

Scene 1 shows a zoomed in flower with floating particles.
Scene 2 zooms out to show more flowers in randomized positions. The color of sky changes depending on mouse position and it starts to rain when the mouse is pressed.
Scene 3 zooms out to a window view of the flowers. when the mouse is in the window, the sky gets darker. Also when the gray bar is clicked, the curtain gives a spring effect.
Scene 4 zooms out to outside the house. The interaction is based off of sound, so if there is a loud sound, fireflies will appear in random positions.

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 🙂






Yingying Yan – Final Project

In architecture, we often have to use brushes in Photoshop. A good brush often makes a drawing very successful. Thus for this project, I decided to render a brush with cubes rotating at different angles with a change in gradient depending on the number of cubes that are drawn. Since WebGL does not support text, I could not explain the direction on the canvas. But ways the users can interact with the brush are: “h” = erase the canvas, “j” = turn off the randomness in size, after pressing j, the user can press “l” to make the cubes bigger, or “k” to make the cubes smaller. The user can also press a to increase the speed of rotation.

sketch

/*
Yingying Yan
Final Project_ Cube Brush
Section E
*/

var cube = []; // the array that stores all the cubes;
var count = 1; // keeps track of the number of cubes
var rr; //color 1
var bb; //color 2

//parameters that are controlled by the keys
var angle = 0; // the rotation of the cubes
var cSize; // the size of the cubes
var bigger = 10; // using key to make the size bigger
var turn = 0.01;
var randomm = true;// use key to turn off randomness of the size


function setup() {
    createCanvas(400,400, WEBGL);
    rr = color(204, 102, 0);
    bb = color(0, 102, 153);
}

function draw() {
    background(220);
    //the orgin for WEBGL is center, move it back to the p5js orgin
    translate(-width / 2, -height / 2);
    //space between the cubes
    var f = frameCount % 2 == 0
    //the size of each cube
    if (randomm) {
        cSize = random(5, 20);
    } else {
        cSize = bigger;
    }
    
    for (var i = 0; i < cube.length; i++) {
         cube[i].draw()

    } 
    //users drawing the cubes
    if (mouseIsPressed) {
        if (f) {
        var ccube = makeCube( cSize, cSize, cSize, mouseX, mouseY, turn);
        count += 1;
        cube.push(ccube);
        }
    }
}

//how the users can interact with the brush by pressing different keys

function keyPressed() {
    //erasing everything
    if (key == "h") {
        cube = [];
        count = 0;
        randomm == true;
        turn = 0.01;
    }

    //turn off randomness in sizes
    if (key == "j") {
        randomm = !randomm;
    }

    //make the cubes bigger after turning off the randomness
    if (key == "l") {
        bigger += 10;
    }

    //make the cubes smaller after turning off the randomness
    if (key == "k") {
        bigger -= 5;
    }
    //make the cubes spin faster
    if (key == "a") {
        turn += 0.1
    }
}


function makeCube (x, y, d, px, py, turn) {
    //use a color gradient depending on the number of cubes that are drawn
    var r = lerpColor(rr, bb ,sin(count / 30));
    //make the color more transparent
    var color_trans = (color(red(r), green(r), blue(r),120));

    return{ x: x,
            y: y,
            d: d,
            px: px,
            py:py,
            ang: random(0,90),
            tturn:turn,
            c: color_trans,
            draw: drawCube
        }
}


function drawCube() {
    //rotate around the corner
    rectMode(CORNER);
    noStroke();
    fill (this.c);
    //drawing the cube with rotation
    push();
    translate(this.px, this.py)
    rotateX(this.ang);
    rotateY(this.ang * 0.2);
    rotateZ(this.ang* 1.5);
    box(this.x, this.y, this.d);
    this.ang += this.tturn;
    pop();

}

Jenni Lee — Final Project

sketch

/* Jenni Lee
Section E
jennife5@andrew.cmu.edu
Final Project
*/

var tapirImage = [];
var cloudImage = [];
var clouds = [];
var plantImage = [];
var plants = [];

var numMovingFrame = 2;

function preload() {

  var filenames = [];
  // image location for tapir
  filenames[0] = "https://i.imgur.com/DW5kRkh.png";
  filenames[1] = "https://i.imgur.com/4KccJ7e.png";

  // image location for plants
  filenames[2] = "https://i.imgur.com/CICxu5P.png";
  filenames[3] = "https://i.imgur.com/KObxJ71.png";
  filenames[4] = "https://i.imgur.com/IYuCsVt.png";

  // image location for clouds
  filenames[5] = "https://i.imgur.com/w9ND0YA.png";
  filenames[6] = "https://i.imgur.com/6Ovkd5e.png";
  filenames[7] = "https://i.imgur.com/JH0DqMf.png";


  // PUT CODE HERE TO LOAD THE IMAGES INTO THE frames ARRAY,
  // USING THE FILENAMES STORED IN THE filenames ARRAY.

  for (var i = 0; i < numMovingFrame; i++) {
    tapirImage[i] = loadImage(filenames[i]);
  }

  for (i = 0; i < 3; i++) {
    plantImage[i] = loadImage(filenames[i + 2]);
  }

  for (i = 0; i < 3; i++) {
    cloudImage[i] = loadImage(filenames[i + 5]);
  } 
}

var ibuffer = 120; // buffer space (minimum distance) between two plants for jumping
function setup() {
  createCanvas(1000, 300);
  initiate();
  frameRate(30);
  textFont('Helvetica');
}

var currentTapirImageIndex = 0; // rotating tapir images to simulate tapir movement
var tapirLocationX = 80;
var tapirLocationY = 80;
var upperYJump = 200,
  lowerYJump = 80;
var speed = 3; // jump speed per frame
var jumpSpeed = 3;
var tapirMinHeight = 120;
var jumpStatus = false; // use this to tell if tapir is jumping or not
var tapirInPlantRangeCurr = []; // to record if tapir is in the plant range or not, the current state
var tapirInPlantRangePrev = []; // to record if tapir is in the plant range or not, the previous state
var numPlantsJumped = 0; // count total # of plants tapir jumps over
var isGameOver = false;

function initiate() {

  // create an initial collection of clouds
  for (var i = 0; i < 10; i++) {
    var rx = random(width); // randomly generate clouds at random locations to start with
    clouds[i] = makeCloud(rx);
  }
  // create an initial collection of plants
  for (i = 0; i < 6; i++) { // fixed location for plants, for jumping practice
    plants[i] = makePlant(230 + i * 180);
  }

  tapirLocationY = lowerYJump; // back to original position
  jumpSpeed = 3; // for tapir's jump speed
  jumpStatus = false; // to keep track if tapir is in jumping state or not
  isGameOver = false;
  numPlantsJumped = 0;
  currentTapirImageIndex = 0;
  for (i = 0; i < 10; i++) {
    tapirInPlantRangePrev[i] = false;
    tapirInPlantRangeCurr[i] = false;
  }
}

function draw() {
  background(229, 233, 137);

  noStroke();
  fill(70, 83, 130);
  rect(0, 0, width - 1, height - 51);

  // handling cloud display, the bottom layer of objects
  updateAndDisplayClouds();
  removeCloudsThatHaveSlippedOutOfView();
  addNewCloudsWithSomeRandomProbability();

  // draw terrain, next layer of object
  drawTerrain();

  // handling plants display, next layer of objects
  updateAndDisplayPlants();
  removePlantsThatHaveSlippedOutOfView();
  addNewPlantsWithSomeRandomProbability();

  // check the jumping status and display tapir
  updateTapirStatus();
  checkTapirOverPlant();
  
  // display the status such as # of total plants jumped, score
  displayStatusString();
}

function checkTapirOverPlant() {
  // check if tapir is inside any plant range
  for (var i = 0; i < plants.length; i++) {
    if (plants[i].x <= tapirLocationX + tapirImage[0].width) {
      if (plants[i].x + plantImage[plants[i].nPlantType].width >= tapirLocationX + tapirImage[currentTapirImageIndex].width) {
        tapirInPlantRangePrev[i] = tapirInPlantRangeCurr[i];
        tapirInPlantRangeCurr[i] = true;
        if (tapirLocationY < tapirMinHeight) {
          noStroke();
          fill(0, 0, 0);
          textSize(20);
          text("Game Over. Touch screen, click mouse, or press 'r' to restart.", width / 2 - 230, height / 2);
          isGameOver = true;
          noLoop();
        }
      } else {
        tapirInPlantRangePrev[i] = tapirInPlantRangeCurr[i];
        tapirInPlantRangeCurr[i] = false;
      }
    }
    if (tapirInPlantRangePrev[i] == true & tapirInPlantRangeCurr[i] == false) {
      numPlantsJumped++;
    }
  }
}

function updateTapirStatus() {
  if (jumpStatus == true) {
    tapirLocationY += jumpSpeed;
    if (tapirLocationY >= upperYJump) { // tapir hit top jupming point, change direction
      tapirLocationY = upperYJump;
      jumpSpeed = -1 * speed;
    } else if (tapirLocationY < lowerYJump) {
      tapirLocationY = lowerYJump;
      jumpSpeed = speed;
      jumpStatus = false;
    }
  }
  image(tapirImage[currentTapirImageIndex], tapirLocationX, height - tapirLocationY);

  if (jumpStatus == false) {
    currentTapirImageIndex++;
  }
  currentTapirImageIndex = currentTapirImageIndex % numMovingFrame; // rotating tapir images

}

function keyTyped() {
  if (key == "a") {
    jumpStatus = true;
  } else {
    jumpStatus = false;
  }
  if (key == "r") {
    if (isGameOver == true) {
      initiate();
      loop();
    }
  }
}

function touchStarted() {
  if (isGameOver == true) {
    initiate();
    loop();
  }
  else {
    jumpStatus = true;
  }
}

// define object for plant
function makePlant(birthLocationX) {
  var plant = {
    x: birthLocationX,
    y: height - 40,
    speed: -2.1,
    nPlantType: round(random(0, 2)),
    move: plantMove,
    display: plantDisplay
  }
  return plant;
}

function plantMove() {
  this.x += this.speed;
}

function plantDisplay() {
  imageMode(CORNERS);
  image(plantImage[this.nPlantType], this.x, this.y,
    this.x + plantImage[this.nPlantType].width, this.y - plantImage[this.nPlantType].height);
  imageMode(CORNER);

}

function updateAndDisplayPlants() {
  // Update the building's positions, and display them.
  for (var i = 0; i < plants.length; i++) {
    plants[i].move();
    plants[i].display();
  }
}

function removePlantsThatHaveSlippedOutOfView() {
  var plantsToKeep = [];
  for (var i = 0; i < plants.length; i++) {
    if (plants[i].x + plantImage[plants[i].nPlantType].width > 0) {
      plantsToKeep.push(plants[i]);
    }
  }
  plants = plantsToKeep; // remember the surviving plants
}

function addNewPlantsWithSomeRandomProbability() {
  var newPlantLikelihood = 0.05;
  var toAddPlant = true;
  if (random(0, 1) < newPlantLikelihood) {
    var i = plants.length - 1;
    // make sure there is at least ibuffer distance from previous plant before adding a new one from right
    if (plants[i].x + plantImage[plants[i].nPlantType].width + ibuffer < width) {
      plants.push(makePlant(width));
    }
  }
}

// define object for cloud
function makeCloud(birthLocationX) {
  var cld = {
    x: birthLocationX,
    y: round(random(25, 50)),
    speed: -0.6,
    nCloudType: round(random(0, 2)),
    move: cloudMove,
    display: cloudDisplay
  }
  return cld;
}

function cloudMove() {
  this.x += this.speed;
}

function cloudDisplay() {
  image(cloudImage[this.nCloudType], this.x, this.y);
}

function updateAndDisplayClouds() {
  // Update the cloud's positions, and display them.
  for (var i = 0; i < clouds.length; i++) {
    clouds[i].move();
    clouds[i].display();
  }
}


function removeCloudsThatHaveSlippedOutOfView() {
  var cloudsToKeep = [];
  for (var i = 0; i < clouds.length; i++) {
    if (clouds[i].x + cloudImage[clouds[i].nCloudType].width > 0) {
      cloudsToKeep.push(clouds[i]);
    }
  }
  clouds = cloudsToKeep; // remember the surviving clouds
}

function addNewCloudsWithSomeRandomProbability() {
  var newCloudLikelihood = 0.004;
  if (random(0, 1) < newCloudLikelihood) {
    clouds.push(makeCloud(width)); // push the new cloud to the clouds array (to the end)
  }
}


function displayStatusString() {
  noStroke();
  fill(0, 200, 0);
  textSize(14);
  text("Touch screen, click mouse, or press 'a' to jump", 5, 20);
  textSize(40);
//  var statusString = "# Plants jumped over = " + numPlantsJumped;
  var statusString = numPlantsJumped;
  text(statusString, width/2, height/2 - 25);
}

function drawTerrain() {
  noFill();
  beginShape();
  var terrainSpeed = 0.00012;
  var terrainDetail = 0.005;

  for (var x = 0; x < width; x++) {
    var t = (x * terrainDetail) + (millis() * terrainSpeed);
    var y = map(noise(t), 0, 1, 0, height - 40);
    stroke(216, 158, 172);
    line(x, y, x, height - 50);
  }
  endShape();
}

For this project, I wanted to create a game of a tapir that jumps over flowers. Tapirs are my favorite animal, so I wanted to use that. I drew all pictures, clouds, tapir, and plants, on Adobe Illustrator to load them at preLoad time for various effects, and use image() function to display them all at specified locations when they are inside canvas range. I drew 3 types of clouds, and design/define a cloud object to record and keep track of the cloud type/shape (randomly selected), location (x and y), and movement speed. Mountain/terrain: I used noise() function to draw vertical line from a variable y location to the ground to simulate mountain. I drew two frames of tapir to simulate its running. When loading different frames at consecutive frames, it would seem tapir is running. Overall, this project was really fun.

Dani Delgado and Elena Deng – Final Project

Click to make music!

sketch

/*
Elena Deng and Dani Delgado
Section E
edeng1 and ddelgad1
Final Project
*/

//variables to load sounds into

var ellipseSound;
var arcSound;
var squareSound;
var triSound;
var bgs;
//create the slider to control bg noise pan
var sliderPan;
//variables to inciment the bg
var volInc;
var pitchInc;
//stores the amount of mouse clicks
var mouseClick = 0;
//variables for the bg graphic based on amplitude
var amp;
var volhistory = [];

var objects = []; //sets the array for objects

var bright = 1; //sets the brightness of the screen+background

var xe = []; //sets the x pos array for the centroid
var ye = []; //sets the y pos array for the centroid


function preload() {
    //load all of the sounds into the file
    ellipseSound = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/ellipseDrip.mp3");

    squareSound = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/bloopSquare.mp3");

    arcSound = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/bong.mp3");

    triSound = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/ding.mp3");

    bgs = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/whiteNoise.mp3");
}

function setup() {
    createCanvas(640, 480);
    frameRate(14);
    angleMode(DEGREES);
    //play bg sound
    bgs.loop();
    bgs.setVolume(1.5);
    //get amplitude
    amp = new p5.Amplitude();
    //create pan slider
    sliderPan = createSlider(-1, 1, 0, 0.01);
    sliderPan.size(640, AUTO);

    for (var i = 0; i < 7; i++) {  //sets the centroid points
        var t = map(i, 0, 7, 0, TWO_PI);
        xe.push(width / 2 + 75 * cos(t));
        ye.push(height / 2 + 75 * sin(t));
    }
}

function draw() {
    background(10, 10, bright); //everytime the mouse is pressed, background brightness increases/decreases
    bright = bright - 5;
    if (mouseIsPressed) {
        bright = 110;
    }
    //pan slider
    bgs.pan(sliderPan.value());
    //get bgs amplitude and apply it the the graphic
    //the graphic is made from a "begin and end shape" method
    //and mapping of amps is made in a forloop
    var vol = amp.getLevel();
    volhistory.push(vol);
    push();
    frameRate(40);
    translate(width / 2, height / 2);
    //create the outer circle
    beginShape();
    strokeWeight(5);
    stroke(20, 20, -bright, 80);
    noFill();
    for (var i = 0; i < 360; i++) {
        var r = (map (volhistory[i], 0, 1, 10, 100)) * 18;
        var x = r * cos(i);
        var y = r * sin(i);
        vertex(x, y);
    }
    endShape();
    //create the inner circle
    beginShape();
    strokeWeight(0.5);
    stroke(80, 80, -bright);
    noFill();
    for (var i = 0; i < 360; i++) {
        var r = (map(volhistory[i], 0, 1, 10, 100)) * 17;
        var x = r * cos(i);
        var y = r * sin(i);
        vertex(x, y);
    }
    endShape();

    if (volhistory.length > 360) {
        volhistory.splice(0, 1);
    }
    pop();

    for (var i = 0; i < objects.length; i++) { //sets the order of the objects
        if (objects[i].type == "circle") {
            drawCir(objects[i]);
        }
        if (objects[i].type == "rect") {
            drawRect(objects[i]);
        }
        if (objects[i].type == "tri") {
            var change = random(0, 2)
            drawTri(objects[i].x, objects[i].y, objects[i].s + change);
        }
        if (objects[i].type == "centroid") {
            drawCentroid();

        }
    }
}

function mousePressed() {
    //variables for sounds made to easily reset the mouse clicks
    var firstSound = mouseClick < 10;
    var secondSound = mouseClick >= 10 & mouseClick <= 20;
    var thirdSound = mouseClick > 20 & mouseClick <= 30;
    var fourthSound = mouseClick > 30 & mouseClick <= 40;
    //images and sounds are based on mouse clicks
    if (firstSound) {
        //this code is to play the sounds
        //only allows a sound effect to be triggered if it's not playing
        //that way theres no overlap of sounds
        if(!ellipseSound.isPlaying()) {
            volInc = map(mouseY, 0, 480, 5, 0.09);
            pitchInc = map(mouseX, 0, 640, 0.25, 1.25);
            ellipseSound.play();
            ellipseSound.setVolume(volInc);
            ellipseSound.rate(pitchInc);
        }
        //pan the sounds based on mouse click
        //you'll hear it in every other ear every other click
        if (mouseClick % 2) {
            ellipseSound.pan(-1.0);
        } else {
            ellipseSound.pan(1.0);
        }
        if (objects.length >= 7) {
            objects.shift();
        }
        objects.push(createCir(mouseX, mouseY)); //creates the circle for the first ten mouse clicks
    }

    if (secondSound) {
        if(!squareSound.isPlaying()) {
            volInc = map(mouseY, 0, 480, 8, 1.5);
            pitchInc = map(mouseX, 0, 640, 0.25, 1.75);
            squareSound.play();
            squareSound.setVolume(volInc);
            squareSound.rate(pitchInc);
            // if (mouseClick % 2){
            //     squareSound.pan(-1.0);
            // } else {
            //     sqaureSound.pan(1.0);
            // }
        }
        if (objects.length >= 7) {
            objects.shift();
        }
        objects.push(createRect(mouseX, mouseY)); //creates the square/rect for the next ten mouse clicks
    }

    if (thirdSound) {
        if(!triSound.isPlaying()) {
            volInc = map(mouseY, 0, 480, 7, 0.5);
            pitchInc = map(mouseX, 0, 640, 0.5, 1.75);
            triSound.play();
            triSound.setVolume(volInc);
            triSound.rate(pitchInc);
        }
        if (mouseClick % 2) {
            triSound.pan(-1.0);
        } else {
            triSound.pan(1.0);
        }
        if (objects.length >= 7) {
            objects.shift();
        }
        objects.push(createTri(mouseX, mouseY, random(1, 2))); //creates the diamond for the next ten mouse clicks
    }

    if (fourthSound) {
        if(!arcSound.isPlaying()) {
            volInc = map(mouseY, 0, 480, 8, 0.5);
            pitchInc = map(mouseX, 0, 640, 0.25, 1.25);
            arcSound.play();
            arcSound.setVolume(volInc);
            arcSound.rate(pitchInc);
        }
        if (mouseClick % 2) {
            arcSound.pan(-1.0);
        } else {
            arcSound.pan(1.0);
        }
        if (objects.length >= 7) {
            objects.shift();
        }
        xe.push(mouseX);
        ye.push(mouseY);
        objects.push(createCentroid()); //creates the centroid for the next ten mouse clicks
    }
    if (mouseClick > 40) {
        mouseClick = firstSound; //mouseClicks resets the cycle
    }
    //increment the mouse clicks
    mouseClick ++;
}

function createCir(bx,by) { //creates the circle
    return {x:bx, y:by, type: "circle", oDiam: 0};
}

function drawCir(cir) { //draws the circle
    for (var i = 0; i < 10; i++) {
        var diam = cir.oDiam - 70 * i; //adjusts the drip spacing effect
        if (diam > 0) {
            var fade = map(diam, 0, width / 2, 100, 255); //sets the color
            strokeWeight(map(diam, 0, width, 12, 1)); //stroke weight decreases as circle gets larger
            stroke(fade, fade / 1.3, fade / 2, 85);
            noFill();
            ellipse(cir.x, cir.y, diam / 2); //draws ellipse
        }
    }
    cir.oDiam = cir.oDiam + 2; //increases size of the circle
    if (cir.oDiam > height / 2) { //once circle is larger than height/2 then the circle enlargement speed decreases
        cir.oDiam -= 1.25;
    }
}

function createRect(bx,by,bs) {
    return {x:bx, y:by, s:bs, type: "rect", oRect: 210}
}

function drawRect(square) {
    noStroke();
    rectMode(CENTER);

    for (var i = 0; i < 6; i++) {
        var recta = square.oRect - 100 * i; //sets the frequency/size of squares
        if (recta < 0) {
            var fade = map(recta, 0, width / 2, 255, 180); //maps the color
            strokeWeight(10)
            stroke(fade, fade / 1.3, fade / 2); //sets the color fade
            noFill();
            rect(square.x, square.y, recta / 2, recta / 2); //draws the square
        }
    }
    square.oRect = square.oRect - random(-2, -4); //shrinks square at random rates
}

function createTri(bx, by, bs) { //creates the diamond
    return {x:bx, y:by, s:bs, type: "tri"}
}

function drawTri(bx, by, bs) { //draws the diamond
    var smashorpass = random(-1, 10); //sets the random rate at which the shapes twinkle
    var smashthenpass = random(-1, 5);
    noStroke();
    //draws the first diamond
    fill(map(by, 0, height, 200, 255), map(by, 0, height, 120, 225), map(by, 0, height, 40, 85));
    quad(bx - 10 + bs - smashorpass, by + bs + random(-4, 4), bx + bs + random(-4, 4), by - 30 + bs - smashthenpass, bx + 10 + smashorpass, by + bs + random(-4, 4), bx + bs + random(-4, 4), by + 30 + bs + smashthenpass);

    strokeWeight(2); //draws the second diamond outline
    stroke(255, 230, 215, 70);
    noFill();
    quad(bx - 15 + bs - smashorpass, by + bs + random(-4, 4), bx + bs + random(-4, 4), by - 35 + bs - smashthenpass, bx + 15 + smashorpass, by + bs + random(-4, 4), bx + bs + random(-4, 4), by + 35 + bs + smashthenpass);

}

function createCentroid() { //creates the centroid
    return {type: "centroid"};
}

function drawCentroid() { //draws the centroid
    noStroke();
    fill(map(ye, 0, height, 200, 255), map(ye, 0, height, 120, 225), map(ye, 0, height, 40, 85));

    // moving the components of the centroid
    var nPoints = xe.length;
    for (var i = 0; i < nPoints; i++) {
        xe[i] += (noise(i * 1 + millis() / 500.0) - .5) * 10;
        ye[i] += (noise(i * 2 + millis() / 500.0) - .5) * 10;
        ellipse(xe[i], ye[i], 30, 30);
    }
    // draw the dots on the outside
    for (var i = 0; i < nPoints; i++) {
        ellipse(xe[i], ye[i], 1, 1);
    }
    // finding the average of the dots (x,y)
    var xAverage = 0;
    var yAverage = 0;
    for (var i = 0; i < nPoints; i++) {
        xAverage += xe[i];
        yAverage += ye[i];
    }
    xAverage /= nPoints;
    yAverage /= nPoints;
    // draws line from center to the points
    strokeWeight(4);
    stroke(255, 255, 255, 10);
    for (var i = 0; i < nPoints; i++) {
        line(xe[i], ye[i], xAverage, yAverage);
    }
    // Draw the centroid
    stroke(0);
    strokeWeight(2);
    ellipse(xAverage, yAverage, 10, 10);
}

For our final project, we wanted to create a “digital instrument” which synthesizes user interaction, sound, and generative visuals. We decided to start with a simple background which not only contains a graphic that reacts to the amplitude of the played music, but also flashes brighter when the user clicks in. From there, we added different graphics and corresponding sound effects to increase visual and audio interest. These graphics all have unique movements and correlate to the sound, while the sound effects change based on the mouseX and mouse Y position. As the user clicks, they’ll explore a range of different sounds and shapes that are placed on a loop which resets after every 40 clicks. Also, as they click, the sounds’s panning will change with every other click (e.x. click once, sound effect will play in one ear and click again, it’ll play in the other).

Here are some screenshots of possible screens you would get by interacting with this project!

shape 1: ripples
shape 2: squares
shape 3: diamonds
shape 4: centroid

We really enjoyed working on this project and exploring how different sounds and visuals interact and affect one another! We had difficulty at the beginning decided on exactly what we wished to do, but once we got the ball rolling things started to fall into place!

Since this was a partner project, we divided up the work to make it manageable and easier to code (as two people working on one computer would be slow and painful on our friendship). So we split the work up into such parts:
Elena Deng created the objects and visuals.
Dani Delgado edited the sound effects and created the background graphic.
Together we worked on debugging the code and adding the intended effects.

We hope you click around and have fun!

Julie Choi – Final Project

My final project requires the use of a camera (webcam), so it does not properly run on WordPress. Here is the link to the zip file:

Julie_Choi_Final_Project.zip

Instructions: Start with a white background for this project. Preferably wear a dark shade shirt and place your portrait on the center on the screen. An easier way to interact with this short game is to wait for all the balls to drop from the top of the screen, but this does not matter hugely. Start clicking the red balls to make them disappear. Once the screen has no red balls, an orange screen will pop up and tell you “good job!”

Since the program does not run on this page, here are some screenshots of the step by step process of the game.

This is how it looks when all the bubbles have stacked on top of each other.
This is how it looks when you click on the red bubbles to get rid of them on the screen.
This screen will appear once all the red bubbles disappear from the screen.

final project

/*Julie Choi
15-104 Section E
jjchoi@andrew.cmu.edu
Final Project
*/

var px = 20;
var py =  100;
var myCaptureDevice;
var brightnessThreshold = 50;
var darknessThreshold = 45
var circleCenter = [];
var x = 5;
var radius = 20;
var randomBalls = 2;
var initGeneration = true;
var initRandom = true;
let timer = 30;


function setup() {
    createCanvas(640, 480);
    myCaptureDevice = createCapture(VIDEO);
    myCaptureDevice.size(640, 480); // attempt to size the camera. 
    myCaptureDevice.hide(); // this hides an unnecessary extra view.
    frameRate(60);
    // generateCircleCenter function is called in setup
    generateCircleCenter();   	
}

function generateCircleCenter(){
	// sets the circles on the top of the circle by pushing makeCircleFall into the circle center
    while(x <= width - radius){
        circleCenter.push(makeCircleFall(x, 20, radius, true, false));
        x += radius;
    }
    x = 5;
    generateRandom(randomBalls);
}

function generateRandom(n){
	// generates more lines of circles to fall after the previous line
    if (n == 0){
        return;
    }
    var j = int(random(0, circleCenter.length));
    if (circleCenter[j].random != true){
        circleCenter[j].random = true;
        generateRandom(n-1);
    } else {
        generateRandom(n);
    }
}

function draw() {
    background(220);
    myCaptureDevice.loadPixels(); 
    // draw the camera at 1:1 resolution
    image(myCaptureDevice, 0, 0);  
    fill(255);
    // call all the objects in draw function
    for(var c = 0; c < circleCenter.length; c++){
        if (circleCenter[c].exist){
            circleCenter[c].render();
            circleCenter[c].update();
            if(circleCenter[c].py >= height){
                circleCenter[c].reset();
            }
        }
    }
    // if the frameCount is divisible by 60, then a second has passed. it will stop at 0
    if (frameCount % 60 == 0 & timer > 0) { 
        timer --;
    }
    if (timer % 2 == 1 & initGeneration){
        initGeneration = false;
        generateCircleCenter();
    }
    if (timer % 2 == 0 & initGeneration != true){
        initGeneration = true;
    }

    // instruction text on the bottom
    fill(0);
    noStroke();
    fill(255);
    textFont('futura');
    textSize(10);
    text("use a white background", width / 2, height - 25);
    text("tip: wait for all the balls to fall and have fun playing with the balls :)", width / 2, height - 10);
    textAlign(CENTER, CENTER);
    textSize(30);
    text( "Pop the red bubbles!", width / 2, height - 50);

    // detect only the py of the yellow point exsisting on the screen
    var result = circleCenter.filter(obj => {
    return obj.py > 0 & obj.random;
    });
    // if the result of the value above is 0 then the timer stops
    if (result.length == 0){
        timer == 0;
        fill(249, 173, 129);
        rect(0, 0, width, height);
        noFill();
        stroke(255);
        strokeWeight(1);
        text("GOOD JOB!", width/2, height /2);
    }
}

function makeCircleFall(inputX, inputY, radius, exist, random) {
    return {px: inputX, py: inputY, radius: radius, exist: exist, random: random,
            update: Update,
            reset: Reset,
            render: Render
           };
}

function isColor(c) {
    return (c instanceof Array);
}

function Update() {
    // fetch the color of the pixel at the (px,py) location of the circleCenter
    var theColorAtPxPy = myCaptureDevice.get(this.px, this.py);
    // compute its brightness
    if(isColor(theColorAtPxPy)){
        var theBrightnessOfTheColorAtPxPy = brightness(theColorAtPxPy);
    }
    // if the circleCenter is in a bright area, move downwards.
    // else, if it's in a dark area, move up until we're in a light area
    if(theBrightnessOfTheColorAtPxPy > brightnessThreshold){
        this.py += 1;
    } else if(theBrightnessOfTheColorAtPxPy < darknessThreshold & this.py > 0){
        this.py -=1;
        theColorAtPxPy = myCaptureDevice.get(px, py);
    }
    // take the objects in the circleCenter array and filter them into the name obj
    // obj.px to keep track of each px in the circleCenter array
    // this makes each circle stack on top of each other regarding the distance of the diameter of each circle
    var result = circleCenter.filter(obj => {
        return obj.px == this.px & obj.py > this.py
    });
    for (var i = 0; i < result.length; i++) {
        if ((result[i].py - radius) < this.py){
            this.py --;
        }
    }
}

function Reset() {
	// reset py to stop at the bottom of the screen
    this.py = height;
}

function Render() {
	// choose 3 circles randomly from each line to fill with red
    if(this.random){
        fill("red");
        stroke(255);
        strokeWeight(2);
        if (initRandom){
            initRandom = false;
        }
    }
    // draw circle at points of px and py in a line across the top of the screen
    if (this.exist) {
        ellipse(this.px, this.py, this.radius, this.radius);
        fill(random(0, 255), random(0, 255), random(0, 150), 80);
        stroke(255);
        strokeWeight(2);
        initRandom = true;
    }
}

function mousePressed() {
	// circles stay on the screen if exist is true
	// when mouse is pressed, exist and random becomes false to pop and make the circle that was clicked disappear
    for (var i = 0; i < circleCenter.length; i++) {
        var c = circleCenter[i];
        if (c.exist & dist(c.px, c.py, mouseX, mouseY) < c.radius / 2) {
            c.exist = false;
            c.random = false;
            return;
        }
    }
}

Reflection: This project utilizes the concept of raining letter assignment that we did a few weeks back. Using what we have learned plus some outside research more about p5.js, I was able to execute a satisfying result. I would say though that that using the different existing functions of p5.js and applying my mathematical calculation for the timer and the balls stacked on top of each other was the most challenging part. Overall, I learned a lot of new operators in the program and enjoyed the process of controlling both the display and the function.

Alice Fang and Jaclyn Saik – Final Project

This program uses the typeface ‘Avara’ from fontlibrary. Some of the words might not be perfectly aligned if the typeface isn’t installed. If so, here’s the zip file that includes the typeface: acfang_jsaik

And here’s a screen capture of all of the interactions: video

sketch

/*
An interactive display of Maya Angelou's poem "Still I Rise"
Click and move the mouse to interact, and press the right key to continue reading

We were inspired in part by digital children's books and "choose-your-own-adventure", except in this piece of 
work, we wanted to create simple interactions in a consistent aesthetic that would complement the words in poem.  
We also really love typography, poetry and literature, so we wanted to take the opportunity to augment a person's experience in 
the reading of a poem. 
 
We chose "Still I Rise" by Maya Angelou because we felt that her words were especially pertinent in today's social and political climate. 

Alice: Stanzas 1, 4, 8, 9, title slide, combining into one file
Jaclyn: Stanzas 3, 5, 6, 7, uploading font ('Avara') into css/html files
Stanza 2 ended up being a combination of the two of us
*/

var state = 0
var instruct = "click and explore to interact, press right key to continue";

// stanza 1
var stanza1a = ["You", "may", "write", "me", "down", "in", "history"];
var stanza1b = ["With", "your", "bitter,", "twisted", "lies,"];
var stanza1c = ["You", "may", "trod", "me", "in", "the", "very", "dirt"];
var stanza1d = ["But", "still,", "like", "dust,", "I’ll", "rise."];
var clickcountStanzaOne = 0;

//stanza 2
var stanzaSassyA = "Does my sassiness upset you?";
var stanzaSassyB = "Why are you beset with gloom?";
var stanzaSassyC = "’Cause I walk like I've got oil wells";
var stanzaSassyD = "Pumping in my living room.";
var AccentStanzaSassyA = "sassiness";
var SassyX = 50;
var SassyY = 200;
var timeKeeping = 0;
var AnotherPositionX = 180;
var AnotherPositionY = 180;
var ROLL = 0.1;
var CircleFill = 40;

// stanza 3
var stanzaThreeA = "Just like moons and like suns,";
var stanzaThreeB = "With the certainty of tides,";
var stanzaThreeC = "Just like hopes springing high,";
var stanzaThreeD = "Still I'll rise.";
var AccentStanzaA = "shoot me";
var AccentStanzaB = "cut me";
var AccentStanzaC = "kill me";
var tX = 50;
var tY = 200;
var y = 200;
var y2 = 200
var speed = 0;
var speed2 = 0;
var acceleration = 0.1;
var acceleration2 = 0.1;
var clicksStanzaThree = 0;
var Ang = 0;

// stanza 4
var stanza4 = "Did you want to see me broken?\nBowed head and lowered eyes?\nShoulders falling down like teardrops,\nWeakened by my soulful cries?";
var teardrops = "teardrops";
var tears = [];

// stanza 5
var stanzaFiveA = "Does my haughtiness offend you?";
var stanzaFiveB = "Don't you take it awful hard";
var stanzaFiveC = "’Cause I laugh like I've got gold mines";
var stanzaFiveD = "Diggin’ in my own backyard.";
var AccentStanzaFiveA = "gold";
var AccentStanzaFiveB = "haughtiness";
var AccentStanzaFiveC = "awful";
var AccentStanzaFiveD = "own";
var AccentStanzaFiveE = "laugh";
var FiveX = 50;
var Fivey = 200;
var ExRan = 10;
var terrainDetailB = 0.01;
var terrainSpeedB = 0.0002;
var imG;
var imgG2;

// stanza 6
var stanzaSixA = "You may shoot me with your words,";
var stanzaSixB = "You may cut me with your eyes,";
var stanzaSixC = "You may kill me with your hatefulness,";
var stanzaSixD ="But still, like air, I’ll rise.";
var AccentStanzaSixA = "shoot me";
var AccentStanzaSixB = "cut me";
var AccentStanzaSixC = "kill me";
var AccentStanzaSixD = "I'll rise.";
var SixtX = 50;
var SixtY = 200;

// stanza 7
var stanzaSevenA = "Does my sexiness upset you?";
var stanzaSevenB = "Does it come as a surprise";
var stanzaSevenC = "That I dance like I've got diamonds";
var stanzaSevenD = "At the meeting of my thighs?";
var AccentStanzaSexy = "sexiness";
var Sevent = 0; 
var SevenX = 50;
var SevenY = 200;
var words = [stanzaSevenA.split(" ")];
var speed = 0;
var speed2 = 0;
var acceleration = 0.1;
var acceleration2 = 0.1;

// stanza 8
var array = ["Out", "of", "the", "huts", "of", "history's", "shame"];
var array2 = ["I", "rise"];
var array3 = ["Up", "from", "a", "past", "that's", "rooted", "in", "pain"];
var array4 = ["I", "rise"];
var array5 = ["I'm", "a", "black", "ocean,", "leaping", "and", "wide,"];
var array6 = ["Welling", "and", "swelling", "I", "bear", "in", "the", "tide."];
var waveSpeed = 0.00005;
var waveDetail = 0.005;
var TimeWaveOne = 0.001;
var TimeWaveTwo = 0.0006;

// stanza 9
var stanza9a = "Leaving behind nights of terror and fear\nI rise";
var stanza9b = "Into a daybreak that’s wondrously clear\nI rise";
var stanza9c = "Bringing the gifts that my ancestors gave,\nI am the dream and the hope of the slave.";
var stanza9d = "I rise\nI rise\nI rise.";
var rows;
var cols;
var radius;
var d; 
var stateStanzaNine = 0;

function preload() { // load images for stanza 5
    var imgURL = "https://i.imgur.com/ppkgvPp.png";
    var imgURL2 = "https://i.imgur.com/QDwN1E1.png"
    imG = loadImage(imgURL);
    imgG2 = loadImage(imgURL2)
}

function setup() {
    createCanvas(480, 480);
    textFont("avara");

    // for stanza 4, pushing teardrops into array
    for (var i = 0; i < 8; i++) {
        var rainX = random(width);
        var rainY = -10;
        tears[i] = makerain(rainX, rainY);
    }
    // for stanza 9, determining grid variables
    rows = 100;
    cols = rows;
    radius = (width / rows) / 2;
    d = radius * 2;
}

function draw() {
    // change stanza when state changes; state changes when right key is pressed
    if (state === 0) {
        StanzaZero();
    } else if (state === 1) {
        StanzaOne();
    } else if (state === 2) {
        StanzaTwo();
    } else if (state === 3) {
        StanzaThree();
    } else if (state === 4) {
        StanzaFour();
        broken();
        for (var j = 0; j < tears.length; j++) { // draw and move teardrop objects
            tears[j].draw();
            tears[j].move();
        }
    } else if (state === 5) {
        StanzaFive();
    } else if (state === 6) {
        StanzaSix();
    } else if (state === 7) {
        StanzaSeven();
    } else if (state === 8) {
        wavey();
        foam();
        StanzaEight();
    } else if (state === 9 || state > 9) {
        StanzaNine();
    }
}

// introduction slide
function StanzaZero() { 
    background(66, 77, 88);
    fill('AliceBlue');
    textSize(30);
    textAlign(LEFT);
    text("Still I Rise", 50, 120);
    textSize(18);
    text("Maya Angelou", 50, 150);
    fill(240, 248, 255, 150);
    text("an interactive poem", 50, 240, 60);
    textSize(12);
    textAlign(RIGHT);
    text(instruct, 430, 430);
}

// stanza 1
function StanzaOne() { // as mouse is clicked, words appear on screen
    background(66, 77, 88);
    textSize(18);
    textAlign(LEFT);
    fill('AliceBlue');
    var offset1 = 0;
    var offset2 = 0;
    var offset3 = 0;
    var offset4 = 0;
    var yPOS = 200; // original y position of first line
        for (var i = 0; i < clickcountStanzaOne; i++) { // with each line, stanza length increases by previous line
        var sLength;
        if (i < stanza1a.length) {
           var WORD1 = textWidth(stanza1a[i]);
           text(stanza1a[i], 5 * i + offset1 + 50, yPOS);
           offset1 += WORD1; //offset determines spacing between words based of width of previous word
        } else if (i - stanza1a.length < stanza1b.length) {
          sLength = i - stanza1a.length;
          var WORD2 = textWidth(stanza1b[sLength]);
          text(stanza1b[sLength], 5 * (sLength) + offset2 + 50, yPOS + 25);
          offset2 += WORD2;
        } else if (i - stanza1a.length - stanza1b.length < stanza1c.length) {
          sLength = i - stanza1a.length - stanza1b.length;
          var WORD3 = textWidth(stanza1c[sLength]);
          text(stanza1c[sLength], 5 * (sLength) + offset3 + 50, yPOS + 50);
          offset3 += WORD3;
        } else if (i - stanza1a.length - stanza1b.length - stanza1c.length < stanza1d.length) {
          sLength = i - stanza1a.length - stanza1b.length - stanza1c.length;
          var WORD4 = textWidth(stanza1d[sLength]);
          text(stanza1d[sLength], 5 * (sLength) + offset4 + 50, yPOS + 75);
          offset4 += WORD4;
        }  
    }   
}

// stanza 2
function StanzaTwo() {
    trial(); //function  for rotating oil blobs
    StanzaSassyWords(); //function for printing stanza 2 words
    Stretch();
}

function StanzaSassyWords() {
    push();
    noStroke();
    textSize(18);
    fill('AliceBlue');
    text(stanzaSassyB, SassyX, SassyY + 30); //manually setting leading for the type 
    text(stanzaSassyC, SassyX, SassyY + 60);
    text(stanzaSassyD, SassyX, SassyY + 90);
    pop();
}

function trial() {
    fill(66, 77, 88);
    rect(-3, -3, width + 3, height + 3); //background rectangle
    for (var i = 0; i < 360; i += 3) { //for loop for the angle that the blobs rotate and generate
        var x = 0; //variables for blob's position and width
        var y = 0; 
        var w = 0;
        floor(x = cos(radians(i)) * 100 + width / 3); //floor function for whole numbers 
        floor(y = sin(radians(i)) * 100 + height / 2); 
        floor(w = sin(radians(timeKeeping/4 + i)) * 400); //the timekeeping variable keeps the blobs constantly moving
        w = abs(w); //whole and positive numbers for w, since it defines width

        float(colOR = map(i, 0, 360, 0, 30)); //color mapped from a range of gray 
        circleFill = map(mouseX, 0, width, 0, 40); //opacity mapped so it changes with mouseX
        noStroke();
        fill(colOR, circleFill);
        ellipse(x, y, w, w);
        fill(255);
    }
    timeKeeping++;
}

function Stretch() {
    push(); //stretch text vertically and horizontally based on mouse
    textSize(18);
    fill('AliceBlue');
    var MouseScaleX = map(mouseX, 100, width, 1, 2); 
    var MouseScaleY = map(mouseY, 100, height, 1, 2);
    scale(MouseScaleX, MouseScaleY);
    text(stanzaSassyA, SassyX, SassyY);
    pop();
}

// stanza 3
function StanzaThree() {
    StanzaThreeWords();
    if (clicksStanzaThree > 0) {
        BounceOne(); //call bounce function when screen is clicked
    }
    if (clicksStanzaThree > 1) {
        clicksStanzaThree = 0; //resets click count specifically for this stanza
    }
    BounceClick() //keeps click count increasing when mouse is pressed
    Moon(); //function for rotating sun and moon 
}

function StanzaThreeWords() {
    push();
    fill(66, 77, 88);
    rect(-3, -3, width + 10, height + 10); //background rectangle
    noStroke();
    textSize(18);
    textFont("Avara");
    fill(255, 255, 255);
    text(stanzaThreeA, tX, tY);
    text(stanzaThreeB, tX, tY + 30);
    text(stanzaThreeC, tX, tY + 60);
    text(stanzaThreeD, tX, tY + 90);

    stroke(66, 77, 88);
    strokeWeight(2);
    fill(66, 77, 88);
    text("springing", 183, 260); //type to cover existing type in stanza,
    text("hopes", 127, 260); //makes room for bouncing type
    noStroke();
    pop();
}

function BounceOne() { 
    textSize(18);
    fill(255);
    textFont("Avara");
    text("springing", 183, y + 60);
    y += speed;
    speed += acceleration;
    
    if (y + 60 > height || y + 60 < 1) { //invert direction if the type hits the bottom of the page 
        speed = -speed;
    }
    text("hopes", 127, y2 + 60);
    y2 += speed2;
    speed2 += acceleration2; //acceleration makes type get progressively faster 
    
    if (y2 + 60 > height || y2 + 60 < 1) { //invert direction if the type hits the bottom of the page
        speed2 = -speed2;
    }
}

function BounceClick() {
    if (mouseIsPressed) {
        clicksStanzaThree++; //adds clicks to stanza click variable
    }
}

function Moon() {
    noStroke();
    push();
    translate(240, 240); //translates center to the center of the page 
    rotate(radians(Ang)); //rotates by Angle defined globally 
    Ang += 0.7; 
    var moonX = 200; //moon and sun inversely positioned to each other 
    var moonY = 90;
    var sunX = -200;
    var sunY = -90;
    fill(255);
    ellipse(moonX, moonY, 30, 30);
    fill(66, 77, 88, 220);
    ellipse(moonX - 10, moonY, 30, 30); //moon is made of 2 ellipses
    noStroke();
    fill(255);
    sunshine(sunX, sunY); //calls function for sun rays 
    pop();
}

function sunshine(sx, sy) { //seperate function for sun rays 
    strokeWeight(2);
    stroke(255);
    line(sx - 20, sy, sx + 20, sy);
    line(sx, sy - 20, sx, sy + 20);
    line(sx - 15, sy - 15, sx + 15, sy + 15);
    line(sx + 15, sy - 15, sx - 15, sy + 15);
    stroke(66, 77, 88);
    strokeWeight(3);
    ellipse(sx, sy, 30, 30);
}

// stanza 4
function StanzaFour() {
    background(66, 77, 88);
    fill('AliceBlue');
    textSize(18);
    noStroke();
    text(stanza4, 50, 180);
    text(teardrops, 299, 225);
}

function makerain() {
    var drop = { x: random(0, width),
                y: -10,
                speed: random(1, 2),
                draw: drawrain,
                move: moverain}
    return drop;
}

function drawrain() { // create falling teardrops
    textSize(12);
    var tearOpacity = map(this.y, 0, height, 200, 0);
    stroke(240, 248, 255, tearOpacity);
    noFill();
    var tearSize = random(4, 8);
    ellipse(this.x, this.y, tearSize, tearSize);
    noStroke();
}

function moverain() {
    this.y += this.speed;
    if (this.y > height) {
        this.y = -10;
    }
}

function broken() { // as mouseY changes, background darkens, highlighting last line of stanza
    var OP = map(mouseY, 0, height, 0, 255); // map opacity to mouseY
    background(30, 30, 40, OP);
    fill('AliceBlue');
    text("Weakened by my soulful cries?", 50, 248);
}

// stanza 5
function StanzaFive() {
    DarkHill(); //background and digging person function 
    StanzaFiveWords(); //stanza word placement 
    DrawTwoo(); //function for the cursor effect
    GoldenWords(); //word interaction function 
}

function StanzaFiveWords() { //simlpe placement of stanza
    push();
    noStroke();
    textSize(18);
    textFont("Avara");
    fill(255, 255, 255);
    text(stanzaFiveA, FiveX, Fivey);
    text(stanzaFiveB, FiveX, Fivey + 30);
    text(stanzaFiveC, FiveX, Fivey + 60);
    text(stanzaFiveD, FiveX, Fivey + 90);
    pop();
}

function DrawTwoo() { //cursor effect to illuminate the mouse
    stroke(255, 215, 80);
    strokeWeight(4);
    line(mouseX, mouseY, pmouseX, pmouseY);
}

function GoldenWords() { //function for the gold words when mouse hovers 
    push();
    stroke(255, 215, 80); //includes background type and also 
    strokeWeight(1);
    textSize(18);
    textFont("Avara");
    fill(255, 215, 80);
    ExRan = random(0, 2);

    if (dist(mouseX, mouseY, 285 + 5, 260) < 40) {
        text(AccentStanzaFiveA, 285, 260); // "gold"
        strokeWeight(3);
        textSize(27);
        textFont("Avara");
        fill(255, 215, 80, 20);
        stroke(255, 215, 80, 20);
        text(AccentStanzaFiveA, 285 + ExRan, 260 + ExRan); // "gold"
    } 
    if (dist(mouseX, mouseY, 135 + 10, 200) < 40) {
        text(AccentStanzaFiveB, 135, 200); // "haughty"
        strokeWeight(3);
        textSize(27);
        textFont("Avara");
        fill(255, 215, 80, 20);
        stroke(255, 215, 80, 20);
        text(AccentStanzaFiveB, 135 + ExRan, 200 + ExRan); // "haughty"
    }
    if (dist(mouseX, mouseY, 201 + 5, 230) < 40) {
        text(AccentStanzaFiveC, 201, 230); // "awful"
        strokeWeight(3);
        textSize(27);
        textFont("Avara");
        fill(255, 215, 80, 20);
        stroke(255, 215, 80, 20);
        text(AccentStanzaFiveC, 201 + ExRan, 230 + ExRan); // "awful"
    }
    if (dist(mouseX, mouseY, 182 + 5, 290) < 40) {
        text(AccentStanzaFiveD, 182, 290); // "own"
        strokeWeight(3);
        textSize(27);
        textFont("Avara");
        fill(255, 215, 80, 20);
        stroke(255, 215, 80, 20);
        text(AccentStanzaFiveD, 182 + ExRan, 290 + ExRan); // "own"
    }
    if (dist(mouseX, mouseY, 124 + 5, 260) < 40) {
        text(AccentStanzaFiveE, 124, 260); // "laugh"
        strokeWeight(3);
        textSize(27);
        textFont("Avara");
        fill(255, 215, 80, 20);
        stroke(255, 215, 80, 20);
        text(AccentStanzaFiveE, 124 + ExRan, 260 + ExRan); // "laugh"
    }
    noStroke();
    pop();
}

function DarkHill() { //function for background hill and digging
    push();
    background(66, 77, 88); //generative landscape based on milliseconds
    beginShape(); 
    stroke(0, 30);
    for (var x = 0; x < width; x++) { //
        var t = (x * terrainDetailB) + (millis() * terrainSpeedB);
        var y = map(noise(t), 0,1, 100, 200);
        line(x, y, x, height); 
    }
    endShape();
    pop();

    if (dist(mouseX, mouseY, 285 + 5, 260) < 80) { //mouse trigger for the digging person to animate 
        image(imgG2, 100, 100);
    } else {
        image(imG, 100, 100);
    }
}
// stanza 6
function StanzaSix() {
    background(66, 77, 88);
    Shootin(); //function for stanza six type 
    Blinds(); //function for closing blinds
    Accent(); //function for accent type above blinds  
}

function Shootin() { 
    push();
    fill(66, 77, 88);
    rect(0, 0, width, height);
    noStroke();
    textSize(18);
    textFont("Avara");
    fill(255, 255, 255);
    text(stanzaSixA,SixtX,SixtY);
    text(stanzaSixB,SixtX,SixtY + 30);
    text(stanzaSixC,SixtX,SixtY + 60);
    text(stanzaSixD,SixtX,SixtY + 90);
    pop();
}

function Blinds() {
    noStroke();
    var whyOne = max(0, min(mouseY, 479));
    var gentleOne = 0.5; //determines easing function for each "blind"
    var gentleTwo = 0.7;
    var y = 1; //y length for blind 1
    var y2 = 1; //y length for blind 2

    var disy = whyOne - y; //finds distance between mapped y vaiable and y position 
    var disy2 = whyOne - y2;
    y += disy * gentleOne; //applies distance to easing 
    y2 += disy2 * gentleTwo;

    //blinds 1
    fill(30, 30, 40, 220);//opacity 
    rect(480, 0, -y, 480); //negative y value so that the blinds open and close on both sides 
    rect(0, 0, y, 480)
    //blinds2
    fill(30, 30, 40, 220);
    rect(480, 0, -y2, 480);
    rect(0, 0, y2, 480)
}

function Accent() { //simple function for placing selected accent words on top of blinds
    fill(255);
    textSize(18);
    textFont("Avara");
    text(AccentStanzaSixA, SixtX + 83.5, SixtY);
    text(AccentStanzaSixB, SixtX + 83.5, SixtY + 30);
    text(AccentStanzaSixC, SixtX + 83.5, SixtY + 60);
    text(AccentStanzaSixD, SixtX + 152.5, SixtY + 90);
}

// stanza 7
function StanzaSeven(){
    mouseGlow(); //mouse background function to make type more readible when mouse hovers 
    wavee() //wave function for diamond pattern 
    Sexy1(); //basic stanza type 
    if (mouseIsPressed) {
        Sexy2(); //second type interaction 
    }
}
function Sexy1() { //basic stanza type 
    push();
    noStroke();
    buffer = dist(mouseX, mouseY, SevenX + 150, SevenY + 40);
    textSize(18);
    textFont("Avara");
    fill('AliceBlue');
    text(stanzaSevenA, SevenX, SevenY);
    text(stanzaSevenB, SevenX, SevenY + 30);
    text(stanzaSevenC, SevenX, SevenY + 60);
    text(stanzaSevenD, SevenX, SevenY + 90);
    pop();
  }

function mouseGlow() {
    noStroke();
    fill(66, 77, 88);
    ellipse(mouseX, mouseY, 120, 120); //ellipse follows mouse, function placed BEHIND 
    //all other functions so this becomes another background
}

function wavee() {
    background(66, 77, 88, 20); 
    noFill();
    stroke(255, 180);
    strokeWeight(1);
    for (var x = 0; x <= width; x = x + 90) { 
        for (var y = 0; y <= height; y = y + 60) { //nested for loop defines grid system for diamonds
            var xAngle = map(mouseX, 0, width, -4 * PI, 4 * PI, true); //maps angle around a circle so it's based on 
            var yAngle = map(mouseY, 0, height, -4 * PI, 4 * PI, true); //the mouse x and y positions 
            var angle = xAngle * (x / width) + yAngle * (y / height); //angle tells diamond to osccillate
            var myX = x + 20 * cos(2 * PI * Sevent + angle); //synthesize x and y angles to one data point for each diamond
            var myY = y + 20 * sin(2 * PI * Sevent + angle);//also makes the diamond change over time regardless of mouse
            Diamond(myX, myY);
        }
    }
    Sevent = Sevent + 0.01; //time function 
}
function Diamond(vx, vy) { //function to manually draw diamond shapes 
    noFill();
    beginShape();
    vertex(vx, vy);
    vertex(vx + 4, vy);
    vertex(vx + 8, vy + 4);
    vertex(vx, vy + 12);
    vertex(vx - 8, vy + 4);
    vertex(vx - 4, vy);
    endShape(CLOSE);

    beginShape();
    vertex(vx - 8, vy + 4);
    vertex(vx + 8, vy + 4);
    vertex(vx, vy + 12);
    vertex(vx - 4, vy + 4);
    vertex(vx, vy);
    vertex(vx + 4, vy + 4);
    vertex(vx, vy + 12);
    endShape(CLOSE);
}

function Sexy2() { //function for interaction with the words "sexinesss"
    textSize(18);
    textFont("Avara");
    fill(0);
    fill(255, 215, 80);
    noStroke();
    text(AccentStanzaSexy, 134.5, 200);
}

// stanza 8
function StanzaEight() { // "wavy" text, affected by mouseX and mouseY
    fill('AliceBlue');
    noStroke();
    var offset1 = 0;
    var yPOS = 85;
    for (var a = 0; a < array.length; a++) { //first line
        var WORD1 = textWidth(array[a]);
        text(array[a], 5 * a + offset1 + 50, yPOS);
        offset1 += WORD1; // offset determines spacing between words based on width of previous word

        var d = dist(mouseX, mouseY, 5 * a + offset1 + 10, yPOS);
        if (d > 0 & d < 20) { // based on distance, word will rise/ fall
            yPOS += 1;
        }
        if (d > 0 & d < 35) {
            yPOS += 5;
        }
        if (d > 0 & d < 50) {
            yPOS += 8;
        }
    }
    var offset2 = 0;
    var yPOS2 = 135;
    for (var b = 0; b < array2.length; b++) { //second line
        var WORD2 = textWidth(array2[b]);
        text(array2[b], 5 * b + offset2 + 50, yPOS2);
        offset2 += WORD2;

        var dd = dist(mouseX, mouseY, 5 * b + offset2 + 10, yPOS2);
        if (dd > 0 & dd < 20) { // based on distance, word will rise/ fall
            yPOS2 += 1;
        }
        if (dd > 0 & dd < 35) {
            yPOS2 += 5;
        }
        if (dd > 0 & dd < 50) {
            yPOS2 += 8;
        }
    }
    var offset3 = 0;
    var yPOS3 = 185;
    for (var c = 0; c < array3.length; c++) { //third line
        var WORD3 = textWidth(array3[c]);
        text(array3[c], 5 * c + offset3 + 50, yPOS3);
        offset3 += WORD3;

        var ddd = dist(mouseX, mouseY, 5 * c + offset3 + 10, yPOS3);
        if (ddd > 0 & ddd < 20) { // based on distance, word will rise/ fall
            yPOS3 += 1;
        }
        if (ddd > 0 & ddd < 35) {
            yPOS3 += 5;
        }
        if (ddd > 0 & ddd < 50) {
            yPOS3 += 8;
        }
    }
    var offset4 = 0;
    var yPOS4 = 235;
    for (var d = 0; d < array4.length; d++) { //fourth line
        var WORD4 = textWidth(array4[d]);
        text(array4[d], 5 * d + offset4 + 50, yPOS4);
        offset4 += WORD4;

        var dddd = dist(mouseX, mouseY, 5 * d + offset4 + 10, yPOS4);
        if (dddd > 0 & dddd < 20) { // based on distance, word will rise/ fall
            yPOS4 += 1;
        }
        if (dddd > 0 & dddd < 35) {
            yPOS4 += 5;
        }
        if (dddd > 0 & dddd < 50) {
            yPOS4 += 8;
        }
    }
    var offset5 = 0;
    var yPOS5 = 285;
    for (var e = 0; e < array5.length; e++) { //fifth line
        var WORD5 = textWidth(array5[e]);
        text(array5[e], 5 * e + offset5 + 50, yPOS5);
        offset5 += WORD5;

        var ddddd = dist(mouseX, mouseY, 5 * e + offset5 + 10, yPOS5);
        if (ddddd > 0 & ddddd < 20) { // based on distance, word will rise/ fall
            yPOS5 += 1;
        }
        if (ddddd > 0 & ddddd < 35) {
            yPOS5 += 5;
        }
        if (ddddd > 0 & ddddd < 50) {
            yPOS5 += 8;
        }
    }
    var offset6 = 0;
    var yPOS6 = 335;
    for (var f = 0; f < array6.length; f++) { //sixth line
        var WORD6 = textWidth(array6[f]);
        text(array6[f], 5 * f + offset6 + 50, yPOS6);
        offset6 += WORD6;

        var dddddd = dist(mouseX, mouseY, 5 * f + offset6 + 10, yPOS6);
        if (dddddd > 0 & dddddd < 20) { // based on distance, word will rise/ fall
            yPOS6 += 1;
        }
        if (dddddd > 0 & dddddd < 35) {
            yPOS6 += 5;
        }
        if (dddddd > 0 & dddddd < 50) {
            yPOS6 += 8;
        }
    }
}

// create wave and foam for stanza 8
function wavey() {
    background(66, 77, 88);
    beginShape();
    stroke(30, 30, 40);
    for (var w = 0; w < width; w++) {
        var t = (w * TimeWaveOne) + (millis() * TimeWaveTwo);
        var waveY = map(noise(t), 0, 0.5, 0, mouseY);
        line(w, waveY, w, width);
    } endShape();
}

function foam() {
    beginShape();
    stroke(255, 255, 255, 90);
    for (var waveF = 0; waveF < width; waveF++) {
        var t = (waveF * TimeWaveOne) + (millis() * TimeWaveTwo);
        var foamY = map(noise(t), 0, 0.5, 0, mouseY);
        vertex(waveF, foamY - 5);
        vertex(waveF, foamY - 5);
    } endShape();
}   

// stanza 9
function StanzaNine() {
    if (stateStanzaNine == 0) { //highlight only first two lines
        fill('AliceBlue');
        stroke(0);
        textSize(18);
        text(stanza9a, 50, 125);
        if (mouseX > 40 & mouseX < 140 && mouseY > 105 && mouseY < 165) {
            fill(66, 77, 88);
            text("rise", 62.25, 147.25) //change color when mouse hovers to indicate "click"
        }
    }
    if (stateStanzaNine == 1) { //highlight only third and fourth lines
        fill('AliceBlue');
        stroke(0);
        text(stanza9b, 50, 170);
        if (mouseX > 40 & mouseX < 140 && mouseY > 150 && mouseY < 215) {
            fill(66, 77, 88);
            text("rise", 62.25, 192.25) //change color when mouse hovers to indicate "click"
        }
    }
    if (stateStanzaNine == 2) { //highlight only fifth and sixth lines
        fill('AliceBlue');
        text(stanza9c, 50, 215);
        stroke(0);
        fill(255, 215, 80);
        text("hope", 268.25, 237.5); //change color when mouse hovers to indicate "click"
    }
    drawGrid(); // create spotlight
    
    if (stateStanzaNine == 3) { //reveal last stanza
        background(255, 250, 205);
        fill(110);
        text(stanza9a, 50, 125);
        text(stanza9b, 50, 170);
        text(stanza9c, 50, 215);
        fill(0);
        text(stanza9d, 50, 300);
    }
}

function drawGrid() { //create spotlight grid
    for (var gridY = 0; gridY < height; gridY += d) {
        for (var gridX = 0; gridX < width; gridX += d) {
            var modifiedX = gridX;
            if ((gridY % (d * 2)) === 0) {
                modifiedX = gridX + radius;
            }
            fill(0);
            var distance = dist(mouseX, mouseY, modifiedX, gridY);
            opacity = map(distance, 0, 160, 0, 255); //zero opacity at center of spotlight, fade as dist increases
            noStroke();
            fill(0, 0, 0, opacity);
            rectMode(CENTER);
            rect(gridX, gridY, d, d);
        }
    }
}

// for stanza nine
function mouseClicked() { //range for click-ability to switch lines
    if (state === 9 & mouseX > 40 && mouseX < 140 && mouseY > 105 && mouseY < 165) { //if clicked in this range, next two lines will be revealed
        stateStanzaNine = 1;
    } else if (state === 9 & mouseX > 40 && mouseX < 140 && mouseY > 150 && mouseY < 215) { //if clicked in this range, next two lines will be revealed
        stateStanzaNine = 2;
    } else if (state === 9 & mouseX > 268 && mouseX < 310 && mouseY > 223 && mouseY < 243) { //if clicked in this range, reveal all of stanza
        stateStanzaNine = 3;
    }
}

// for global state change
function keyPressed() {
    if (keyCode === RIGHT_ARROW) { //when right key is pressed, change stanzas
        state++;
    }
}

// for stanza one
function mousePressed() { //allow text to appear word by word in stanza one
    clickcountStanzaOne++;
}

Both of us are very interested in poetry and literature, so for this final project we wanted to take the opportunity to use animation and mouse interaction to augment the experience of reading a poem. We were inspired in part by digital children’s books and “choose-your-own-adventure”, except in this piece of work, we wanted to create simple interactions in a consistent aesthetic that would complement the words in poem.  We chose “Still I Rise” by Maya Angelou because we felt that her words were especially pertinent in today’s social and political climate.

This project was challenging to accomplish, and luckily we were able to work together well in order to tackle bigger problems. One of the most time-consuming struggles we encountered was the process of uploading, arranging, aligning, ordering and placing all of the type: we had to learn a couple new functions in order to manipulate the text in certain ways and treat some words as objects. We got sucked into the technicalities of typography, and spend more time than we should have finding and uploading the correct typeface. In the treatment of individual words, when placing words from a string in a for loop to appear one at a time, spacing became a tricky thing to deal with because our typeface wasn’t monospace. Another thing we struggled with was synthesizing all of our work. Because we would create sketches for each slide in a separate file, we had to establish a guide for how we labeled our variables so that they didn’t conflict. We also had to be careful about universal functions like mousePressed and keyPressed.

Inspiration credit:
p5.js official Wavemaker exampled
bounce function from p5.js official examples
Yasai openprocessing CircleShift Sketch

Even if we were working on different stanzas, we were able to talk problems out with each other and give advice. Although Alice focused on the opening slide, stanzas 1, 4, 8, and 9, and Jaclyn focused on stanzas 2, 3, 5, 6, and 7, the collaborative nature of our workflow allowed both of us to have a holistic influence to the project.
Overall, we are happy how this project turned out, and excited that we got to explore typography within p5.js.