casher-UnityTutorial

Various shots from the scripting videos & my implementations.

Loops

Classes

Components

Lerping

 

Changing material with letter keys

Deleting GameObject with mouse click

Moving GameObject with arrow keys

casher-book

Lyric Poems

A collection of rhyming poetry generated from songs from the last 50 years.

PDFs: Lyric Poems

Inspiration for this project initially came from my dad. After I told him about the guidelines and that we were going to be pulling data from random corpora, he sent me a link to a list of 10,000 fake band names. This had me thinking about possibilities that related to music. I'm part of The Cut, CMU's music magazine, so at first I thought it would be fun to generate a whole fake magazine article about a new band's first tour -- their name, their genre, a bio on each member, a generated graphic of each member (like a Bitmoji, from different head pieces), the set list of the tour, the cities that they were touring, and a mini concert review -- which I would try to include in the next issue as a joke. I realized while planning out ideas that 1) each page of the article would probably end up just being a repetitive list of words randomly chosen from each corpus, and 2) I probably wouldn't have enough time to execute all of my ideas well. Therefore, I decided I should pick one or a few of the ideas. When I was researching how to generate song titles to include in the set lists, one corpus I found also had 2.5 million song lyrics. So then I realized I could pull lyrics from those songs and make my own songs out of them.

What was especially interesting about the generative process here was that a lot of the lines make sense (meaningful sense), no matter where in the list of 2.5 million they came from. Even with a rhyme scheme. I like how this  illustrates that artists have written about the same themes in their songs for decades: love, life, and loss. The combinations ended up abstract enough, however, that they resembled poetry more than songs. I ironically titled the chapter "Lyric Poetry" after an actual genre of poetry in which the poet specifically addresses emotions and feelings -- funny because, on the surface, some of these poems can seem very deep, beautiful, and emotional, but the fact that they were generated by a computer means that there is actually zero intent behind the words, making them emotionless on the deeper level.

Through the development I also discovered a bug with RiTa -- she has trouble rhyming the word "eight."

import processing.pdf.*;
import rita.*;
import cbl.quickdraw.*;
boolean bExportingPDF; 
// See https://processing.org/reference/libraries/pdf/index.html
 
int nPages; 
PImage imageArray[];
JSONArray jsonPages;
int outputPageCount = 0; 
float inch = 72;
 
String[] bandnames; 
String[] genres;
String[] lyrics;
String[] stanza;
String[] titles;
String[] colors = {"#5b97f7", "#a07ef7", "#ffb5ec"};
int numcircles = (int) random(8, 16);
QuickDraw qd;
 
int x = 160, y = 240;
RiLexicon lexicon;
Boolean pressed = true;
int numstanzas;
 
PFont caviar25;
PFont pulsab70;
PFont font1;
PFont font2;
 
Boolean on = true;
int numcircles1 = 14;
 
 
//=======================================================
void setup() {
 
// The book format is 6" x 9". 
// Each inch is 72 pixels (or points). 
// 6x72=432, 9*72=648
 
// USE THESE COMMANDS WHEN YOU'RE NOT EXPORTING PDF, 
// AND YOU JUST WANT SCREEN DISPLAY:
//size(432, 648); 
//bExportingPDF = false;
//
// BUT: IMPORTANT: 
// WHEN IT IS TIME TO EXPORT THE PDF, 
// COMMENT OUT THE ABOVE size() COMMAND, AND
// USE THE FOLLOWING TWO COMMANDS INSTEAD:
size(432, 648, PDF, "poems25.pdf");
bExportingPDF = true;
 
background(255);
lexicon = new RiLexicon();
bandnames = loadStrings("bandnames1.txt");
genres = loadStrings("musicgenres.txt");
lyrics = loadStrings("SONGS.txt");
titles = loadStrings("songtitles.txt");
qd = new QuickDraw(this, "guitars.ndjson");
caviar25 = createFont("Arvo-Regular.ttf", 10);
pulsab70 = createFont("Alpaca Scarlett Demo.ttf", 25);
font1 = createFont("Arvo-Regular.ttf", 28);
font2 = createFont("Alpaca Scarlett Demo.ttf", 50);
 
}
 
//=======================================================
void draw() {
if (bExportingPDF) {
drawForPDFOutput();
} else {
drawForScreenOutput();
}
}
 
//=======================================================
void drawForPDFOutput() {
 
// When finished drawing, quit and save the file
if (outputPageCount >= nPages) {
exit();
} 
else if (outputPageCount == 0) {
title();
PGraphicsPDF pdf = (PGraphicsPDF) g;
if (outputPageCount < (nPages-1)) {
pdf.nextPage();
}
}
else {
drawPage(outputPageCount); 
PGraphicsPDF pdf = (PGraphicsPDF) g; // Get the renderer
if (outputPageCount < (nPages-1)) {
pdf.nextPage(); // Tell it to go to the next page
}
}
 
outputPageCount++;
}
 
 
//=======================================================
void drawForScreenOutput() {
int whichPageIndex = (int) map(mouseX, 0, width, 0, nPages);
drawPage(whichPageIndex);
println(whichPageIndex);
}
 
//=======================================================
void drawPage(int whichPageIndex) {
background(255);
whichPageIndex = constrain(whichPageIndex, 0, nPages-1); 
fill(0);
//if (pressed == true) {
background(255);
for (int e = 0; e < numcircles; e++) {
noFill();
strokeWeight(random(.1,3.5));
int colorr = (int) random(170,255);
int colorg = (int) random(170,220);
int colorb = (int) random(220,245);
int rad = (int) random(30, 200);
int x = (int) random(0, width);
int y = (int) random(0, height);
//String col = random(colors);
int p = (int) random(1, 8);
for (int g = 0; g < p; g++) { stroke(colorr, colorg, colorb, 225-(g*40)); ellipse(x, y, rad, rad); rad*=.89; } } strokeWeight(5); pushMatrix(); stroke(180); int guitarIndex = (int) random(0, 80); //print("g", guitarIndex); qd.create(0,random(height/2-100, height/2+100), 140, 140, guitarIndex); popMatrix(); fill(255,180,80); textFont(pulsab70); drawText(); fill(0); textFont(caviar25); makeStanza(); } void drawText() { //textSize(70); textAlign(CENTER); int titleIndex = (int) random(titles.length); textAlign(LEFT); String title = titles[titleIndex]; String[] wordsInTitle = RiTa.tokenize(title); if (wordsInTitle.length > 3) {
titleIndex+=1;
}
text(titles[titleIndex], 30, height/5);
}
 
void makeStanza() {
textAlign(LEFT);
numstanzas = (int) random(1, 8);
for (int j = 0; j < numstanzas; j++) {
for (int i = 0; i < 2; i++) {
int lyricsIndex1 = (int) random(lyrics.length);
String lyric1 = getLyric(lyrics, lyricsIndex1);
text(lyric1, 30, height/5 + 25 + 60*j + (i*2)*13);
String lyric2 = getRhymingLyric(lyrics, lyric1, lyricsIndex1);
text(lyric2, 30, height/5 + 25 + 60*j + (i*2)*13+13);
}
}
}
 
String getLyric(String lyrics[], int lyricsIndex) {
String lyric = lyrics[lyricsIndex];
if (lyric.length() < 4 ) { return getLyric(lyrics, lyricsIndex + 1); } if ((lyric.toLowerCase().contains("chorus")) || (lyric.toLowerCase().contains("verse")) || (lyric.toLowerCase().contains("[")) || (lyric.toLowerCase().contains("(")) || (lyric.toLowerCase().contains("nigga"))) { return getLyric(lyrics, (int) random(lyrics.length)); } char firstLetter = lyric.charAt(0); char lastLetter = lyric.charAt(lyric.length()-1); if (firstLetter == '\"') { return lyric.substring(1); } if (((firstLetter >= 'a' && firstLetter <= 'z') || (firstLetter >= 'A' && firstLetter <= 'Z')) && ((lastLetter >= 'a' && lastLetter <= 'z') || (lastLetter >= 'A' && lastLetter <= 'Z'))) {
return lyric;
} else {
return getLyric(lyrics, lyricsIndex + 1);
}
}
 
String getRhymingLyric(String[] lyrics, String lyric, int lyricsIndex) {
 
String[] words = RiTa.tokenize(lyric); //words of previous lyric
if (words.length < 3) {
words = RiTa.tokenize(getLyric(lyrics, lyricsIndex+1));
}
String lastWord = words[words.length-1]; //last word of previous lyric
for (int i = (int) random(lyrics.length); i < lyrics.length; i++ ) {
if (abs(lyricsIndex-i) <= 300) {
continue;
}
 
String newLyric = lyrics[i];
if (newLyric.length() < 3) {
continue;
}
if (newLyric.toLowerCase().equals(lyric.toLowerCase())) {
return getRhymingLyric(lyrics, lyric, lyricsIndex);
}
 
String[] newWords = RiTa.tokenize(newLyric); //words of previous lyric
if ((newWords.length < 3) || (newWords.length > 10)) {
continue;
}
if (newWords[0].startsWith("\"")) {
newWords[0] = newWords[0].substring(1);
}
 
String newLastWord = newWords[newWords.length-1]; //last word of previous lyric
String lastWordLC = lastWord.toLowerCase();
String newLastWordLC = newLastWord.toLowerCase();
int count = 0;
for (int n = 0; n < newWords.length; n++) { String word = newWords[n].toLowerCase(); if ((word.equals("chorus")) || (word.equals("nigga")) || (word.equals("christmas")) || (word.equals("santa"))) { count+=1; } } if (count > 0) {
continue;
}
if (newLastWordLC.equals("eight")) {
continue;
}
if (lastWordLC != newLastWordLC) {
if (lyric.toLowerCase() != newLyric.toLowerCase()) {
if (RiTa.isRhyme(lastWordLC, newLastWordLC, false)) {
return newLyric;
} else {
continue;
}
}
}
}
return getLyric(lyrics, (int)random(lyrics.length-1));
}
 
void title() {
background(255);
//if (on) {
for (int e = 0; e < numcircles1; e++) {
noFill();
strokeWeight(random(.1,3.5));
int colorr = (int) random(170,255);
int colorg = (int) random(170,220);
int colorb = (int) random(220,245);
int rad = (int) random(30, 200);
int x = (int) random(0, width);
int y = (int) random(0, height);
//String col = random(colors);
int p = (int) random(1, 8);
for (int g = 0; g < p; g++) {
stroke(colorr, colorg, colorb, 225-(g*40));
ellipse(x, y, rad, rad);
rad*=.89;
}
}
textAlign(CENTER);
fill(255,180,80);
textFont(font2);
text("Lyric Poetry", width/2, height/3+50);
fill(0);
textFont(font1);
text("by Casher", width/2, height/3+85);
}

casher-parrish

I really appreciate how Parrish continued the outer space analogy throughout the whole presentation. This not only made it easier for someone like me, with little knowledge on semantics, to understand the more technical aspects of the process of generative poetry and the concept of semantic space, but it also made everything much more beautiful. Outer space is wonderfully vast, complex, and unexplored, somewhat full of opportunities (of life, happiness - stars) but mostly of empty nothingness (the nonsensical unknown); when she finally described herself as an "explorer," I realized that this metaphor is a perfect description of my (and potentially others' at the STUDIO) purpose at CMU. Like Robin Sloan said during his lecture, we are all "media inventors" -- the quotes being a symbol of the unknown, nonsensical nature of the new media art we are inventing. Our projects are unfamiliar to the public and are therefore not yet semantically normal. Although this could be a frightening aspect of new media art, Parrish's discussion has inspired my desire to break through the "frontier" and make sense of the nonsensical.

casher-ocannoli-Automaton

"Skipper's Dreamhouse"
    

"Skipper's Dreamhouse" is a scary house that first reacts to movement at a far distance, then waits for the viewer to come closer. Once the viewer is at a certain close distance, a random timer is triggered and the deconstructed Barbie doll, Skipper, pops up to scare the viewer. Throughout making the project, various decisions were made often having to compromise aesthetic over practicality; however, on the whole the house fits the spooky, homemade, childish vibe. If we were to further this project, we'd love to add sound, either with sound effects or music that reacts to the viewer, etc. As for the title, we slowly created this narrative that Barbie's little sister, Skipper, wanted her own dream house and it is terribly spooky. We joked that it could represent somewhat of a critique on Barbie and the stigma surrounding her, as we have deconstructed the doll to just her flailing limbs, showing how her appeal consists only of mindless, emotionless body -- an ultimately ~spooky~ idea to be teaching little girls. We contributed equally to the concept of the project and problem solving, where Cassie contributed more to the Arduino software and Olivia to the outer appearance. Overall, although we hit a lot of speed bumps, it was fun and the problem solving resulted in a satisfyingly spooky project.

casher-LookingOutwards04

Physical Telepresence is a project by MIT's Tangible Media group from 2014. A Kinect is hung above one platform and captures a bird's eye view of various hand movements made by the user, and the program transforms that data into a physical and almost graphical representation of your hands, creating an accurate "topography" of the distance between your hands and the platform with white pegs. I am interested in this project because I actually got to experience it when I went to the MIT Media Lab's 30th Anniversary celebration (both of my parents went to the Media Lab for grad school). At the time I didn't know I would become so excited about these kinds of works, and that it was considered art! I think the design of Telepresence is especially well done -- it has a really nice aspect of simplicity. This is the kind of work I aspire to make as an artist -- I really appreciate how that with a good eye and by using sensors, you can take natural, fluid aspects of the world and visualize it in a totally nuanced way. And the interactive component is inspiring as well. I love creating art that allows the viewer/user to have a personal experience with it, as it allows them to think deeper about the meaning of the piece. I am excited to see what kinds of visualization this leads to.

casher-body

(wow I look tired)

I had been intrigued by the last project's option to design a drawing program in Glitch, so I thought it would be fun to explore that in this project. I also wanted to create something that involves the whole body in order to make the user look ridiculous. Combining the two, I made this silly drawing application (which, I realized, can also double as an exercise "machine," if the user plays with it intensely enough). In my process, since this is a live application, I had to figure out how to track the different motions of various body parts and calculate their distances from specific points on the screen. Originally I had planned for the wideness of the opening of your mouth to control the stroke weight of the lines, but I discovered that using this code template, which doesn't analyze the mouth, was easier for me to use, so I had to change my idea a little bit. I am mostly happy with the product here -- it is very similar to the vision I had in the beginning, which is awesome because it means that I have either (or both) made progress in my coding abilities or learned to not be overly ambitious in my projects! If I could change a few things, I would first try to make it collaborative, so that people could draw together; and once I realized that it could be a form of exercise, I thought it could be cool to make it a game where the user would have to race a clock to draw something of specific shape, color, and size, which would force them to hop, squat, and move pretty quickly.

Directions:

You draw around the board with your nose. To turn on the pen, lift your left hand until it crosses the top white bar. Pick up the pen by touching the left bar with your left hand, and back up to the top to turn it on again. To change the color of the pen, jump high enough that both feet cross above the bottom white line. To make the pen thicker, jump so your nose crosses the top line; to thin the pen, duck low enough that your nose crosses the bottom line. To erase all lines, cross the right bar with your right hand.

 

let video;
let poseNet;
let poses = [];
let colors = ["black", "white", "red", "orange", "yellow", "green", "blue", "purple", "pink", "brown", "grey"]
let colorindex = 0;
let inArea = false;
let drawing = false;
let inArea2 = false;
let inArea3 = false;
let allLines = [];
let lines = [];
let circle = [65, 50, 30, 30];
let prevKpX;
let prevKpY;
let strokewidth = 3;
 
// Storing the last keypoint position
let lastKeypoints = [];
 
function setup() {
  createCanvas(640, 480);
  frameRate(24);
  video = createCapture({
    audio: false,
    video: {
      width: {
        ideal: 640
      },
      height: {
        ideal: 480
      }
    }
  });
  video.size(width, height);
 
  // Create a new poseNet method with a single detection
  poseNet = ml5.poseNet(video);
  // This sets up an event that fills the global variable "poses"
  // with an array every time new poses are detected
  poseNet.on('pose', function(results) {
    poses = results;
  });
  // Hide the video element, and just show the canvas
  video.hide();
 
  //print(lastKeypoints); 
  // setup original keypoints
  createDefaultKeypoints();
  //print(lastKeypoints);
}
 
// Create default keypoints for easing. 
function createDefaultKeypoints() {
  let numKeypoints = 17;
  for (let i = 0; i < numKeypoints; i++) {
    newKeypoint = {
      x: width / 2,
      y: height / 2
    }
 
    lastKeypoints.push(newKeypoint);
  }
}
 
function updateKeypoints() {
  // If there are no poses, ignore it.
  if (poses.length <= 0) {
    return;
  }
 
  // Otherwise, let's update the points; 
 
  let pose = poses[0].pose;
  let keypoints = pose.keypoints;
 
  for (let kp = 0; kp < keypoints.length; kp++) {
 
    let oldKeypoint = lastKeypoints[kp];
    let newKeypoint = keypoints[kp].position;
 
    let interpX = lerp(oldKeypoint.x, newKeypoint.x, .3);
    let interpY = lerp(oldKeypoint.y, newKeypoint.y, .3);
 
    let interpolatedKeypoint = {
      x: interpX,
      y: interpY
    }
 
    lastKeypoints[kp] = interpolatedKeypoint;
  }
}
 
function draw() {
  push();
  translate(width, 0, 0);
  scale(-1, 1);
  image(video, 0, 0, width, height);
 
 
  updateKeypoints();
 
  drawKeypoints();
  pop();
 
  strokeWeight(5);
  stroke(255);
  fill(255);
  line(0, height * .9, width, height * .9);
  line(0, height *.1, width, height*.1);
  line(width*.1, 0, width*.1, height);
  line(width*.9, 0, width*.9, height);
  stroke(colors[colorindex]);
  ellipse(circle[0], circle[1], circle[2], circle[3]);
}
 
// A function to draw ellipses over the detected keypoints
function drawKeypoints() {
  for (let i = 0; i < lastKeypoints.length; i++) {
    if (i == 0) {
      keypoint = lastKeypoints[i];
      fill(colors[colorindex]);
      //console.log(allLines);
      for (let i = 0; i < allLines.length; i++) {
        for (let j = 0; j < allLines[i].length; j++) { //console.log("c"); stroke(colors[allLines[i][j][4]]); strokeWeight(allLines[i][j][5]); line(allLines[i][j][0], allLines[i][j][1], allLines[i][j][2], allLines[i][j][3]); } } if (drawing == true) { if (lastKeypoints[9].y > height * .1) {
          stroke(colors[colorindex]);
          //console.log(lines);
					lines[0] = [lastKeypoints[0].x, lastKeypoints[0].y, lastKeypoints[0].x, lastKeypoints[0].y, colorindex, strokewidth]
          x1 = prevKpX;
          y1 = prevKpY;
          x2 = lastKeypoints[0].x;
          y2 = lastKeypoints[0].y;
          lines.push([x1, y1, x2, y2, colorindex, strokewidth]);
          for (i = 1; i < lines.length; i++) {
            stroke(colors[lines[i][4]]);
            strokeWeight(lines[i][5]);
            line(lines[i][0], lines[i][1], lines[i][2], lines[i][3]);
          }
          prevKpX = x2;
          prevKpY = y2;
        }
 
      }
      if (lastKeypoints[9].y < height * .1) { if (drawing == false) { drawing = true; } } if (lastKeypoints[9].x > width * .9) {
        if (drawing == true) {
          allLines.push(lines);
          lines = [];
          drawing = false;
        }
      }
      if (lastKeypoints[10].x < width*.1) {
        allLines = [];
        lines = [];
        drawing = false;
      }
      if (lastKeypoints[0].y < height*.1) {
        if (inArea2 == false) {
          if (circle[2] < 80 && circle[3] < 80 && strokewidth < 52) { circle[2] += 5; circle[3] += 5; strokewidth += 3; inArea2 = true; } } } else { inArea2 = false; } if (lastKeypoints[0].y > height*.9) {
        if (inArea3 == false) {
          if (circle[2] > 10 && circle[3] > 10 && strokewidth > 3) {
            circle[2] -= 5;
            circle[3] -= 5;
            strokewidth -= 3;
            inArea3 = true;
          }
        }
      }
      else {
        inArea3 = false;
      }
      stroke(255);
      fill(255);
      strokeWeight(0);
      ellipse(lastKeypoints[0].x, lastKeypoints[0].y, 10, 10);
      ellipse(lastKeypoints[9].x, lastKeypoints[9].y, 20, 20);
      ellipse(lastKeypoints[10].x, lastKeypoints[10].y, 20, 20);
      ellipse(lastKeypoints[15].x, lastKeypoints[15].y, 20, 20);
      ellipse(lastKeypoints[16].x, lastKeypoints[16].y, 20, 20);
      if (lastKeypoints[15].y < height * .9 && lastKeypoints[16].y < height * .9) {
        if (inArea == false) {
          colorindex = (colorindex + 1) % colors.length;
          inArea = true;
        }
      } else {
        inArea = false;
      }
    }
  }
}

casher-LookingOutwards03

I looked at Danny Rozin for this Looking Outwards, as I recognized his name when I saw it on the list of artists Golan provided. When I clicked on his website, I realized that I knew him from my Art and Arudino class -- I am working on an "Interactive" project in that class right now as well, and my professor had also used the wooden mirror as an example of what kinds of interactivity can be achieved with Arduino (Rozin made the wooden one just with servos). It amazes me how basically anyone could make this kind of art, since Arduino is so accessible and easy to learn. There is a beauty to how simple it is, yet this idea is so creative.

I love how there is a juxtaposition between the facts that many of his "mirrors" are made with natural or recycled materials, but they are technically put together and controlled in a calculated and digitized way with software. I also like how Rozin questions the self in his artwork, as he has come up with so many ways to represent reflection. It's interesting to discuss whether he is trying to critique society and its narcissism by showing how we interact with our reflections even when we are reduced to just shadows, or if he means for individuals to look deeper within themselves and discover things they couldn't see on their obvious surfaces.

casher-Telematic

Starry Night AgarioInteractive Embedded App

Gif

Sequential Sketches
My game is a simple take on Agar.io. To play, move the mouse around the screen, and your circle will follow in the center. The larger stars can be eaten to make you bigger, whereas if you eat the smaller stars they will release all of your current points and return you to the starting size. You can also eat other players to gain points. The goal is to try and get as big as possible without exploding from the little stars.
As seen in my sketches, this game was not my original plan. I ended up being too ambitious for my abilities in my first two ideas. The first was a "Hungry Hungry Hippo" type game, where all players would move their triangle characters around with the mouse, take pebbles from the center of the screen, stash them in their respective circles, and then proceed to steal pebbles from each other until one had 10 in their circle. When I realized that was too complicated, I tried to make a simpler shooter game, where each character controlled a triangle with the mouse and could shoot other players out of the game with the space bar. You can see all the geometric transformation calculations I was trying to do; unfortunately I was unable to figure those out, so finally I moved on to this simpler idea.
I wanted from the beginning for my environment to be mostly anonymous, as I was intrigued by the idea of creating competition that didn't really stem from anywhere. A lot of the time, people say that competition is based on social norms and expectations that society has created for specific groups, so we tend to feel the need to meet them in order to fit in. However, anonymously, in my game there is nothing to prove and no one to impress -- therefore, it shows how people are still eager to come out on top even without a reason, and that we are competitive by nature. I show each player their own name to create an identity for themselves, but I hide the names of other players to give the least information out as possible. The only number that defines them is how many points they have -- or, how competitive they are.