Markov Chain in processing

Hello everyone, and merry christmas if it applies to you,

I am relatively new to processing and to coding, so I apologize in advance if my questions are redundant.

I am trying to understand, learn about and program a markov chain based text generator. The first step is to reproduce the one in Daniel Schiffman’s excellent video on the subject (Coding Challenge #42.1: Markov Chains - Part 1.

Unfortunately, the tutorial is on p5.js, and given my limited knowledge, it’s taken me a couple of days only to adapt the code for my processing sketch. But several elements seem to behave quite differently between the two and I’m unfamiliar with how to manipulate classes and objects (despite reading the chapter on the subject… I need to practice more).

I’ve reached a point where I can’t find any more documentation online and was hoping a good soul may be able to look at my attempt.

In a nutshell: the program looks at every trigram (groups of three characters) in a text, and counts the occurence of each. It should then suggest the possibilities for which character might come next, based on these statistics.

In Schiffman’s tutorial, the console.log(array); command seem to automatically sort the array—arraylist in processing— and the suggested possibilities (when .push_ing —.add_ in processing—new characters in the array).

However, my excuse of an attempt below only manages to add the actual next character in the source, not offer options based on the statistics uncovered.

It feels like I am able to translate the program up to 13’00, but when he suddenly transforms his array into a new array (from 13’30 on), I am lost and have no idea how to replicate this in processing.

Because it’s quite short, here is the entire code so far. The issue comes from lines 17-18 and 21, which I’ve currently commented out, because they mess with my gramCounter variable.

Thank you in advance for any hint or help.

Julien

int order = 3 ;
String txt;
ArrayList<String> ngrams;  
int gramCounter;

void setup() {
  // initaialize the array list that will store the 'grams'.
  ngrams = new ArrayList<String>();
  // the source text (wikipedia)
  txt = "The unicorn is a legendary creature that has been described since antiquity as a beast with a single large, pointed, spiraling horn projecting from its forehead. The unicorn was depicted in ancient seals of the Indus Valley Civilization and was mentioned by the ancient Greeks in accounts of natural history by various writers, including Ctesias, Strabo, Pliny the Younger, and Aelian. The Bible also describes an animal, the re'em, which some versions translate as unicorn.";

  // loop over each character of the text 
  for (int i = 0; i <= txt.length() - order - 1; i++) {
    // extract/create grams of length 'order' using .substring method on the source.
    String gram = txt.substring(i, i + order); 
    if (!ngrams.contains(gram)) {
      // gram += txt.charAt(i + order); // adds the next character to the current 'gram' (if hapax).
      // ngrams.add(txt.substring(i + order)); // add next character (has to be a string, because arraylist of type string)
      gramCounter = 1;
    } else {
      // gram += txt.charAt(i + order); // adds the next character to the current (if > 1 occurence) 'gram'
      gramCounter += 1;
    }
    ngrams.add(gram);
    print(gram, gramCounter, "\n");
  }
}

1 Like

update: I have now found the RiTa library, and the “Kafgenstein” example it contains seems to answer my questions… So I guess my previous post is irrelevant now.

2 Likes

Hi @julien – glad you found RiTa and that it solved your Markov chain problem.

You can also use off-the-shelf Java code for a Markov chain implementation.

https://www.google.com/search?q=java+markov+chain

For example, there is one on Rosetta Code – although I’m not sure if it depends on Java 8 features:

https://rosettacode.org/wiki/Markov_chain_text_generator#Java

You might also be interested in past sketches:

3 Likes

Hi,
Thank you for your answer.

The Rosettacode article was my first read indeed, but some aspects of the code were too advanced for my level, so I started looking for other references. I’d be really curious to listen to markov chains in action generating music, but unfortunately the code isn’t available any more for Leo Wolstenholme’s project and the browser cannot play it anymore either for it seems deprecated. And thank you for the link to ignivome’s post, I had also found it and it was helpful.

After finding the rita library, the technical aspect of the markov chain was suddenly all sorted out and I had to find a new way to spend time coding, so I made the generated text to show in a pseudo handwritten shape typewriter that allows to export svg. I then traced the svg with a pen plotter to make it look like it was actually done by hand. I just posted it yesterday, if you’re interested: https://github.com/djebel-amila/markov_handwriter

cheers,
Julien

1 Like

OpenProcessing.org/sketch/285202/files/Final_Processing.pde

Final_Processing.pde:

int w, h, xpos, ypos, xa, xd;        //variables for width, height, mouse position and the left and right of each section

int num = 500;                       //size of arrays (number of ellipses) for visuals
int[] dx = new int[num];
int[] dy = new int[num];             //arrays for position and size of ellipses
int[] diam = new int[num];

import arb.soundcipher.*;            //imports soundcipher library

SoundCipher notes = new SoundCipher(this);
                                                //these create soundcipher instruments for notes and chords
SoundCipher chord = new SoundCipher(this);

void setup()
{
  size(1200, 760);                              //setting up the canvas
  background(40);
  smooth();
  frameRate(10);
  
  w = width; h = height;                          //assigning width and height to w and h
  
  drawLines();                                    //draws the lines and words from the start
  words();
  
  notes.instrument(39);                           //bass guitar instrument for notes
  chord.instrument(25);                           //steel string acoustic guitar for broken chords
 }

void draw()
{
  drawLines();       //draws the dividing lines
  
  xpos = mouseX; ypos = mouseY;                    //assigns mouse position to xpos and ypos
  
  float vel = map(ypos, 0, h, 30, 90);             //maps mouse y position to velocity of the notes
  
  background(40);
  drawLines();                                    //resets background and redraws lines and text every frame to cover up previous ellipses
  words();

  if (mousePressed == true) {
    if (xpos < w/6) {xa = 0; xd = w/6; ellipses(); notes.playNote(38, vel, 1.2);}
    else if (xpos < w/3) {xa = w/6; xd = w/3; ellipses(); notes.playNote(40, vel, 1.2);}                  //this section determines where the mouse is. this defines
    else if (xpos < w/2) {xa = w/3; xd = w/2; ellipses(); notes.playNote(41, vel, 1.2);}                  //values for xa and xd and calls the ellipse function to    
    else if (xpos < w - w/3) {xa = w/2; xd = w - w/3; ellipses(); notes.playNote(43, vel, 1.2);}          //draw the ellipses in the correct box
    else if (xpos < w - w/6) {xa = w - w/3; xd = w - w/6; ellipses(); notes.playNote(45, vel, 1.2);}
    else {xa = w - w/6; xd = w; ellipses(); notes.playNote(47, vel, 1.2);}
  }
  saveFrame();
}

void keyPressed()
{
  if (key == ' ') markovChords();                      //if the space bar is pressed, call the markov function which plays chords according to a markov chain
}

void words()                                          //this function prints all of the text to the canvas
{
  fill(200, 0, 100);
  textAlign(CENTER);
  textSize(20);
  text("Click to Play Notes!", w/2, h-35);
  text("Hold Space Bar to Play Chords!", w/2, h - 10);
  textSize(40);
  text("D", w/12, h - 90);
  text("E", 3*w/12, h - 90);
  text("F", 5*w/12, h - 90);
  text("G", 7*w/12, h - 90);
  text("A", 9*w/12, h - 90);
  text("B", 11*w/12, h - 90);
}

void markovChords()                                  //this function contains the markov chain whiach plays the chords
{
  if (frameCount % 5 == 0)                           //plays a new chord only every 5 frames
  {
    int last = 1;                                    //variable for last state of the state engine
    float r = random(1);                             //creates the random probabilities
    
    float[] chord1 = {48, 52, 55};    //Cmaj
    float[] chord2 = {53, 57, 60};    //Fmaj          //arrays for the three chords
    float[] chord3 = {55, 59, 60};    //Gmaj
    
    float[] dyn = {65, 70, 75};                       //these arrays define the velocity and duration of each respective note in the broken chord
    float[] dur = {0.2, 0.2, 0.3};
    
    switch(last)                                      //switches the last variable around
    {
      case 1:
      if (r < 0.2)
        {
          chord.playPhrase(chord1, dyn, dur);
          last = 1;
        }
        else if (r < 0.4)
        {
          chord.playPhrase(chord2, dyn, dur);
          last = 2;
        }
        else if (r < 0.8)
        {
          
        }
        else
        {
          chord.playPhrase(chord3, dyn, dur);
          last = 3;
        }
      break;
      case 2:
        if (r < 0.3)
        {
          chord.playPhrase(chord1, dyn, dur);
          last = 1;
        }
        else if (r < 0.5)
        {
          chord.playPhrase(chord2, dyn, dur);
          last = 2;
        }
        else if (r < 0.8) 
        {
          
        }
        else
        {
          chord.playPhrase(chord3, dyn, dur);
          last = 3;
        };
      break;
      case 3:
        if (r < 0.1)
        {
          chord.playPhrase(chord1, dyn, dur);
          last = 1;
        }
        else if (r < 0.3)
        {
          chord.playPhrase(chord2, dyn, dur);
          last = 2;
        }
        else if (r < 0.8) 
        {
          
        }
        else
        {
          chord.playPhrase(chord3, dyn, dur);
          last = 3;
        }
      break;
    }
  }
}
    
void ellipses()
{
  for (int i = 0; i < num; i++) {
    dy[i] = trianglePass(0, ypos);
    diam[i] = lowPass(50);
  }
  
  for (int i = 0; i < num; i++) {
    dx[i] = trianglePass(xa, xd);
  }

  
  for (int j = 0; j < num; j++) {
    stroke(random(255), random(255), random(255));
    fill(random(155), random(155), random(255), 30);
    ellipse(dx[j], dy[j], diam[j], diam[j]);
    
  }
}

void drawLines()
{
   for (int i = 0; i < w; i += w/6)
  {
    stroke(255);
    line(i, 0, i, h-60);
  }
  line(0, h-60, w, h-60);
}


int trianglePass(int a, int d)
{
  int aa = int(random(a, d) + random(a, d) + random(a, d) + random(a, d) + random(a, d) + random(a, d) + random(a, d) + random(a, d))/8;
  return aa;
}

int lowPass(int b)
{
  int bb = min(int(random(5, b)), int(random(5, b)));
  return bb;
}
2 Likes

Awesome, thanks for posting!