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);
}