I really like her point about how "nonsense is frustrating and scary." People resist nonsense, in this case odd or previously-unspoken orders of words, because it makes them uncomfortable. This makes me think about where something stops being sensible and becomes nonsensible. By an extreme definition, anything original is nonsense, as what people see as sensible comes form what they know. It is the familiar parts of something original that makes the whole thing sensible. Parrish's example of nonsense has so few familiar parts that it is often considered nonsense.

But should I create nonsensible work? I don't think that I should make nonsense to the far extreme. If I am trying to convey a message, then I may be trying to be relatable. True nonsense cannot be relatable outside the fact that it is nonsense at all.


What stuck with me about Parrish's talk was the overall theme of the exploration of uncertainty. It's something thats commonly done in science/math areas, yet not so much in the humanities. Parrish gives many options to exploring the unknown, from the most literal way--for example, mapping out semantic space through n-grams, and finding the empty spaces--to more abstract ways, such as creating new words through splicing existing words together and generating new definitions. It was something I thought about a lot as I was working on the book project as well, since I felt pulled towards making something more generative/abstract, yet ultimately made something that explored connections between existing language. These two sides of the spectrum feel like the two types of infinity, one that exists between numbers and goes towards the infinitely small, and the other one that exists towards infinite largeness.


My biggest take away from this lecture was that the greatest tool to understanding chaos is to frame it in as many different ways as possible, finding which frame is the most compatible with our existing tools of perception. It was really interesting to see how organizing word relationships in 3D space gives you the ability to judge those relationships in a really tangible way. It takes information that exists in our minds (as reflected by the medium of recorded text) and represents the most significant aspects of that data in way that utilizes our spacial reason to show us those relationships. I also appreciated the connection she made between these data extrapolation fields and autonomous recording of weather data from balloons. It's important to realize what these systems are at their lowest level, which are systems that process information on our behalf when we are unable or too impatient to do so.


Reddit Bible


In short, the Reddit Bible compares questions found on 8 advice boards of reddit with interrogative sentences found in the bible.

After much wrangling with religious-text based concepts, I decided to create a "Reddit Advice" board by answering biblical questions with Reddit content. I scraped the Bible for interrogative sentences, and found similarities in questions scraped from Reddit advice boards, using sentence embedding. From there, I wanted to to answer the Bible Questions with Markov-chain generated answers based on the thread responses of the respective Reddit questions.

Unexpectedly, I loved the results of the "similar" reddit and bible questions--the seeming connections between the two almost hint at some sort of relation between the questions people are asking now and in Biblical times. Though I did go through with the Markov chained responses, the results were a bit jumble-y and seemed to take away from what I liked about the juxtaposed questions. Ultimately, I made the decision to cut the Markov chains, and to highlight the contrast in pairs of questions, as well as how similar the computer thinks the question pairs are.


I originally wanted to generate some sort of occult text, ie. Chinese Divination. I ended up pivoting to the more normative of religious texts, the Bible to be specific, since I have a lot of personal experience with this one. Prior to the "reddit advice" board, I actually had the opposite idea, of making a "christian advice" board where I would gather 4chan questions, and answer them with markov chain generated responses based on real christian advice forums. I scraped a christian advice forum, but the results were too few and inconsistent, so I knew I had to pivot a bit. That's when I flipped the idea and decided to reverse it to answering bible questions with reddit data. (4chan's threads were a little too inconsistent and lacking compared with reddits thousand-count response threads).

If anyone ever wants buttloads of  responses from a christian forum


Once I solidified my concept, it was time to execute, one step at a time.

  1. Getting matching question pairs from Reddit and the Bible
    1. Getting questions from Reddit
      1. Reddit has a great API called PRAW
      2. Originally I only scraped r/advice, but towards the end, I decided to bump it up and scrape 8 different advice subreddits: r/advice, r/internetparents, r/legal_advice, r/need_a_friend, r/need_advice, r/relationship_advice, r/tech_support, r/social_skills
        1. Using PRAW, i looked at top posts of all time, with no limit
      3. r/Advice yielded over 1000 question sentences, and the other advice subreddits ranged more or less around that number.
      4. Lists with the lists of scraped questions:
        # load 400 iterations 
        # format [distance, subreddit, reddit question, bible verse, bible question]
        # get bible questions
        from final_bible_questions import bible_questions
        from split_into_sentences import split_into_sentences
        # write to file
        data_file= open("rqbq_data.txt", "w")
        rel_advice_file = open("rel_advice.txt", "w")
        legal_advice_file = open("legal_advice.txt", "w")
        tech_support_file = open("tech_support.txt", "w")
        need_friend_file = open("needfriend.txt", "w")
        internetparents_file = open("internetparents.txt", "w")
        socialskills_file = open("socialskills.txt", "w")
        advice_file = open("advice.txt", "w")
        needadvice_file = open("needadvice.txt", "w")
        files = [rel_advice_file, legal_advice_file, tech_support_file, need_friend_file, internetparents_file, socialskills_file, advice_file, needadvice_file]
        # libraries
        import praw
        import re
        # reddit keys
        reddit = praw.Reddit(client_id='---',
                             user_agent='script: v.0.0(by /u/swlsheltie)')
        # enter subreddit here ------------------------------------------------------------------------------------action required
        list_subreddits = ["relationship_advice", "legaladvice", "techsupport", "needafriend", "internetparents", "socialskills", "advice", "needadvice"]
        relationshipadvice = {}
        subreddit_dict_list=[relationshipadvice, legaladvice, techsupport, needafriend, internetparents, socialskills, advice, needavice]
        relationshipadvice_questions = []
        legaladvice_questions =[]
        questions_list_list=[relationshipadvice_questions, legaladvice_questions, techsupport_questions, needafriend_questions, internetparents_questions, socialskills_questions, advice_questions, needavice_questions]
        # sub_reddit = reddit.subreddit('relationship_advice')
        for subreddit in list_subreddits:
            i = list_subreddits.index(subreddit)
            sub_reddit = reddit.subreddit(subreddit)
            txt_file_temp = []
            for submission in
                # print(submission)
                print("...getting from reddit", counter)
                submission_txt = str(reddit.submission(id=submission).selftext.replace('\n', ' ').replace('\r', ''))
            for sub_txt in txt_file_temp:
                sent_list = split_into_sentences(sub_txt)
                for sent in sent_list:
                    print("grabbing questions")
                    if sent.endswith("?"):
            print("writing file")
            print("written file, next")
        # for list_ in questions_list_list:
        #     print("\n")
        #     print(list_subreddits[questions_list_list.index(list_)])
        #     print(list_)
        #     print("\n")
    2. Getting questions from the bible
      1. Used the King James Version, because of this awesome text file, that only has the text in it (no verse numbers, etc) (would bite me in the ass later on)
      2. Found some code on Stack Overflow that allowed me to get a list of the sentences in the bible
      3. Originally used RITA to get the question sentences, then towards the end (since rita --> python was too much of a hassle), I just went through found all sentences that ended with a "?".
        1. file = open("bible.txt", "r")
          empty= open("bible_sent.txt", "w")
          bible =
          from nltk import tokenize
          import csv
          import re
          alphabets= "([A-Za-z])"
          prefixes = "(Mr|St|Mrs|Ms|Dr)[.]"
          suffixes = "(Inc|Ltd|Jr|Sr|Co)"
          starters = "(Mr|Mrs|Ms|Dr|He\s|She\s|It\s|They\s|Their\s|Our\s|We\s|But\s|However\s|That\s|This\s|Wherever)"
          acronyms = "([A-Z][.][A-Z][.](?:[A-Z][.])?)"
          websites = "[.](com|net|org|io|gov)"
          # final_output=[]
          def split_into_sentences(text):
              text = " " + text + "  "
              text = text.replace("\n"," ")
              text = re.sub(prefixes,"\\1",text)
              text = re.sub(websites,"\\1",text)
              if "Ph.D" in text: text = text.replace("Ph.D.","PhD")
              text = re.sub("\s" + alphabets + "[.] "," \\1 ",text)
              text = re.sub(acronyms+" "+starters,"\\1 \\2",text)
              text = re.sub(alphabets + "[.]" + alphabets + "[.]" + alphabets + "[.]","\\1\\2\\3",text)
              text = re.sub(alphabets + "[.]" + alphabets + "[.]","\\1\\2",text)
              text = re.sub(" "+suffixes+"[.] "+starters," \\1 \\2",text)
              text = re.sub(" "+suffixes+"[.]"," \\1",text)
              text = re.sub(" " + alphabets + "[.]"," \\1",text)
              if "”" in text: text = text.replace(".”","”.")
              if "\"" in text: text = text.replace(".\"","\".")
              if "!" in text: text = text.replace("!\"","\"!")
              if "?" in text: text = text.replace("?\"","\"?")
              text = text.replace(".",".")
              text = text.replace("?","?")
              text = text.replace("!","!")
              text = text.replace("",".")
              sentences = text.split("")
              sentences = sentences[:-1]
              sentences = [s.strip() for s in sentences]
              return (sentences)
              # final_output.append(sentences)
          # with open('christian_forums1.csv', newline='') as csvfile:
          #     reader = csv.reader(csvfile)
          #     for row in reader:
          #         for i in range(2): 
          #             if (i==1) and (row[i]!= ""):
          #                 input_txt = row[0]
          #                 # print(text)
          #                 # list_sent = tokenize.sent_tokenize(text)
          #                 # sentences.append(list_sent)
          #                 list_sent= split_into_sentences(input_txt)
          #                 final_output.append(list_sent)
          # list_sent = split_into_sentences(bible)
          # for sent in list_sent:
          #     # print(sent)
          #     empty.write(sent+"\n")
          # empty.close()
          # print(list_sent)
      4. Results: around 1000+ Bible questions, find them here
    3. Get matching pairs!!! 
      1. My boyfriend suggested that I use sentence embeddings to find the best matching pairs. This library is the one i used. It was super easy to install+use! 4.75 Stars
      2. Infersent pumps out a 2d matrix containing each vector of each sentence in the list of sentences that you provide. I gave it the list of bible questions, then ran a loop to get the embeddings of all 8 subreddit question lists.
      3. Then another loop with matrix multiplication to get each matrix with the distances between the bible vs. [respective] subreddit sentences.
      4. Since the matrix contains such precise information about how close two sentences, are I wanted to visualize this data. I saved the "distances," and used circle size to show how close they are.
        1. I didn't have that much time to visually design the book, which I regret, and the circle sizes were obviously not that communicative about what they represented. I ended up including a truncated version of the distances on each page.
    4. Markov chaining the responses
        1. Now that I had my reddit questions to their bible question counterparts, I wanted to get a mishmash of the respective response threads of the submissions that the questions came from.
        2. submission = reddit.submission(pair["thread_id"])
          for top_level_comment in submission.comments:
              if isinstance(top_level_comment, MoreComments):
              # print(top_level_comment.body)
          comment_body = split_into_sentences(comment_body_single_str)
          # print(" ".join(comment_body))
          text_model = markovify.Text(comment_body)
          for i in range(10):
        3. This was an example of my result:
          1.  Submission Data: {'thread_id': '7zg0rt', 'submission': 'Deep down, everyone likes it when someone has a crush on them right?   So why not just tell people that you like them if you do? Even if they reject you, deep down they feel good about themselves right? '}

            Markov Result: Great if you like it, you generally say yes, even if they have a hard time not jumping for joy.

            Us guys really do love it when someone has a crush on them right?

            I think it's a much more nuanced concept.

            In an ideal world, yesDepends on the maturity of the art.

            But many people would not recommend lying there and saying "I don't like you", they would benefit from it, but people unequipped to deal with rejection in a more positive way.

            If it's not mutual, then I definitely think telling them how you feel, they may not be friends with you or ignore you, or not able to answer?

            I've always been open about how I feel, and be completely honest.

            In an ideal world, yesDepends on the maturity of the chance to receive a big compliment.

            So while most people would argue that being honest there.

            I think that in reality it's a much more nuanced examples within relationships too.

        4. While this was ok, I felt like the Markov data just probably wasn't good (extensive) enough to sound that different from the source. It didn't seem like this would add anything to the concept, so I decided to cut it.
    5. Organizing the matching pairs 
      1. The part that I probably had the most difficulty with was trying to organize the lists into [least distant] to [most distant] pairs. Seemed really easy in my head, but for reason, I just had a lot of trouble executing.
        1. However, it paid off in the end, as I was able to get random pairs from only the closest related 100 pairs from each of the 8 subreddit/bible lists.
        2. import numpy
          import torch
          import json
          from rel_advice import rel_advice
          from legal_advice import legal_advice
          from techsupport import techsupport
          from needfriend import needfriend
          from internetparents import internet_parents
          from socialskills import socialskills
          from advice import advice
          from needadvice import needadvice
          from final_bible_questions import bible_questions
          from bible_sent import bible_sentences
          final_pairs= open("final_pairs.txt", "w")
          # final_pairs_py= open("final_pairs_py.txt", "w")
          with open("kjv.json", "r") as read_file:
              bible_corpus = json.load(read_file)
          rqbq_data_file = open("rqbq_data.txt", "w")
          subreddit_questions = [rel_advice, legal_advice, techsupport, needfriend, internet_parents, socialskills, advice, needadvice]
          list_subreddits = ["relationship_advice", "legaladvice", "techsupport", "needafriend", "internetparents", "socialskills", "advice", "needadvice"]
          # for 
          from models import InferSent
          V = 2
          MODEL_PATH = 'encoder/infersent%s.pkl' % V
          params_model = {'bsize': 64, 'word_emb_dim': 300, 'enc_lstm_dim': 2048,
                          'pool_type': 'max', 'dpout_model': 0.0, 'version': V}
          print("HELLO", MODEL_PATH)
          infersent = InferSent(params_model)
          W2V_PATH = 'dataset/fastText/crawl-300d-2M.vec'
          with open("encoder/samples.txt", "r") as f:
              sentences = f.readlines()
          print("embed bible")
          temp_bible = bible_questions 
          embeddings_bible = infersent.encode(temp_bible, tokenize=True)
          normalizer_bible = numpy.linalg.norm(embeddings_bible, axis=1)
          normalized_bible = embeddings_bible/normalizer_bible.reshape(1539,1)
          pairs = {}
          for question_list in range(len(subreddit_questions)):
              print("setting variables: ", list_subreddits[question_list])
              temp_reddit = subreddit_questions[question_list] #TRIM THESE TO TEST SMALLER LIST SIZES
              print("embed", list_subreddits[question_list], "questions")
              embeddings_reddit = infersent.encode(temp_reddit, tokenize=True)
              print("embed_reddit dim: ",embeddings_reddit.shape)
              print("embed_bible dim: ", embeddings_bible.shape)
              normalizer_reddit = numpy.linalg.norm(embeddings_reddit, axis=1)
              print("normalizer_reddit dim: ", normalizer_reddit.shape)
              print("normalizer_bible dim: ", normalizer_bible.shape)
              temp_tuple = normalizer_reddit.shape
              normalized_reddit = embeddings_reddit/normalizer_reddit.reshape(temp_tuple[0],1)
              print("normalized_reddit dim:", normalized_reddit)
              print("normalized_bible dim:", normalized_bible)
              print("normed normalized_reddit dim: ", numpy.linalg.norm(normalized_reddit, ord=2, axis=1))
              print("normed normalized_bible dim: ", numpy.linalg.norm(normalized_bible, ord=2, axis=1))
              reddit_x_bible = numpy.matmul(normalized_reddit, normalized_bible.transpose())
              print("reddit x bible", reddit_x_bible)
              matrix = reddit_x_bible.tolist()
              distances = []
              distances_index = []
              for reddit_row in matrix:
                  closest = max(reddit_row)
                  cur_index = matrix.index(reddit_row)
                  final_pairs.write("\n-------\n" + "distance: "+ str(closest)+"\n" +str(list_subreddits[question_list])+"\n"+subreddit_questions[question_list][cur_index]+"\n"+ bible_questions[reddit_row.index(closest)]+"\n-------\n")
              for distance in distances: 
                  inde_x = distances_double.index(distance)
              # print(pairs)
          # for pair in pairs: 
          #     # print( "\n-------\n", reddit_questions[pair],"\n", bible_questions[pairs[pair]], "\n-------\n")
          #     # export_list.append([max_nums[counter], pair, pairs[pair], reddit_questions[pair],  bible_questions[pairs[pair]]])
          #     counter+=1
          # # final_pairs_py.write(str(export_list))
          # # final_pairs_py.close()
          # final_pairs.close()
                  # nums.append(closest)
                  # max_nums.append(closest)
                  # for distance in max_nums:    
                  # row = nums.index(distance) #matrix row 
                  # column = matrix[row].index(distance)
                  # pairs[row]= column
                  # pairs[list_subreddits[question_list]][closest]={}
                  # reddit_bodies.append()
          # export_list = []
          #     nums=[]
          #     max_nums = []
          #         max_nums.sort()
          #     max_nums.reverse()
          # load 400 iterations 
          # format [distance, subreddit, reddit question, bible verse, bible question]
          # build dictionary in loop, and keep list of min distances 
          # final_pairs.write(str(pairs))
          # counter = 0
          # bible_x_reddit = numpy.matmul(embeddings_bible, reddit_trans)
          # print(bible_x_reddit)
  2. Basil.js
    1. Using Basil's CSV method, I was able to load pair data into the book.
      1. from rqbq_data import rqbq_dictionary
        from bibleverses import find_verse
        import random
        import csv
        from rel_advice import rel_advice
        from legal_advice import legal_advice
        from techsupport import techsupport
        from needfriend import needfriend
        from internetparents import internet_parents
        from socialskills import socialskills
        from advice import advice
        from needadvice import needadvice
        from final_bible_questions import bible_questions
        # print(len(rqbq_dictionary))
        # print(rqbq_dictionary.keys())
        list_subreddits = ["relationship_advice", "legaladvice", "techsupport", "needafriend", "internetparents", "socialskills", "advice", "needadvice"]
        subreddit_questions = [rel_advice, legal_advice, techsupport, needfriend, internet_parents, socialskills, advice, needadvice]
        def getPage():
            subreddit = list_subreddits[subreddit_index]
            print("subreddit: ", subreddit)
            length = len(rqbq_dictionary[subreddit]["distances"])
            print("length: ", length)
            random_question = random.randint(0,500) #SPECIFY B AS CUT OFF FOR REDDIT/BIBLE ACCURACY. 1=MOST ACCURATE, LENGTH-1 = LEAST ACCURATE 
            print("random question num: ", random_question)
            print("distance of random question: ", rqbq_dictionary[subreddit]["distances"][random_question])
            print("index of random question: ", rqbq_dictionary[subreddit]["distances_indexer"][random_question])
            index_rand_q_bible = rqbq_dictionary[subreddit]["bible_question"][index_rand_q]
            # print(index_rand_q, index_rand_q_bible)
            print("question: ", subreddit_questions[subreddit_index][index_rand_q])
            print("verse: ", bible_questions[index_rand_q_bible])
            verse = find_verse(bible_questions[index_rand_q_bible])
            write_csv.append([rqbq_dictionary[subreddit]["distances"][random_question], subreddit, subreddit_questions[subreddit_index][index_rand_q], verse, bible_questions[index_rand_q_bible]])
        # getPage()
        for i in range(15):
        with open('redditxBible.csv', 'w', newline='') as f:
                writer = csv.writer(f)
                writer.writerow(["distance", "subreddit", "reddit_question", "verse", "bible_question"])
      2. Example of one of the books' CSV: redditxBible
    2. As said earlier, not having the Bible verse data was a pain when I realized it would be nice to have the verses. So I had to run each bible question in this code to get the verses:
      1. import json
        with open("kjv.json", "r") as read_file:
            bible_corpus = json.load(read_file)
        sample = " went up against the king of Assyria to the river Euphrates: and king Josiah went against him; and he slew him at Megiddo, when he had seen him."
        def find_verse(string):
            for x in bible_corpus["books"]:
                for chapter  in x["chapters"]: 
                    # print(chapter["verses"])
                    for verse in chapter["verses"]:
                        if string in verse["text"]:
                            return (verse["name"])
    3. Designing the book
      1. Unfortunately, I didn't save many iterations from my design process, but I did play with having two columns, and other ways of organizing the text+typography.
    4. I had used Basil.js before in Kyu's class last year, so that was really helpful in knowing how to auto-resize the text box sizes.
      1. That way, I was able to get exact distances between all the text boxes.
    5.  I had some trouble with rotating the text boxes and getting the locations after rotation.
    6. The circle was drawn easily by mapping the distance between sentences to a relative size of the page.







Overall, i really enjoyed making this book. This project was super interesting in that I feel like I really had to apply more programming knowledge than previous projects in order to combine all the parts to get fully usable code. They couldn't only work disparately, they had to able to work all together too. Piping all the parts together was definitely the toughest part. I only wish I had more time to design the book, but i will probably continue working on that onwards.

See a list of ALL (8000+) the pairs here


There were a two points that stuck out with me about this talk, each of which I'll go over below:

  1. Relating generative language to exploration. I really liked Parrish's theme of space exploration throughout the talk, partially because it gave an exciting tone to the lecture, but mostly because it alluded to how much we can learn from generative writings. Parrish was astute in observing that since bots aren't constrained to norms the way that humans are, they can come up with things humans would be unable to in their current state.
  2. What is known and unknown is in the eye of the beholder. I really liked how Parrish talked about how what we deem as nonsense or sensical depends on who we listen to. Recent movements such as as #MeToo or Gay Marriage Equality would have been completely dismissed 100 years ago. Conversely, we now look back certain midcentury opinions that were held as facts and gawk. This shows the importance of both lending a voice to people that have none and maintaining an open mind.


Punk Rock Album Generator
The Punk Rock Album Generator is spreading anarchy and rebelling against the system with its questionably plagiarized lyrics

Going into this project I was thinking about how I typically don't incorporate my identity as a musician into my work, both in CFA and in SCS. Until recently my passion for music and punk rock has been a separate entity from my identity as an artist and creative coder. While I was looking at various books looking for inspiration I found my band's bass guitarist's massive Leonard Cohen bass songbook of popular rock songs, and I realized there is a standard set of conventions used in tabs, transcripts and sheet music that are pretty much universally followed: lyrics spaced over on top of bars of sheet music/tablature. To me this was a great way to get my feet wet with letting my musical self into my art world.

I used Markov chains over a relatively small and hand-picked corpus that consisted of lyrics from my favorite Green Day, Offspring, Rage Against The Machine and Sublime albums. After some tinkering I was satisfied with the whacky lyrical compositions being spit out by my program and I thought it was cool that every now and then I'd recognize the particular 2-3 songs that were being mashed together and when I shared the lyrics with my band they picked up on this as well.

At first I thought learning Basil to handle layouts was going to slow me down in that I'd have to learn a new skill, but in retrospect Basil was a very versatile and intuitive program and without it I could not have generated my totally random sheet music. Were I to revisit this project I would want to generate actual MIDI files based in music theory that actually sound like coherent songs, as well as add parts for more instruments. This is one of the few projects I have done this semester that I want to keep going with until I feel like I can sit down and record myself performing an entire procedurally-generated punk rock album on every instrument I know.


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
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
//size(432, 648); 
//bExportingPDF = false;
size(432, 648, PDF, "poems25.pdf");
bExportingPDF = true;
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) {
} else {
void drawForPDFOutput() {
// When finished drawing, quit and save the file
if (outputPageCount >= nPages) {
else if (outputPageCount == 0) {
PGraphicsPDF pdf = (PGraphicsPDF) g;
if (outputPageCount < (nPages-1)) {
else {
PGraphicsPDF pdf = (PGraphicsPDF) g; // Get the renderer
if (outputPageCount < (nPages-1)) {
pdf.nextPage(); // Tell it to go to the next page
void drawForScreenOutput() {
int whichPageIndex = (int) map(mouseX, 0, width, 0, nPages);
void drawPage(int whichPageIndex) {
whichPageIndex = constrain(whichPageIndex, 0, nPages-1); 
//if (pressed == true) {
for (int e = 0; e < numcircles; e++) {
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) {
text(titles[titleIndex], 30, height/5);
void makeStanza() {
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) {
String newLyric = lyrics[i];
if (newLyric.length() < 3) {
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)) {
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) {
if (newLastWordLC.equals("eight")) {
if (lastWordLC != newLastWordLC) {
if (lyric.toLowerCase() != newLyric.toLowerCase()) {
if (RiTa.isRhyme(lastWordLC, newLastWordLC, false)) {
return newLyric;
} else {
return getLyric(lyrics, (int)random(lyrics.length-1));
void title() {
//if (on) {
for (int e = 0; e < numcircles1; e++) {
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);
text("Lyric Poetry", width/2, height/3+50);
text("by Casher", width/2, height/3+85);


For my generative book, I chose to make a bad design student. The school of design teaches a required course for its freshman students called Placing, in which students produce photo essays. I wrote an algorithm that would generate these essays by taking other students' essays (with their permission) as source material for a markov chain. The resulting essays are a mish-mosh of all sorts of different content that addresses the same assignment (every time the code is run it produces an essay that addresses one of the four assignments given in Placing). These essays do not make much sense, but they tend to contain gems of crazy, nonsensical sentences or outlandish claims about society that are the result of multiple different sentences from the source material being put together in a strange way.

After the text is generated and saved into a JSON file, it is fed through a Basil.js script that places the resulting text onto different pages and pairs each paragraph with a photograph randomly pulled from a collection of photos used in students' essays for that particular assignment.

The code for the text generator is just a Markov chain with no additional logic added to it. I spent some time experimenting with the n-value for the chain because I was trying to find a balance between sentences that were more or less grammatical and not simply lifting full sentences directly from the source material. The code generates a random number of sentences between a range of 60 to 75. It then splits the resulting text into paragraphs of 5 sentences each.

The Basil.js script creates a title page for the book, then lays out two generated Placing essays (I select these essays by hand). Each page of the laid out essay features an image paired with one paragraph.

I'm not totally satisfied with the results. I would have liked the essays to be a little more interesting. At this point, they are more or less just a random set of random sentences that happen to focus on the same general topic. The essays are not random enough to be funny, but they don't make enough sense to be interesting for other reasons. I might be able to it more interesting by increasing the dataset or by having some sort of logical decision making in the code to focus the sentences a little more.


var lines = [], markov, data = [], x = 160, y = 240;
var paragraphs = [];
var essay;
class Paragraph {
    constructor(title, text) {
        this.title = title;
        this.text = text;
function preload() {
    var essayNum = int(random(4));
    switch(essayNum) {
    case 0 :
        essay = "Stuff";
        data[0] = loadStrings('src/Essays/stuff_delgado.txt');
        data[1] = loadStrings('src/Essays/stuff_vzhou.txt');
        data[2] = loadStrings('src/Essays/stuff_carpenter.txt');
    case 1 :
        essay = "Nature";
        data[0] = loadStrings('src/Essays/nature_delgado.txt');
        data[1] = loadStrings('src/Essays/nature_fang.txt');
        data[2] = loadStrings('src/Essays/nature_carpenter.txt');
    case 2 :
        essay = "Neighborhood";
        data[0] = loadStrings('src/Essays/neighborhood_carpenter_fan_zhang.txt');
        data[1] = loadStrings('src/Essays/neighborhood_cho_fang_nishizaki.txt');
        //data[2] = loadStrings('src/Essays/stuff_carpenter.txt');
    default :
        essay = "Trash";
        data[0] = loadStrings('src/Essays/trash_delgado_powell.txt');
        data[1] = loadStrings('src/Essays/trash_choe_fang.txt');
        data[2] = loadStrings('src/Essays/trash_carpenter_ezhou.txt');
function setup() {
    createCanvas(500, 3500);
    textFont('times', 16);
    lines = ["click to (re)generate!"];
    // create a markov model w' n=4
    markov = new RiMarkov(3);
    // load text into the model
    for(var i = 0; i < data.length; i++) {
        markov.loadText(data[i].join(' '));
function drawText() {
    if(lines.length <= 1) {
        text(lines.join(' '), x, y, 400, 400);
    for(var i = 0; i < lines.length; i++) {
        var line = [lines[i]];
        text(line.join(' '), x, y + (i * 410), 400, 400 + (i * 410));
function keyTyped() {
    if(key === ' ') {
        x = y = 50;
        var essayLength = int(random(75, 100));//int(random(4, 9));
        var fullEssay = markov.generateSentences(essayLength);
        for(var i = 0; i < int(essayLength / 10); i++) {
            lines[i] = "";
            if((i + 1) * 10 > essayLength) {
                for(var j = i * 10; j < essayLength; j++) {
                    lines[i] = lines[i].concat(' ', fullEssay[j]);
            } else {
                lines[i] = "";
                for(var j = i * 10; j < (i + 1) * 10; j++) {
                    lines[i] = lines[i].concat(' ', fullEssay[j]);
        var newParagraph = new Paragraph(essay, lines);
        paragraphs[0] = newParagraph;
        var output = {};
        output.paragraphs = paragraphs;
        createButton('SAVE PARAGRAPHS')
            .position(450, 3450)
            .mousePressed(function() {
                saveJSON(output, 'essay.json');



#include "../../bundle/basil.js";
var numPages;
var neighborNums = [48, 40, 33];
var neighborNames = ["carpenter_fan_zhang", "cho_fang_nishizaki", "gersing_kim_powell"];
var natureNums = [5, 13, 6, 6, 5];
var natureNames = ["carpenter", "delgado", "fang", "powell", "zhai"];
var stuffNums = [8, 9, 2, 8, 19, 7];
var stuffNames = ["carpenter", "delgado", "fang", "powell", "vzhou", "zhai"];
var trashNums = [13, 12, 15, 6, 5];
var trashNames = ["carpenter_ezhou", "choe_fang", "choi_zhai", "delgado_powell", "zhai_vzhou"];
var usedImages = [];
function setup() {
	var jsonString2 = b.loadString("neighborhood/essay (18).json");
	var jsonString1 = b.loadString("stuff/essay (44).json");
	b.clear (b.doc());
	var jsonData1 = b.JSON.decode( jsonString1 );
	var paragraphs1 = jsonData1.paragraphs;
	var jsonData2 = b.JSON.decode( jsonString2 );
	var paragraphs2 = jsonData2.paragraphs;
	b.println("paragraphs: " + paragraphs1.length + "+" + paragraphs2.length);
	var inch = 72;
	var titleW = inch * 5.0;
	var titleH = inch * 0.5;
	var titleX = (b.width / 2) - (titleW / 2);
	var titleY = inch;
	var paragraphX = inch / 2.0;
	var paragraphY = (b.height / 2.0) + (inch * 1.5);
	var paragraphW = b.width - inch;
	var paragraphH = (b.height / 2.0) - (inch * 2.0);
	var imageX = inch / 2.0;
	var imageY = inch / 2.0;
	var imageW = b.width - (inch);
	var imageH = (b.height * 0.5) + inch;
	numPages = 0;
	//first page of book
	b.textFont("Archivo Black", "Regular");
	b.textAlign(Justification.CENTER_ALIGN, VerticalJustification.CENTER_ALIGN);
	b.text("Plagiarizing:", inch / 2.0, b.height / 2.0 - inch, b.width - inch, inch);
	b.textFont("Archivo", "Bold");
	b.textAlign(Justification.CENTER_ALIGN, VerticalJustification.CENTER_ALIGN);
	b.text("A reinterpretation of other people's photo-essays from Placing", inch / 2.0, b.height / 2.0, 
		b.width - inch, inch);
	//introduce first essay
	b.textFont("Archivo Black", "Regular");
	b.textAlign(Justification.CENTER_ALIGN, VerticalJustification.CENTER_ALIGN);
	b.text(paragraphs1[0].title + "ing", inch / 2.0, b.height - inch * 2, 
		b.width - inch, inch);
	var coverImageName = imageName(paragraphs1[0].title);
	var coverImage = b.image(coverImageName, inch / 2.0, 
		inch  / 2.0, b.width - inch, (b.height / 2.0) + (2 * inch));;
	for(var i = 0; i &lt; paragraphs1[0].text.length; i++) {
		b.textFont("Archivo", "Regular");
		b.textAlign(Justification.LEFT_ALIGN, VerticalJustification.TOP_ALIGN);
		b.text("\t" + paragraphs1[0].text[i].substring(1), paragraphX, paragraphY, 
			paragraphW, paragraphH);
		var imgName = imageName(paragraphs1[0].title);
		var img = b.image(imgName, imageX, imageY, imageW, imageH);;
	if(numPages % 2 == 0) {
	//Second Photo Essay
	b.textFont("Archivo Black", "Regular");
	b.textAlign(Justification.CENTER_ALIGN, VerticalJustification.CENTER_ALIGN);
	b.text(paragraphs2[0].title + "ing", inch / 2.0, b.height - inch * 2, 
		b.width - inch, inch);
	usedImages = [""];
	coverImageName = imageName(paragraphs2[0].title);
	coverImage = b.image(coverImageName, inch / 2.0, inch  / 2.0, b.width - inch, 
		(b.height / 2.0) + (2 * inch));;
	for(var i = 0; i &lt; paragraphs2[0].text.length; i++) {
		b.textFont("Archivo", "Regular");
		b.textAlign(Justification.LEFT_ALIGN, VerticalJustification.TOP_ALIGN);
		b.text("\t" + paragraphs2[0].text[i].substring(1), paragraphX, paragraphY, 
			paragraphW, paragraphH);
		var imgName = imageName(paragraphs2[0].title);
		var img = b.image(imgName, imageX, imageY, imageW, imageH);;
	//give credit to original authors and photographs
	b.textFont("Archivo", "Bold");
	b.textAlign(Justification.CENTER_ALIGN, VerticalJustification.CENTER_ALIGN);
	b.text(paragraphs1[0].title + "ing", inch / 2.0, (b.height / 2.0) - (inch * 1.5), 
		b.width - inch, inch / 2.0);
	var authors = generateCredits(paragraphs1[0].title);
	b.textFont("Archivo", "Regular");
	b.textAlign(Justification.LEFT_ALIGN, VerticalJustification.TOP_ALIGN);
	b.text("Original text and photos by:", inch / 2.0, (b.height / 2.0) - inch, 
		b.width - inch, 14.4);
	b.text(authors.join(", "), inch, (b.height / 2.0) - inch + 14.4, 
		b.width - (inch * 1.5), inch - 14.4);
	b.textFont("Archivo", "Bold");
	b.textAlign(Justification.CENTER_ALIGN, VerticalJustification.CENTER_ALIGN);
	b.text(paragraphs2[0].title + "ing", inch / 2.0, (b.height / 2.0), 
		b.width - inch, inch / 2.0);
	authors = generateCredits(paragraphs2[0].title);
	b.textFont("Archivo", "Regular");
	b.textAlign(Justification.LEFT_ALIGN, VerticalJustification.TOP_ALIGN);
	b.text("Original text and photos by:", inch / 2.0, 
		(b.height / 2.0) + (inch * 0.5), b.width - inch, 14.4);
	b.text(authors.join(", "), inch, (b.height / 2.0) + (inch * 0.5) + 14.4, 
		b.width - (inch * 1.5), inch - 14.4);
	if(numPages % 2 != 0) {
function newPage() {
function imageName(assignment) {
	var fileName = "";
		if(assignment == "Neighborhood") {	
			var i = b.floor(b.random(neighborNames.length));
			fileName = neighborNames[i] + b.floor(b.random(neighborNums[i]) + 1);
		} else if(assignment == "Nature") {	
			var i = b.floor(b.random(natureNames.length));
			fileName = natureNames[i] + b.floor(b.random(natureNums[i]) + 1);
		} else if(assignment == "Trash") {	
			var i = b.floor(b.random(trashNames.length));
			fileName = trashNames[i] + b.floor(b.random(trashNums[i]) + 1);
		} else {
			var i = b.floor(b.random(stuffNames.length));
			fileName = stuffNames[i] + b.floor(b.random(stuffNums[i]) + 1);	
	return "images/" + assignment + "/" + fileName + ".jpg";
function usedImagesIncludes(fileName) {
	for(var i = 0; i &lt; usedImages.length; i++) {
		if(usedImages[i] == fileName)
			return true;
	return false;
function generateCredits(assignment) {
	if(assignment == "Neighborhood") {	
		return ["Sebastian Carpenter", "Danny Cho", "Sophia Fan", "Alice Fang", "Margot Gersing",
				"Jenna Kim", "Julia Nishizaki", "Michael Powell", "Jean Zhang"];
	} else if(assignment == "Nature") {	
		return ["Sebastian Carpenter", "Daniela Delgado", "Alice Fang", 
				"Michael Powell", "Sabrina Zhai"];
	} else if(assignment == "Trash") {	
		return ["Sebastian Carpenter", "Eunice Choe", "Julie Choi", "Daniela Delgado", "Alice Fang", 
				"Mimi Jiao", "Michael Powell", "Sabrina Zhai", "Emily Zhou"];
	} else {
		return ["Sebastian Carpenter", "Daniela Delgado", "Michael Powell", 
				"Sabrina Zhai", "Vicky Zhou"];	


Bioinvasive Dingus

The U.S. judicial branch, bioinvasion, war, and sociology collide with the vocabularies of Luis Carroll and Steve Brule in their first and last ever mash-up.
Here is a .zip containing 25 iterations of 10-page chapters:

The text portion of this project was generated using a combination of Markov chains. First, the text body was generated from a corpus of a series of academic papers on serious subjects ranging from Supreme Court decisions to changing migration habits. These papers were selected from the MICUSP database of student papers. The Markov chain had an n-gram length of 4, and was word based.

Next, random nouns were selected from the text to be replaced with other generated words. The replacement words were generated letter by letter with an n-gram length of 2. They were generated from Luis Carroll's Jabberwocky and transcripts of Check It Out! With Doctor Steve Brule. These words in isolation can be read and heard here by clicking to generate a new word:

The resultant text is a mishmash of technical jargon, actual nonsensical words, and serious problems with the world that are obscured by a dense dialect. Finally, images were generated by rotating and superimposing images from Yale's face database, and overlaying selected words from the "glossary" of generated words. These images are strung through the text, breaking it up, making it even more difficult to read line to line. Visually and textually, the generated nonsensical words make it almost impossible to parse the discussion of national and global crises.

Here's the code for the text:

var dingusGen, input, markBody, final
var bigJSON = { versions: [{pages: [] },{pages: [] },{pages: [] },{pages: [] },{pages: [] },{pages: [] },{pages: [] },{pages: [] },{pages: [] },{pages: [] },{pages: [] },{pages: [] },{pages: [] },{pages: [] },{pages: [] },{pages: [] },{pages: [] },{pages: [] },{pages: [] },{pages: [] },{pages: [] },{pages: [] },{pages: [] },{pages: [] },{pages: [] }] };
function preload()
  dingus = loadStrings('dingus.txt');
  input = loadStrings('input1.txt'); 
function setup() { 
	dingusGen = new Markov(2, dingus.join(" ").split(""), " ", 100);
  markBody = new RiMarkov(4);
  markBody.loadText(input.join(' '));
  for (var version = 0; version &lt; 25; version++)
    for (var page = 0; page &lt; 10; page++)
      bigJSON.versions[version].pages[page] = genText(version, page);
  saveJSON(bigJSON, "final1.json");
function genText(version, page) {
  var final = RiTa.tokenize(markBody.generateSentences(20).join(' '));
  var glossary = [];
  for (var i = 0; i &lt; final.length; i++)
    if (RiTa.isNoun(final[i]) &amp;&amp; random(1) &lt; 0.5)
      var result = dingusGen.generate().join("")
      while (result.length &lt; final[i].length || result.length &gt; final[i].length+5)
        result = dingusGen.generate().join("")
      if (final[i].charAt(0) == final[i].charAt(0).toUpperCase())
        result = result.charAt(0).toUpperCase() + result.slice(1);
      if (random(1) &lt; 0.2)
       	result = RiTa.pluralize(result); 
      if (random(1) &lt; 0.2)
        glossary.push(result.charAt(0).toUpperCase() + result.slice(1)); 
      final[i] = result;
  var thisJSON = {}; 
  thisJSON.glossary = glossary;
  thisJSON.text = RiTa.untokenize(final);
  //text(version + "_" + page, 25, 25);
  text(RiTa.untokenize(final), 50, 50, 400, 400);
function Markov(n, input, end, maxLen) {
	this.n = n;
  this.ngrams = [];
  this.tokens = [];
  this.end = end;
  this.maxLen = maxLen;
  this.N = 0;
  this.indexFromGram = function(gram) {
    var index = 0;
    for (var i = 0; i &lt; n; i++)
      index += this.tokens.indexOf(gram[i]) * pow(this.tokens.length, n - i - 1);
    return index;
  this.indexToGram = function(index) {
    var gram = [];
    for (var i = 0; i &lt; n; i++)
      gram.unshift(this.tokens[(Math.floor(index/(pow(this.tokens.length, i)))) % (this.tokens.length)]);
    return gram;
  for (var i = 0; i &lt; input.length; i++)
    if (!(this.tokens.includes(input[i])))
  for (i = 0; i &lt; pow(this.tokens.length, n); i++)
  var gram = [];
  for (i = 0; i &lt; input.length - n + 1; i++)
    gram = []
    for (var j = 0; j &lt; n; j++)
      gram.push(input[i + j]);
    this.ngrams[this.indexFromGram(gram)] ++;
  for (i = 0; i &lt; this.ngrams.length; i++)
    this.N += this.ngrams[i];
  this.seed = function() {
    var randInd = Math.floor(random(this.N));
    var n = 0;
    for (var i = 0; i &lt; this.ngrams.length; i++) { n += this.ngrams[i]; if (n &gt; randInd) 
        return this.indexToGram(i);
    print("seed is fucked");
    return [];
  this.nextToken = function(gram) {
    var index0 = this.indexFromGram(gram);
    var N = 0;
    for (var i = 0; i &lt; this.tokens.length; i++)
      N += this.ngrams[index0 + i];
    var n = 0;
    var randInd = Math.floor(random(N));
    for (i = 0; i &lt; this.tokens.length; i++) { n += this.ngrams[index0 + i]; if (n &gt; randInd) return this.tokens[i];
    print("nextToken is fucked");
    return 0;
  this.generate = function() {
    var out = this.seed();
    //print("out", out);
    var i = 0;
    while (out.includes(this.end) &amp;&amp; i &lt; this.maxLen)
    	out = this.seed();
    i = 0;
    while (out[out.length - 1] != this.end &amp;&amp; i &lt; this.maxLen)
      out.push(this.nextToken(out.slice(out.length - n + 1, out.length)));
    return out.splice(0,out.length-1);

And the code for the images:

var book;
var images = []
var img;
var offset;
var c;
var wordsSoFar;
function preload() {
  for (var i = 1; i &lt;= 230; i+=10) { var filename; if (i &gt;= 100){ filename = "" + i +".png";}
    else if (i &gt;= 10){filename = "0" + i + ".png";}
    else {filename = "00" + i + ".png";}
  book = loadJSON('bigBoyFinal1.json');
  wordsSoFar = loadStrings('wordsSoFar.txt');
function setup() {
  c = createCanvas(300, 300);
  var count = 0;
  for (var v = 0; v &lt; book.versions.length; v++)
    for (var p = 0; p &lt; book.versions[v].pages.length; p++)
      for (var w = 0; w &lt; book.versions[v].pages[p].glossary.length; w++)
function genImage(word) {
  var offset = 1;
  for (var i = 0; i &lt; word.length/2; i++)
    image(images[Math.floor(random(images.length))], 0, 0, width, height*offset);
    if (i % 2 == 0) blendMode(ADD);
    offset += 0.1;
  saveCanvas(c, Math.floor(random(50,100)), "jpg");

And the code for the PDF generation in basil.js:

#includepath "~/Documents/;%USERPROFILE%Documents";
#include "basiljs/bundle/basil.js";
// to run this example 
function draw() 
	var dpi = 72;
	var json = b.JSON.decode(b.loadString("bigBoyFinal1.json"));
	for (var v = 0; v &lt; json.versions.length; v++)
		var wSum = 0;
		for (var p = 0; p &lt; json.versions[v].pages.length; p++)
			b.textFont('Gadugi', 'Regular');
			var currentText = b.text(json.versions[v].pages[p].text, 1*dpi, 1*dpi, 4*dpi, 7*dpi);
			var i = 0;
			for (var word in b.words(currentText))
				if (word in json.versions[v].pages[p].glossary)
					b.typo(b.words(currentText)[i], 'appliedFont', 'Gadugi\tBold');
			for (var w = 0; w &lt; json.versions[v].pages[p].glossary.length; w++)
				var x = 3*dpi + 2*dpi*b.sin(wSum/3);
				var y =, 0, json.versions[v].pages[p].glossary.length, 1.2*dpi, 8.2*dpi);
				var wrapper = b.ellipse(x, y, 0.9*dpi+0.2*dpi*b.sin(wSum/4), 0.9*dpi+0.2*dpi*b.sin(wSum/4));
				wrapper.textWrapPreferences.textWrapMode = TextWrapModes.CONTOUR;
				var circle = b.ellipse(x, y, 0.75*dpi+0.2*dpi*b.sin(wSum/4), 0.75*dpi+0.2*dpi*b.sin(wSum/4));
				try {
					var imgCircle = b.image("FaceImgBgs/" + Math.floor(b.random(73)) + ".jpg", circle);
				catch(error) {
					b.ellipse(x, y, 0.75*dpi+0.2*dpi*b.sin(wSum/4), 0.75*dpi+0.2*dpi*b.sin(wSum/4));
				var myText = b.text(json.versions[v].pages[p].glossary[w],x-1*dpi,y-0.04*dpi,2*dpi,0.5*dpi);
				myText.textFramePreferences.ignoreWrap = true;
			if (v == 0) 
				if (p &lt; json.versions[v].pages.length - 1){b.addPage();}
				if (p &lt; json.versions[v].pages.length - 1){}
		//if (v &lt; 10){ b.savePDF("0" + v + "_dinkolas.pdf", false);}
		//else { b.savePDF(v + "_dinkolas.pdf", false);}



The thing that stuck with me from Allison Parrish's talk was that her goal as a poet is "not to imitate existing poetry but to find new ways for poetry to exist." This reminded me a bit of the talk given by Robbie Barrat, who also uses computers to generate art. The goal of AI-driven art isn't to replace or mimic artists, but to create new things that could never be conceived by a human. I really liked the example with the cute robot explorer going into the unknown, because robots are truly helping us to explore new areas and should be thought as our helpers rather than competitors.