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

The words huge, huuuuge and huuuuuuuuuuuge, each above a square as wide as the word.
huge, huuuuge, huuuuuuuuuuuge

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;
    }
}
Thank you very much Mr. Robot, with each word's stress pattern below it.
1 ⟶ stressed, 0 ⟶ not stressed

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
The Ezra Pound couplet, each word with its PENN part-of-speech tag below it.
In a Station of the Metro, tagged

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);
}
new haiku on every click
  • ” ” ⟶ 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);
}