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.
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.
]]>****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.
/*
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);
}
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.
/*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
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.
Wait a few seconds… it’s loading!
Click to start! (Click mouse to see next visual)
//variables to load sound
var sound1;
var sound2;
var sound3;
var sound4;
var sound5;
var sound6;
//variable to switch between shapes and songs
var toggle = 0;
//variable for drawing astroid (toggle 2 shape)
var power = 33;
var r = 255;
function setup() {
createCanvas(500, 500, WEBGL);
amplitude = new p5.Amplitude();
frameRate(40);
}
function preload() {
sound1 = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/york.mp3");
sound1.setVolume(1);
sound2 = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/prettiestvirgin.mp3");
sound2.setVolume(1);
sound3 = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/purity.mp3");
sound3.setVolume(1);
sound4 = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/pizza.m4a");
sound4.setVolume(1);
sound5 = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/Siamese_Sea.mp3");
sound5.setVolume(1);
sound6 = loadSound("https://courses.ideate.cmu.edu/15-104/f2018/wp-content/uploads/2018/12/perth.mp3");
sound6.setVolume(1);
}
function draw() {
background(0);
noFill();
stroke(255, 0 ,0);
//retrieves amplitude of song playing
var level = amplitude.getLevel();
//maps the amplitudes across values for use in shape later on
var twist = map(level, 0, .6, 0, .3);
var twist2 = twist * 15000;
//adjusts the size of the astroids
var twistSize = map(level, 0, .3, 75, 300);
//sophia's twists
var twisty1 = map(level, 0, 1, 0, .3);
var twisty2 = twisty1 * 5;
var twisty3 = twisty1 * 4;
//MIMI JIAO'S CODE
//first shape - Mimi's code
if (toggle === 1) {
rotateY(frameCount * twist / 100);
rotateX(frameCount * twist / 100);
rotateZ(frameCount * twist / 100);
for (var i = 0; i < twist2; i++) {
fill(i * sin(i), i * cos(i), 255);
beginShape();
vertex(i * cos(i), i, i - 1000);
vertex(i * .01, i * 0.1, i * .01);
vertex(i * sin(i), i * cos(i), i);
endShape(CLOSE);
}
}
//second shape (astroid) - Mimi's code
if (toggle === 2) {
rotateX(twist);
rotateY(twist);
rotateZ(twist);
//randomizes value so astroid will randomly be bright
var randomBright;
randomBright = random(255);
//first astroid
beginShape();
noFill();
for(var i = 0; i < twist2; i++) {
if (randomBright > 250) {
stroke(255, 0, 0);
} else {
stroke(twist * 900 * sin(i), twist * 300, sin(i) * twist * 900);
}
vertex(twistSize * (cos(i) ** power),
twistSize * (sin(i) ** power));
}
endShape();
//second astroid
push();
rotateZ(5);
rotateX(3);
rotateY(4);
beginShape();
noFill();
for(var i = 0; i < twist2; i++) {
stroke(twist * 300, twist * 900 * sin(i), sin(i) * twist * 900);
vertex(twistSize * (cos(i) ** power),
twistSize * (sin(i) ** power));
}
endShape();
pop();
//third astroid
push();
rotateZ(3);
rotateX(4);
rotateY(5);
beginShape();
noFill();
for(var i = 0; i < twist2; i++) {
if (randomBright > 250) {
stroke(255, 0, 0);
} else {
stroke(twist * 900 * sin(i), twist * 300, sin(i) * twist * 900);
}
vertex(twistSize * (cos(i) ** power),
twistSize * (sin(i) ** power));
}
endShape();
pop();
//fourth astroid
push();
rotateZ(4);
rotateX(3);
rotateY(5);
beginShape();
noFill();
for(var i = 0; i < twist2; i++) {
if (randomBright > 250) {
stroke(255, 0, 0);
} else {
stroke(twist * 900 * sin(i), twist * 300, sin(i) * twist * 900);
}
vertex(twistSize * (cos(i) ** power),
twistSize * (sin(i) ** power));
}
endShape();
pop();
//fifth astroid
push();
rotateZ(4);
rotateX(3);
rotateY(5);
beginShape();
noFill();
for (var i = 0; i < 250 * TWO_PI; i++) {
vertex(300 * (cos(i) ** power),
300 * (sin(i) ** power));
}
endShape();
pop();
}
//third shape - Mimi's code
if (toggle === 3) {
beginShape();
noFill();
//x and y coordinates
var x;
var y;
var t = TWO_PI;
var a = map(twist2, 0, width, 2, 10);
var n = map(twist2, 0, height, QUARTER_PI, HALF_PI);
var ma = map(a, 0, 200, 0, QUARTER_PI);
//shape1
push();
beginShape();
for(var i = 0; i < twist2; i++) {
noStroke();
fill(cos(twist2) * 100, cos(twist2) * 100, sin(twist2) * 100);
x = a * sin(ma) * ((n - 1) * cos(t) + cos((n - 1) * t)) / n;
y = a * sin(ma) * ((n - 1) * sin(t) - sin((n - 1) * t)) / n;
vertex(-i * sin(i), i * cos(i), i);
vertex(x, y);
t += QUARTER_PI;
}
endShape();
pop();
//shape2
push();
beginShape();
for(var i = 0; i < twist2; i++) {
noStroke();
fill(sin(twist2) * 100, cos(twist2) * 100, sin(twist2) * 100);
x = a * sin(ma) * ((n - 1) * cos(t) + cos((n - 1) * t)) / n;
rotateZ(-4);
y = a * sin(ma) * ((n - 1) * sin(t) - sin((n - 1) * t)) / n;
vertex(x, y);
vertex(i * sin(i) , i * cos(i), i);
t += HALF_PI;
}
endShape();
pop();
//accent shape3
push();
rotateX(frameCount * .003);
rotateY(frameCount * .004);
rotateZ(frameCount * .005);
beginShape();
for(var i = 0; i < twist2; i++) {
noStroke();
fill(sin(twist2) * 255, cos(twist2) * 255, sin(twist2) * 255);
x = a * sin(ma) * ((n - 1) * cos(t) + cos((n - 1) * t)) / n;
rotateZ(-4);
y = a * sin(ma) * ((n - 1) * sin(t) - sin((n - 1) * t)) / n;
vertex(x, y);
vertex(i * sin(i) , i * cos(i), i);
t += QUARTER_PI;
}
endShape();
pop();
}
//SOPHIA KIM's code below
// first "slide" for Sophia's Code - sphere
push();
if (toggle === 4) {
var radiusSphere1 = twisty2 * 200;
fill(232, 0, 0);
noStroke();
rotateY(frameCount * twisty2 / 1000);
rotateX(frameCount * twisty2 / 1000);
rotateZ(frameCount * twisty2 / 1000);
sphere(radiusSphere1);
var constrainR = constrain(mouseX, radiusSphere1, radiusSphere1);
fill('yellow');
noStroke();
rotateY(frameCount * twisty2 / 500);
rotateX(frameCount * twisty2 / 500);
rotateZ(frameCount * twisty2 / 500);
sphere(constrainR);
}
pop();
//first "slide" - lines behind the sphere
push();
if (toggle === 4) {
for (var i = 0; i < twisty2 * 1000; i++) {
stroke('red');
beginShape();
vertex(i * cos(i), i, i - 2000);
vertex(i * .01, i * 0.09, i * .1);
vertex(i * sin(i) , i * cos(i), i / 100);
endShape(CLOSE);
stroke('orange');
beginShape();
vertex(i * cos(i), i, i - 2000);
vertex(i * .01, i * 0.05, i * .1);
vertex(i * sin(i), i * cos(i), i / 500);
endShape(CLOSE);
}
}
pop();
//2nd "slide" for Sophia's code - lines
push();
if (toggle === 5) {
var Rfor2 = random(twisty2 * 140, 255);
var Gfor2 = random(twisty2 * 140, 255);
for (var i = 0; i < twisty2 * 3000; i++) {
stroke(Rfor2, Gfor2, 230);
strokeWeight(.4);
beginShape();
vertex(i * sin(i / 10), tan(sin(i / 20)) * 10);
vertex(i * sin(i / 20), sin(i / 100) * 20, cos(i / 50));
vertex(tan(i / 10), cos(i / 100), cos(i * 100));
vertex(sin(i / 20), tan(i / 50) * 40, sin(i * 5) / 20);
endShape(CLOSE);
}
}
pop();
//3rd "slide" for Sophia's code -
//multiple circles moving around
push();
if (toggle === 6) {
for(var j = 0; j < 4; j++){
var Rfor3 = random(twisty3 * 200, 255);
stroke(Rfor3, 100, 240);
for(var i = 0; i < twisty3 * 3000; i++){
translate(sin(twisty3 * 0.4 + j) * 20,
sin(twisty3 * 0.1 + j) * 20, i * 3);
rotateX(frameCount * .3 / 5000 / twisty3);
rotateY(frameCount * .2 / twisty3 / 100);
rotateZ(frameCount * .5 / twisty3 / 300);
push();
sphere(14, 7, 5);
pop();
}
}
}
pop();
}
function mousePressed() {
//reset to first shape/song after 6th song
if (toggle < 6) {
toggle ++;
} else {
toggle = 1;
}
//play songs based on mouse click sequence
if (toggle === 1) {
sound1.play();
sound6.stop();
}
if (toggle === 2) {
sound2.play();
sound1.stop();
}
if (toggle === 3) {
sound3.play();
sound2.stop();
}
if (toggle === 4) {
sound4.play();
sound3.stop();
}
if (toggle === 5) {
sound5.play();
sound4.stop();
}
if (toggle === 6) {
sound6.play();
sound5.stop();
}
}
For the final project, we created various types of visuals that respond to different songs’ amplitude levels. We were interested in exploring how sound can be translated visually and wanted to challenge ourselves and try something new. So instead of using 2D, we decided to explore the basics of 3D with WEBGL.
We wanted to further explore sound and graphics as one, so we wanted to directly tie the image of the graphics to the amplitude of the music playing. We used shapes like spheres, and beginShape/endShape to create the visuals and played around with implementing trigonometric functions to create curves and other shapes. We wanted to create something that the viewer could flip through, so we made this click-through visual presentation. By clicking on the mouse, the user is able to see different visuals each with its own song.
Have fun and we hope you enjoy it
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.
/*
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
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.
]]>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!
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!
]]>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:
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.
/*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.
]]>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
/*
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.