Week 4 – Persian generative text

Inspiration

For this week, I was really struggling on what to do I was prompting with Claude AI and decided to go with the generative text option. I knew I wanted to do something with a different language and then decided to go with my mother-tongue, Farsi. Persian poetry is famous and so I wanted to choose a quote that had a theme, replicate that theme in p5.js with shapes and colours.

My name in Farsi ‘خاطره’ means memory, remembrance, longing – something along those lines. It was difficult to find old poetry with that word as it is more modern but I liked the theme of memories and love so I went with the following line from Rumi – Masnavi (مثنوی معنوی)

Rumi is a 13th century poet and was known for his work regarding mystical themes in Islam (Sufism). Majority of his work is in Persian and very famous.

Idea

This was the quote I went with.

let poem = {
persian: “بشنو از نی چون حکایت می‌کند، از جدایی‌ها شکایت می‌کند”,
transliteration: “Beshno az ney chon hekayat mikonad, Az jodayiha shekayat mikonad”,
english: “Listen to the reed flute, how it tells a tale, complaining of separations”,
};
I wanted the words to be floating around, referring to ‘separation’, and. I want the colours / shapes to be pink, hearts just to refer to love.
I also wanted the user to be able to see the quote altogether so that they can see the final image.

Heart

There is no heart shape in p5.js so I was looking at different examples with math function and came across this link. The artist uses sin and cos functions to connect the hearts.  https://editor.p5js.org/marlontenorio/sketches/M_BGUpfKL

I edited it to suit the image in my mind. I made it a function with the size parameter because I wanted the heart to have some sort of pounding effect.

drawHeart(size) {
        beginShape();
        let s = size * 0.5;
        for (let t = 0; t < TWO_PI; t += 0.1) {
            let x = s * (16 * pow(sin(t), 3)) * 0.03;
            let y = -s * (13 * cos(t) - 5 * cos(2*t) - 2 * cos(3*t) - cos(4*t)) * 0.03;
            vertex(x, y);
        }
        endShape(CLOSE);
    }

Interactivity

Click Mouse

I wanted the user to be able to add words or hearts when the mouse is pressed.

function mousePressed() {
    hearts.push(new Heart());
    texts.push(new Text(mouseX, mouseY));
    
    // Limit elements
    if (hearts.length > 15) hearts.shift();
    if (texts.length > 15) texts.shift();
}

I added a limit so the program won’t be too complex. The words added would be from either the Persian, transliteration, or English – and at the point of the mouse. The hearts would simply be added randomly on the canvas.

SpaceBar – pressed

I wanted a way for users to add more words to screen, but a bunch of words. So I added the keyPressed function for the spacebar so that they user can see more words at once.

function keyPressed() {
    if (key === ' ') {
        for (let i = 0; i < 3; i++) texts.push(new Text());
    }

 

A / a – pressed

I wanted a way for the user to see the 3 versions of the poem at once, so I just used the A button as that trigger.

I have a Text class that will choose random words from the 3 lines verses and then have the floating simulation similar to the hearts. I also wanted these words to have some lifespan and disappear into the background slowly to reference the theme of the separation and memory.

class Text {
    constructor(x = random(100, width-100), y = random(100, height-100)) {
        this.x = x;
        this.y = y;
        this.text = this.getRandomText();
        this.size = random(14, 20);
        this.life = 0;
        this.maxLife = random(400, 800);
        this.dx = random(-0.3, 0.3);
        this.dy = random(-0.2, 0.2);
    }

Here is the display function and how it uses alpha to dictate the opacity. Depending on the life value, the opacity of the text changes.

display() {
    let alpha = this.getAlpha();
    if (alpha <= 0) return;
    
    push();
    textAlign(CENTER, CENTER);
    textSize(this.size);
    fill(340, 50, 90, alpha);
    noStroke();
    text(this.text, this.x, this.y);
    pop();
}

// calculate the opacity value for fading 
getAlpha() {
    let ratio = this.life / this.maxLife; // value between 0 and 1
    // fade in at the first 20% of life
    if (ratio < 0.2) return map(ratio, 0, 0.2, 0, 0.8);
    // fade out at the last 20% of life
    if (ratio > 0.8) return map(ratio, 0.8, 1, 0.8, 0);
    // stay mostly visible in the middle
    return 0.8;
}

Complete poem texts

Whenever the ‘a’ button is pressed, I would add the whole lines of poetry to the texts variable so that it would float around exactly like the singular words. I also added the life parameter as the same so that they disappear at the same time.

function showCompletePoem() {
    texts = [];
    let centerX = width / 2;
    let startY = height / 2 - 60;
    
    // add complete poem texts
    let persian = new Text(centerX, startY);
    persian.text = poem.persian;
    persian.size = 24;
    persian.maxLife = 1200;
    texts.push(persian);
    
    let transliteration = new Text(centerX, startY + 50);
    transliteration.text = poem.transliteration;
    transliteration.size = 18;
    transliteration.maxLife = 1200;
    texts.push(transliteration);
    
    let english = new Text(centerX, startY + 100);
    english.text = poem.english;
    english.size = 20;
    english.maxLife = 1200;
    texts.push(english);
}

It is also important to remove ‘old’ texts from the array once their life is ‘complete’. This gets checked in the draw function.

// remove old texts
texts = texts.filter(text => text.life < text.maxLife);

Next time

Next time, I would definitely want to add some flute noises to really tie the piece together. Next time I should probably add some message to indicate which key / mouse action equates to each action.

Outcome

Leave a Reply