Week 10 — Generative Text and Typography
This week is all about libraries and generative text.
Libraries
- Libraries are pieces of javascript that extend the functionality of p5js.
- Libraries are added to index.html using a script tag with the src attribute ⟶ path to the javascript file.
<script src="../../libraries/p5.min.js"></script>
<script src="../../libraries/rita.js"></script>
<script src="sketch.js"></script>
- libraries/ ⟶ a directory (because of the slash).
- Load the libraries before sketch.js.
RiTa.js
- RiTa ⟶ natural language processing objects for generative literature. Created by Daniel Howe.
- Used for analyzing and generating text. eg., poetry, finding patterns in speeches, drawing shapes based on words in a text.
- RiTa.js
Note:
- RiTa is very specific about strings vs arrays as parameters.
- RiTa.tokenize() ⟶ takes a string
- RiTa.getPosTags() ⟶ takes an array
- Keep track of what goes into a function and what is returned from it.
- The course uses RiTa v1 (libraries/rita.js is v1.3.94). The functions are renamed in v2 and later.
| v1 (course) | v2+ |
|---|---|
| RiTa.getPhonemes() | RiTa.phones() |
| RiTa.getStresses() | RiTa.stresses() |
| RiTa.getPosTags() | RiTa.pos() |
| lexicon.randomWord() | RiTa.randomWord({ pos, numSyllables }) |
Open source texts ⟶ Project Gutenberg
RiTa.tokenize
- Takes a string ⟶ returns an array of tokens.
- Tokens ⟶ words and punctuation marks.
function setup() {
createCanvas(400, 400);
var words = RiTa.tokenize("I scream, you scream, we all scream for ice cream.");
for (var i = 0; i < words.length; i++) {
text(words[i], 10, i * 20 + 20);
}
}
- The commas and the period are also tokens.
RiTa.getPhonemes
- Takes a string ⟶ returns the phonetic spelling of the text. eg., for a karaoke app.
var phonemes = RiTa.getPhonemes("got me looking so crazy right now");
// g-aa-t m-iy l-uh-k-ih-ng s-ow k-r-ey-z-iy r-ay-t n-aw
text(phonemes, 50, 50, 300, 100);
- text(string, x, y, width, height) ⟶ wraps text inside the box.
- text() also accepts an array, it joins the elements with commas.
textWidth
- p5js function. Returns the width of a string in pixels.
- eg., draw rectangles to represent how long the words are.
var word = "huge";
var wordWidth = textWidth(word);
text(word, 50, 50);
rect(50, 70, wordWidth, wordWidth);
Code in folder /10.6-TextWidth

RiTa.getStresses
- Stresses ⟶ emphasis on a syllable.
- Takes a string ⟶ returns 1 (stressed) and 0 (not stressed) for each syllable.
- Syllables are separated by / ⟶ very ⟶ 1/0
var words = RiTa.tokenize("Thank you very much Mr. Robot");
function draw() {
background(255);
translate(20, 20);
var pos = 0;
for (var i = 0; i < words.length; i++) {
text(words[i], pos, 20);
text(RiTa.getStresses(words[i]), pos, 45);
// move along by the width of the previous word
pos += textWidth(words[i]) + 10;
}
}

RiTa.getPosTags
- Takes an array of words ⟶ returns the part of speech (PENN) tag of each word.
Some PENN tags
- nn ⟶ noun, singular
- nns ⟶ noun, plural
- jj ⟶ adjective
- vb ⟶ verb
- vbg ⟶ verb, gerund (eg., walking)
- rb ⟶ adverb
- dt ⟶ determiner
- in ⟶ preposition
Full list ⟶ PENN tags
// In a Station of the Metro - Ezra Pound
var words = RiTa.tokenize("The apparition of these faces in the crowd");
var pos = RiTa.getPosTags(words);
// [dt, nn, in, dt, nns, in, dt, nn]
- pos has the same length as words ⟶ one tag per word. So pos[i] is the tag of words[i].
- Words wrap to the next line when wordPosX goes beyond the width. Code in folder /10.8-PosTags

RiTa.getWordCount
- Counts the number of words in a string. Useful for large texts like a book or a chapter.
RiTa.getWordCount("Thank you very much Mr. Robot");
- Returns 7 here, the period in “Mr.” is counted as a token.
Generating Text
- Analyzing text ⟶ RiTa object. Generating text ⟶ RiLexicon object.
- new ⟶ creates a new instance of RiLexicon (same as in objects lesson).
var lexicon;
function setup() {
createCanvas(400, 400);
lexicon = new RiLexicon();
}
function mousePressed() {
background(255);
text(lexicon.randomWord(), width / 2, height / 2);
}
lexicon.randomWord
// any random word
lexicon.randomWord();
// random word of a part of speech ⟶ PENN tag
lexicon.randomWord("nn");
// random word of a part of speech and number of syllables
lexicon.randomWord("nn", 2);
Example: Generative Haiku (code in folder /10.11-Haiku)
- Haiku ⟶ 5, 7, 5 syllables in the three lines.
- New haiku on every click.
function generateHaiku() {
// 1 + 2 + 2
var output = lexicon.randomWord("jj", 1);
output += " " + lexicon.randomWord("vbg", 2);
output += " " + lexicon.randomWord("nn", 2);
output += "\n";
// 2 + 2 + 3
output += lexicon.randomWord("vbg", 2);
output += " " + lexicon.randomWord("jj", 2);
output += " " + lexicon.randomWord("nn", 3);
output += "\n";
// 1 + 2 + 2
output += lexicon.randomWord("nns", 1);
output += " " + lexicon.randomWord("vbg", 2);
output += " " + lexicon.randomWord("nn", 2);
text(output, width / 2, height / 2);
}
- ” ” ⟶ space between the words.
- “\n” ⟶ new line character.
- textAlign(CENTER, CENTER) ⟶ aligns text horizontally and vertically.
- Call background() in mousePressed, otherwise the haikus are drawn over each other.
preload and loadStrings
- preload() ⟶ built into p5js, runs before setup. Waits until large files are fully loaded before running the sketch.
- loadStrings(path) ⟶ loads a text file as an array of strings. Each string is a line in the file.
var lines;
function preload() {
lines = loadStrings("assets/station-of-the-metro.txt");
}
function setup() {
createCanvas(400, 400);
// join ⟶ array to one string, with a space between the elements
text(lines.join(" "), 20, 20, width - 40, height - 40);
}
Note:
- If the sketch is stuck at “Loading…” with no errors, the problem is most likely in preload.
- Check the path to the file. Exact file name, including spaces and the extension.
Example sketches
The p5 sketches for this week, from the course repo.
10.4 — Tokenizerepo ↗
function setup() {
createCanvas(400, 400);
background(220);
// string ⟶ array of tokens (words and punctuation)
var words = RiTa.tokenize("I scream, you scream, we all scream for ice cream.");
for (var i = 0; i < words.length; i++) {
text(words[i], 10, i * 20 + 20);
}
}10.5 — Phonemesrepo ↗
function setup() {
createCanvas(400, 400);
background(255);
noFill();
rect(0, 0, width - 1, height - 1);
var phonemes = RiTa.getPhonemes("got me looking so crazy right now");
text(phonemes, 50, 50, 300, 100);
print(phonemes);
}10.6 — Text Widthrepo ↗
var words = ["huge", "huuuuge", "huuuuuuuuuuuge"];
function setup() {
createCanvas(400, 400);
background(220);
var y = 50;
for (var i = 0; i < words.length; i++) {
var wordWidth = textWidth(words[i]);
text(words[i], 50, y);
// rectangle as wide as the word
rect(50, y + 20, wordWidth, wordWidth);
y += wordWidth + 50;
}
}10.7 — Stressesrepo ↗
var lyricLine = "Thank you very much Mr. Robot";
var words = [];
function setup() {
createCanvas(400, 400);
words = RiTa.tokenize(lyricLine);
}
function draw() {
background(255);
translate(20, 20);
var pos = 0;
for (var i = 0; i < words.length; i++) {
// 1 ⟶ stressed, 0 ⟶ not stressed, / ⟶ separates syllables
var stresses = RiTa.getStresses(words[i]);
text(words[i], pos, 20);
text(stresses, pos, 45);
pos += textWidth(words[i]) + 10;
}
}10.8 — Pos Tagsrepo ↗
// In a Station of the Metro - Ezra Pound
var couplet = "The apparition of these faces in the crowd; Petals on a wet, black bough.";
var words = [];
var pos = [];
function setup() {
createCanvas(400, 400);
words = RiTa.tokenize(couplet);
// getPosTags takes an array, not a string
pos = RiTa.getPosTags(words);
}
function draw() {
background(255);
var wordPosX = 10;
var wordPosY = 30;
for (var i = 0; i < words.length; i++) {
text(words[i], wordPosX, wordPosY);
// same length as words ⟶ one tag per word
text(pos[i], wordPosX, wordPosY + 20);
wordPosX += textWidth(words[i]) + 16;
if (wordPosX > width - 50) {
wordPosX = 10;
wordPosY += 50;
}
}
}10.9 — Word Countrepo ↗
var lyricLine = "Thank you very much Mr. Robot";
function setup() {
createCanvas(400, 400);
background(255);
text(lyricLine, height / 2, height / 2);
text(RiTa.getWordCount(lyricLine), height / 2, height / 2 + 45);
}10.10 — Random Wordrepo ↗
var lexicon;
function setup() {
createCanvas(400, 400);
textAlign(CENTER, CENTER);
lexicon = new RiLexicon();
background(255);
text("click", width / 2, height / 2);
}
function mousePressed() {
background(255);
var word = lexicon.randomWord();
text(word, width / 2, height / 2);
}10.11 — Haikurepo ↗
var lexicon;
function setup() {
createCanvas(400, 400);
textAlign(CENTER, CENTER);
lexicon = new RiLexicon();
background(255);
generateHaiku();
}
function mousePressed() {
background(255);
generateHaiku();
}
// 5 - 7 - 5 syllables
function generateHaiku() {
// 1 + 2 + 2
var output = lexicon.randomWord("jj", 1);
output += " " + lexicon.randomWord("vbg", 2);
output += " " + lexicon.randomWord("nn", 2);
output += "\n";
// 2 + 2 + 3
output += lexicon.randomWord("vbg", 2);
output += " " + lexicon.randomWord("jj", 2);
output += " " + lexicon.randomWord("nn", 3);
output += "\n";
// 1 + 2 + 2
output += lexicon.randomWord("nns", 1);
output += " " + lexicon.randomWord("vbg", 2);
output += " " + lexicon.randomWord("nn", 2);
text(output, width / 2, height / 2);
}10.12 — Load Stringsrepo ↗
var lines;
// runs before setup, waits for the file to load
function preload() {
lines = loadStrings("assets/station-of-the-metro.txt");
}
function setup() {
createCanvas(400, 400);
background(255);
// array of lines ⟶ one string, lines separated by a space
text(lines.join(" "), 20, 20, width - 40, height - 40);
}