Final Project – Undertale Piano


determination

glamour

highA

highAsharp

highB

highC

highCsharp

highD

highDsharp

highE

highF

highFsharp

highG

highGsharp

lowA

lowAsharp

lowB

lowC

lowCsharp

lowD

lowDsharp

lowE

lowF

lowFsharp

lowG

lowGsharp

papyrusTalk

sansTalk

My project is an interactive piano keyboard that will play when the user presses the mouse over the individual keys. Keep in mind that because of the way the code is written, the white keys are only playable in the area below the black keys.

For a simple melody from the indie game Undertale to play, the user needs to type in a certain password that will display as the user types. That certain is… “determination.”

Screenshots to prove functionality of project:

screen-shot-2016-12-09-at-6-54-27-pmscreen-shot-2016-12-09-at-6-54-39-pmscreen-shot-2016-12-09-at-6-54-43-pm

Running a local server is required for the code template. I didn’t know how to display the project for this reason, and so I have attached my code and embedded all 28 of the .wav sound files used in the code. (If the .wav files are inaccessible somehow even when looking at the text, please let me know!) All the image URLs are direct links to the images themselves. Directions for running a local server are here.

Credits:
All copyright and materials, including the depicted characters, sound effects, quotes, and songs, of Undertale belong to its creator, Toby Fox.

The performance of the “determination” melody belongs to Kyle Landry, whose video can be found here for a direct link or embedded below.

All manual piano key notes were recorded by myself using the sound editor Audacity and the piano in Mudge residence hall in Carnegie Mellon University.

P.S., if you’ve played Undertale, try typing in the word that would please a certain fabulous robot the most when he presents his essay question… or you could just look at the code for the little treat I put in. 🙂

Jinhee Lee; Project Proposal

Undertale Piano

img_0245

I propose making an interactive piano project, playing to a selected keyword. The respective chords would play a melody in tandem with the appropriate key presses and indicate which keys on the piano are being pressed. Whether the keys can be played individually by choice remains to be seen. The white keys I could simply map to the number keys, but I would have to put more thought into how to map the black keys, as the keyword “determination” already occupies the three rows of letters on the keyboard.

The melody is inspired from Undertale, a popular indie game released in September 2015. Various characters and visuals will pop up with each key press, and since they’re sprite-animated, I could possibly make helper functions for the sole purpose of drawing the characters on each key press. I may even use sprite sheets to animate the characters during the key presses.

tumblr_static_azo52oj4gqw488wokkk8s8go8

All material and rights of Undertale are reserved to its creator, Toby Fox.

Jinhee Lee; Project

jinheel1_project-10

//Jinhee Lee
//Section C
//jinheel1@andrew.cmu.edu
//Project-10

var buildings = [];
var ourLordandSaviour;

function preload() {
	ourLordandSaviour = loadImage("http://i.imgur.com/b4dYgGz.jpg"); //I don't know
}

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

    ourLordandSaviour.loadPixels();
    
    // create an initial collection of buildings
    for (var i = 0; i < 10; i++){
        var rx = random(width);
        buildings[i] = makeBuilding(rx);
    }
    frameRate(30);
}


function draw() {
	var skyCol = color(135,206,235);
    background(skyCol); //sky color backdrop

    image(ourLordandSaviour, 25, 0);
    
    displayGround(); //displaying grass

    updateAndDisplayBuildings();
    removeBuildingsThatHaveSlippedOutOfView();
    addNewBuildingsWithSomeRandomProbability(); 
}


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


function removeBuildingsThatHaveSlippedOutOfView(){
    // If a building has dropped off the left edge,
    // remove it from the array.  This is quite tricky, but
    // we've seen something like this before with particles.
    // The easy part is scanning the array to find buildings
    // to remove. The tricky part is if we remove them
    // immediately, we'll alter the array, and our plan to
    // step through each item in the array might not work.
    //     Our solution is to just copy all the buildings
    // we want to keep into a new array.
    var buildingsToKeep = [];
    for (var i = 0; i < buildings.length; i++){
        if (buildings[i].x + buildings[i].breadth > 0) {
            buildingsToKeep.push(buildings[i]);
        }
    }
    buildings = buildingsToKeep; // remember the surviving buildings
}


function addNewBuildingsWithSomeRandomProbability() {
    // With a very tiny probability, add a new building to the end.
    var newBuildingLikelihood = 0.007; 
    if (random(0,1) < newBuildingLikelihood) {
        buildings.push(makeBuilding(width));
    }
}


// method to update position of building every frame
function buildingMove() {
    this.x += this.speed;
}
    

// draw the building and some windows
function buildingDisplay() {
    var floorHeight = 20;
    var bHeight = this.nFloors * floorHeight; 
    fill(255); 
    stroke(0); 
    push();
    translate(this.x, height - 40);
    rect(0, -bHeight, this.breadth, bHeight);
    fill("red"); //red windows and roofs
    triangle(0, -bHeight, //roofs
    	this.breadth, -bHeight, 
    	this.breadth/2, 3 / 2 * -bHeight);
    stroke(200); 

    for (var i = 0; i < this.nFloors; i++) {
        rect(5, -15 - (i * floorHeight), this.breadth - 10, 10);
    }
    pop();
}

function makeBuilding(birthLocationX) {
    var bldg = {x: birthLocationX,
                breadth: 50,
                speed: -1.0,
                nFloors: round(random(1,4)),
                move: buildingMove,
                display: buildingDisplay}
    return bldg;
}


function displayGround(){
	var groundCol = color(66, 244, 86);
	fill(groundCol);
    rect(0,height/2, width, height); 
}

I used the template and manipulated and added various elements to simulate a suburb.

P.S., This has been a very long week and weekend for me… I apologize for the lackluster submission. That said, I added in something that I hope will at least make someone smile, if not be impressed. Any partial credit received is greatly appreciated.

Jinhee Lee; Looking Outwards 10

The person whose work I am writing about is Chloe Varelidi. She has a Master’s in Fine Arts at Parsons’ Design and Technology Program. Currently she works at littleBits as a Sr. Product Strategist, and is also a resident artist at Eyebeam, making her own games.


The littleBits Analog Arcade Machine, for the 2015 Bay Area MakerFaire, presented by Kristin Salomon, Paul Rothman, and their littleBits team, of which Varelidi was a member.

The team’s projects include an arcade game that dispenses candy, an electronic drum module creating synth beats, and even an animatronic hand project that allows for games of rock-paper-scissors with a computer using one’s own hand in a glove with a wireless receiver. According to the presenters, the modules are assembled with bits (hence the name) which challenge the creators’ creative electrical engineering skills without being overly complicated, at least in terms of assembly.

The rock-paper-scissors game I find particularly interesting because of the many touches to help simulate a real game, such as using one’s own hand, having the opposing animatronic shaped like a hand, and simulating prediction of your move by detecting the subtle movements of your hand as you play.

P.S., the Donkey Kong theme is a nice touch. 🙂

Jinhee Lee; Project-09

jinheel1_project-09

//Jinhee Lee
//Section C
//jinheel1@andrew.cmu.edu
//Project-09

var underlyingImage;

function preload() {
    var myImageURL = "http://i.imgur.com/EH0p0KK.png";
    underlyingImage = loadImage(myImageURL);
}

function setup() {
    createCanvas(600, 600);
    background(70); //dark grey background to contrast with black
    underlyingImage.loadPixels();
    frameRate(25); //fast frame rate to fill up canvas
}

function draw() {
    var px = random(width);
    var py = random(height);
    var ix = floor(px);
    var iy = floor(py);
    var theColorAtLocationXY = underlyingImage.get(ix, iy);

    var pixelSize = 10; //size of pixels spawning 

    var bright = brightness(theColorAtLocationXY); //extract brightness of pixel for greyscale effect
    var amt = map(bright, 0, 100, 0, 255); //map from 0 to 255 so overall image isn't too dark

    noStroke();
    fill(amt); //fills pixel with appropriate brightness value
    ellipse(px, py, pixelSize, pixelSize);
}

function mouseDragged() { //draws a line with mouse
    var theColorAtTheMouse = underlyingImage.get(mouseX, mouseY);
    stroke(theColorAtTheMouse); //while pixels are greyscaled, the line is colored
    strokeWeight(10); //thicker stroke so it can be seen

    line(pmouseX, pmouseY, mouseX, mouseY);
}

I made the generated pixels with a greyscale effect, along with a mouseDragged() function that adds color depending on the stroke of the mouse. Like breathing life into the photo, though if left alone too long, it would revert back to grey with the refreshing pixels.

Jinhee Lee; Project 07

jinheel1_project-07

//Jinhee Lee
//Section C
//jinheel1@andrew.cmu.edu
//Project-07

var nPoints = 100;
var angle = 0; //start angle for rotating (out of canvas)

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

function draw() {
	background(250);

    translate(width / 2, height / 2);
    if (mouseX > width) { //this one's for you, Anirudh
    	rotate(angle); //shape rotates in place
    }

    drawDeltoidCurve(); //calling helper function

    angle += 0.05; //increment for rotation
}

function drawDeltoidCurve() {
	// Deltoid
	// http://mathworld.wolfram.com/Deltoid.html

	var x; //curve in parametric form
	var y;

	var minSize = 80; //min size of shape
	var maxSize = 160; //max size of shape
	var mapA = map(mouseY, 0, width, minSize, maxSize); //maps size between minSize and maxSize
	var a = constrain(mapA, minSize, maxSize); //so that shape doesn't grow when mouseX beyond canvas
	var b = a / 3; //variable for curve equations
	var rot = constrain(mouseX / 30, 0, width / 30); //rotate according to mouseX

	fill("red"); //larger red circle
	ellipse(0, 0, 2 * a, 2 * a);

	fill(0);
	beginShape();
	for (var i = 0; i < nPoints; i++) {
		var theta = map(i, 0, nPoints, 0, TWO_PI);

		x = 2 * b * cos(theta) + b * cos(2 * theta - rot); //parametric equations of curve
		y = 2 * b * sin(theta) - b * sin(2 * theta - rot);
		vertex(x, y);
	}
	endShape(CLOSE);

	fill("red"); //smaller red circle
	ellipse(0, 0, b, b);
}

Having to implement parametric equations felt daunting at first, but in a broader look, it was mostly plugging in the deltoid curve’s equation with the templates given in the prompt. The two red circles I made separately fairly easily, but I made them share variables with the deltoid curve that governed its size, so they would all grow proportionally.

P.S.,

Fun fact, if you spin the deltoid to the right past the canvas, then you get-
YOU ARE NOW UNDER MY GENJUTSU

Jinhee Lee; Looking Outwards 06

This is a very small example that very few people have commented on, but small details like this are what immerse me into games. In the Devil May Cry series, the game encourages the player to fight expressively and stylishly by grading their performance at the end of every mission.

maxresdefault

An example of this can be seen here, in which the player receives an “S” rank and the screen is sliced many, many times and eventually breaks at the seams.

The number of slices/gunshots (depending on the played character) that appear on the screen before the displayed rank is random, specifically “pseudo-random”, in that it can actually be determined and reproducible depending on the player’s rank. The higher the rank, the more slices/gunshots.

I believe that the cutting angles are all predetermined, and that the game picks a fairly consistent amount to display depending on the rank, but randomizes the order in which each cut is executed. Additionally, when the screen breaks apart like glass, randomness seems to be utilized to a greater extent as the shards drop. Particularly, applying the physics engine to each shard and treating it as an object in free-fall. This is all speculation though, seeing as how neither player nor developer has really elaborated on such a minor feature.

What this produces is a nice visual that reflects the free-flowing rather than choreographed nature of the combat and feels empowering to players that take the time to refine their skill.

Jinhee Lee; Project 06

jinheel1_project-06

//Jinhee Lee
//Section C
//jinheel1@andrew.cmu.edu
//Project-06

var theSunX = 0; //starting coordinates for the Sun/Moon at midnight
var theSunY = 200;
var theMoonX = 0;
var theMoonY = -200;

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

function draw() {
	var h = hour();
	var m = minute();
	var s = second();
	var daySecs = 86400; //# of seconds in a day
	var timeSecs = (h * 60 * 60 + m * 60 + s); //current time in seconds
	var theSun = 100; //size of theSun
	var theMoon = 100; //size of theMoon
	var skyDayCol = color(135,206,235); //sky color at day
	var skyNightCol = color(0); //sky color at night
	var gDayCol = color(222,184,135); //ground color at day
	var gNightCol = color(139,69,19); //ground color at night

	//various times of day in seconds
	var dawn = daySecs/5;
	var dusk = 4 * daySecs/5;
	var sunrise = 3 * daySecs/10;
	var sunset = 7 * daySecs/10;

	//for future lerpColor() functions
	var twilight1 = map(timeSecs,dawn,sunrise,0,1); //"amt" arguments for lerpColor
	var twilight2 = map(timeSecs,sunset,dusk,0,1);
	var cG1 = lerpColor(gNightCol,gDayCol,twilight1); //color changes during twilight
	var cG2 = lerpColor(gDayCol,gNightCol,twilight2);
	var cSky1 = lerpColor(skyNightCol,skyDayCol,twilight1);
	var cSky2 = lerpColor(skyDayCol,skyNightCol,twilight2);

	//sky
	if (timeSecs < dawn || timeSecs > dusk) { //between dawn and dusk
		background(skyNightCol);
	} else if (timeSecs > sunrise & timeSecs < sunset) { //between sunrise and sunset
		background(skyDayCol);
	} else if (timeSecs >= dawn & timeSecs <= sunrise) { //during 1st twilight
		background(cSky1);
	} else if (timeSecs >= sunset & timeSecs <= dusk) { //during 2nd twilight
		background(cSky2);
	}

	//rising/setting sun and moon
	push();
	translate(width/2,height/2);
	rotate(TWO_PI * timeSecs / daySecs);

	fill("yellow");
	ellipse(theSunX,theSunY,theSun,theSun);
	fill("white");
	ellipse(theMoonX,theMoonY,theMoon,theMoon);
	pop();

	//ground
	if (timeSecs < dawn || timeSecs > dusk) { //between dawn and dusk
		fill(gNightCol);
		rect(0,height/2,width,height/2);
	} else if (timeSecs > sunrise & timeSecs < sunset) { //between sunrise and sunset
		fill(gDayCol);
		rect(0,height/2,width,height/2);
	} else if (timeSecs >= dawn & timeSecs <= sunrise) { //during 1st twilight
		fill(cG1);
		rect(0,height/2,width,height/2);
	} else if (timeSecs >= sunset & timeSecs <= dusk) { //during 2nd twilight
		fill(cG2);
		rect(0,height/2,width,height/2);
	}
}

Coming up with the individual parts, such as defining certain times of day like dusk and dawn and determining arguments for lerpColor() functions, wasn’t difficult. However, implementing them to work in combination was a tougher task. In an attempt to piecemeal the process, I came up with various “if” statements detailing different periods of time during the day, and duplicating said “if” statements so I could use them for changing the ground and sky separately (otherwise there would have been a layering error where the Sun was “on top” of the ground instead of in the horizon).

I initially had an explicit time display, but after reading that conventional numerals were disallowed, I had to take it out. I didn’t want there to just be a black sky at night, so I also added a moon to take the place of the Sun at night. Though you’ll see both of them during the twilight times.

Jinhee Lee; Looking Outwards 05

gollum-and-andy-serkis

Gollum (pictured right), played by Andy Serkis (pictured left) in Lord of the Rings (2001-2003), and The Hobbit: An Unexpected Journey (2012), both directed by Peter Jackson.

Being that this blog post is about images rather than animation, I will talk about how Gollum’s appearance was rendered, though I will reference the latter in regards to The Hobbit.

Gollum’s appearance I believe is a rare achievement amongst CGI characters (even in modern films), as it incorporates several elements to create the character other than CGI, combining, to name a few, the 3D model with sculpted models as reference, motion capture with Serkis’ facial expressions, and Serkis’ physical presence on the set to help his fellow actors react believably.

According to this article, Bay Raitt and his team at Weta Digital created a system “which implemented 964 control points on Gollum’s face,” allowing detailed control of his facial expressions. To summarize, this system was used to animate Gollum’s face and a 3D model was drawn over the rest of Serkis’ body during recording.

The additional technique used in The Hobbit is particularly interesting in that it essentially gives Gollum a “living” skeleton, muscles, fascia, etc. to further enhance his lifelike appearance. As for the algorithms, I believe that the individual body elements are calculated to react to Serkis’ nuances in facial expression, in order to save the trouble of having to animate every single frame by hand.

Here is a link to a video describing the added techniques for animating the 3D image of Gollum for The Hobbit: An Unexpected Journey.

Jinhee Lee; Project-05

jinheel1_project-05

//Jinhee Lee
//Section C
//jinheel1@andrew.cmu.edu
//Project-05

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

function draw() {
	background(20);

	var scale = Math.sqrt(3)/2; //helps make equilateral triangle
	var triDistX = 60; //increments triangle on x-axis
	var triDistY = 60 * scale; //increments triangle on y-axis
	var triOffset = 30; //for making bottom vertices of triangles

	var fro = color(100,20,100); //color for stripes
    var to = color(150,20,150);

    var fro2 = color(20,20,100); //color for triangles
    var to2 = color(20,20,255);

	for (var y = 0; y < height; y += 2 * triDistY) { //for each row
		var amt = map(y,0,height,0,1);
		var col = lerpColor(fro,to,amt);

		fill(col); //fills the stripes
		stroke(col); 

		rect(0,y + triDistY,width,triDistY); //draws the stripes
		for (var x = 0; x < width; x += triDistX) { //for each "column"
			var amt2 = map(x,0,width,0,1);
			var col2 = lerpColor(fro2,to2,amt2);

			fill(col2); //fills the triangles
			stroke(col2);
			
	    	triangle(x + triOffset, y, //each line is a coordinate pair
	    		x, y + triDistY,
	    		x + 2 * triOffset, y + triDistY);
	    }
	}
	noLoop(); //as per instructions
}

I wanted to go for cooler colors on the color spectrum, to give more of a subdued tone to the wallpaper. Drawing nested for() loops is, in itself, not difficult, but it did present a bunch of restrictions I wasn’t aware of. I previously tried to use variables defined locally, before the for() loop, for incrementation. However, with such a configuration I ended up with only a single row of triangles that I had intended to repeat down the canvas. I had to change it later so that the variables for incrementation were defined inside the for() loop arguments.

P.S., if this were a shirt, I would NOT wear this in public.