How to 'deform' a text generated with theGeomerative library?

Hello,
I try(and manage) to ‘deform’ a text generated thanks to the Geomerative library, but once the text transformed it immediately gets back to its original form?! I’d like to keep this transformation but don’t know how ? Could someone help me with this issue please ?
The problem is certainly in the soundMaster class / void update() / line n°373 ( initAttractor(currentSound):wink:
Sorry for the long code…
Thanks a lot in advance.
Best, L

import generativedesign.*;
import geomerative.*;
import ddf.minim.analysis.*;
import ddf.minim.*;
// List of a list of points. 1rst the numbre of phrases: 4,  the 2nd indicate the number of points
RPoint[][] myPoints = new RPoint[5][0];
RFont font;
PFont f;
Attractor attractor;

// Variables for lines
Attractor_Lines attractor_Lines;
int xCount=401;
int yCount=401;
float gridSizeX=1800;
float gridSizeY=1000;
Node [] myNodes = new Node[xCount*yCount];
float xPos, yPos;

String [][] phraseSets = new String [4][0];
String [] FR1 = {
  "On me dit de te haïr et je m'y efforce", 
  "Je t'imagine cruel, violent, implacable", 
  "Mais à te voir je n'ai bientôt plus de force", 
  "Et de te blesser je suis bien incapable", 
};
String [] FR2 = {
  "Tous mes camarades combattent avec rage", 
  "Et pleurent la nuit au souvenir des supplices", 
  "Infligés à leurs frères qui sont du même âge", 
  "et rêvent comme eux de toucher une peau lisse"
};
String [] FR3 =
  {"Et de pouvoir enfin caresser des obus", 
  "Autres que ceux produits par le pouvoir obtus", 
  "Je rêve de quitter ces boyaux infernaux"
};
String [] FR4 = {
  "De laisser ces furieux des deux bords du Rhin", 
  "Et de pouvoir embrasser ta chute de rein", 
  "Et porter notre amour sur les fonts baptismaux"
};

//TEXT
final color textColor = color(245);
int fontSize;
PVector [] nodesAtStartPositions;
PVector [] nodesAtEndPositions;
//SOUND
Minim minim;
AudioPlayer[] sounds;
FFT fft;
float bandHeight;
float soundDuration ;
float soundDuration1 ;
String []fileNamesFR= {"FR_01", "FR_02", "FR_03", "FR_04", "FR_05", "FR_06", "FR_07", "FR_08", "FR_09", "FR_10", "FR_11", "FR_12", "FR_13", "FR_14"};
SoundManager sm;

// TIME
int startTime;
int initTime;
int lineSpacing;
int index;
int state;
float duration;
int durPhrase1;
int durPhrase2;
int durPhrase3;
int durPhrase4;
int currentSound;
int pauseTime=2500;

//----------------SETUP---------------------------------------------------------------------------------------

void setup() {
  size(1920, 1080, JAVA2D);
  //add phrases to list
  phraseSets[0]=FR1;
  phraseSets[1]=FR2;
  phraseSets[2]=FR3;
  phraseSets[3]=FR4;

  smooth();
  RG.init(this);
  font = new RFont("FreeSans.ttf", 86, CENTER);
  stroke(textColor);
  strokeWeight(0.05);
  //INIT
  drawPhrases(phraseSets[0]);

  // LINES initiate attractor + attractors specs
  nodesAtStartPositions = new PVector [xCount*yCount];
  initGrid();
  attractor_Lines = new Attractor_Lines(0, 0);
  attractor_Lines.strength=-160;
  attractor_Lines.ramp = 0.85;

  //SOUND
  minim = new Minim(this);
  sounds = new AudioPlayer[fileNamesFR.length];
  for (int idx=0; idx<sounds.length; idx++) {
    sounds[idx] = minim.loadFile(fileNamesFR[idx]+".wav", 2048);
    fft = new FFT(sounds[idx].bufferSize(), sounds[idx].sampleRate());
  }
  soundDuration = 2000;
  sm=new SoundManager(this);
  //}
  // TIME
  startTime=millis();
  initTime=millis();
  index=0;
  lineSpacing =150;
}

//----------------DRAW---------------------------------------------------------------------------------------------

void draw() {
  background(255);
  state =0;

  //SOUNDS ANALYZIS
  for (int idx=0; idx < sounds.length; idx++) {
    fft.forward(sounds[idx].mix);
    for (int i =0; i< fft.specSize(); i++) {
      float bandDB = 10*log(fft.getBand(i)/fft.timeSize());
      bandDB = constrain(bandDB, -1000, 1000);
      bandHeight = map(bandDB*4, 0, -220, 0, height);
      stroke(0);
      //line(i, height, i, bandHeight-fft.getBand(i)*8);
    }
  }


  // LINES
  if (millis()-startTime > 0) {
    for (int i = 0; i<myNodes.length; i=i+10) {
      pushMatrix();
      translate(myNodes[i].x, myNodes[i].y);
      stroke(0, 100);
      strokeWeight(0.01);
      float noiseXRange = attractor_Lines.x/100.0;
      float noiseYRange = attractor_Lines.y/1000.0;
      float noiseX = map(myNodes[i].x, 0, xCount, 0, noiseXRange/5);
      float noiseY = map(myNodes[i].y, 0, yCount, 0, noiseYRange/5);
      float noiseValue = noise(noiseX, noiseY);
      float angle = noiseValue*TWO_PI;
      rotate(angle);
      line(0, 0, 10, 10);
      popMatrix();
    }
  }

  // TEXTS
  // draw on the center of the screen
  translate(width/2, height/2);
  // draw phrases vertically centered by moving the top up by half the line spaces
  translate(0, -1.0*lineSpacing*(phraseSets[index].length-1)/2.0);
  // loop through lines
  for (int i=0; i< myPoints.length; i++) {
    // draw a line
    for (int j=0; j< myPoints[i].length-1; j++) {
      pushMatrix(); 
      translate(myPoints[i][j].x, myPoints[i][j].y);
      noFill();
      stroke(0, 200);
      strokeWeight(0.25);
      float angle = TWO_PI*10;
      rotate(j/angle);
      bezier(-2*(noise(10)), 10, 25*(noise(10)), -5, 2*noise(5), -15, 10, -3);
      //bezier(-10*(noise(20))+mouseX/15, 30+mouseY/10, -10*(noise(10))+mouseX/15, 20+mouseY/15, -20*noise(20)+mouseX/15, -20+mouseY/5, 10+mouseX/15, -10+mouseY/15);
      popMatrix();
    }
    // move to the next line
    translate(0, lineSpacing);
  }
  //check Timer and redraw phrases if time has passed

  changePhraseTimerN();
  sm.update();

  //changePhraseTimer(duration*4, phraseSets);
}

//----------------INITIALIZE----------------------------------------------------------------------------------------------------------------------------------------
void drawPhrases(String [] phrases) {
  myPoints = new RPoint[phrases.length][0];
  for (int j=0; j<phrases.length; j++) {
    RGroup myGroup = font.toGroup(phrases[j]);
    myGroup = myGroup.toPolygonGroup();
    myPoints[j] = myGroup.getPoints();
  }
}

//----------------TIMER----------------------------------------------------------------------------------------------------------------------------------------

/*void changePhraseTimer( float duration, String [][] phraseSets) {
 duration = sounds[0].length()-150;
 if (millis()-startTime > duration*4) {
 index =(index+1) % phraseSets.length; 
 drawPhrases(phraseSets[index]);
 startTime = millis();
 }
 }*/


void changePhraseTimerN() {
  durPhrase1 = 11500;
  durPhrase2 = 9500;
  durPhrase3 = 6000;

  if (millis()-startTime >-1 && millis()-startTime < durPhrase1+pauseTime) {
    drawPhrases(phraseSets[0]);
  } else if (millis()-startTime > durPhrase1+pauseTime && millis()-startTime < durPhrase1+durPhrase2+pauseTime*2) {
    drawPhrases(phraseSets[1]);
  } else if (millis()-startTime > durPhrase1+durPhrase2+pauseTime*2 && millis()-startTime < durPhrase1+durPhrase2+durPhrase3+pauseTime*3) {
    drawPhrases(phraseSets[2]);
  } else if (millis()-startTime > durPhrase1+durPhrase2+durPhrase3+pauseTime*3 &&  millis()-startTime < durPhrase1+durPhrase2+durPhrase3*2+pauseTime*4) {
    drawPhrases(phraseSets[3]);
  } else {
    drawPhrases(phraseSets[0]);
    startTime = millis();
  }
}


//----------------TEXT ATTRACTOR INIT----------------------------------------------------------------------------------------------------------------------------------------
void initAttractor(int i) {
  if (i>=4 && i<8) {
    i-=4;
  } else if (i>=8 && i<11) {
    i-=8;
  } else if (i>=11 && i<14) { 
    i-=11;
  } else if (i>14) {
    i=0;
  }

  float x = 0;
  float y =-50; 
  // println(i);
  attractor = new Attractor(x, y, myPoints[i]);
}
//----------------LINES ATTRACTOR INIT----------------------------------------------------------------------------------------------------------------------------------------
void updateAttractorLines(float x, float y) {
  attractor_Lines.x=x;
  attractor_Lines.y=y;
}
//----------------LINES GRID INIT----------------------------------------------------------------------------------------------------------------------------------------
void initGrid() {
  int i =0;
  for (int x=0; x<xCount; x++) {
    for (int y=0; y<yCount; y++) {

      xPos = x*(gridSizeX /(xCount-1)) + (width-gridSizeX)/2;
      yPos = y*(gridSizeY /(yCount-1)) + (height-gridSizeY)/2;
      myNodes[i] = new Node(xPos, yPos);
      myNodes[i]. setBoundary(0, 0, width, height);
      myNodes[i].setDamping(0.9);
      i++;
    }
  }
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class Attractor {

  float force_radious = 100;
  float maxForce = 15;
  RPoint position;
  RPoint[] points;

  Attractor(float x, float y, RPoint[] p) {
    points = p;
    position = new RPoint(x, y);
  }

  void attract() {
    
    for (int i =0; i < points.length; i++) {

      float d= points[i].dist(position);
     // println ("d : "+d);
      if (d < force_radious) {   
        RPoint desired = new RPoint(points[i]);
        //points[i]= new RPoint(points[i]);
        //println( "avant x : "+ points[i].x +" y: "+points[i].y);
        desired.sub(position);
        desired.normalize();
        desired.scale(map(d, 0, force_radious, maxForce, 0));
        points[i].add(desired);
         //println( "après x : "+ points[i].x +" y: "+points[i].y);
      }
    }
  }
  void display () {
    stroke(0);
   strokeWeight(2);
  // ellipse (position.x, position.y-750, 30, 30);
  }
  void moveTo(float x, float y){
    position.x=x;
    position.y=y;
    
  }
}
class Attractor_Lines {
  float x=0, y=0;
  float radius =110;
  float strength= 0.55;
  float ramp=0.05;
  float theX;
  float theY;

  Attractor_Lines( float theX, float theY) {
    x= theX;
    y = theY;
  }

  void attract_Lines (Node theNode) {

    float dx = x-theNode.x;
    float dy = y-theNode.y;
    float d= mag(dx, dy);
    if ( d > 0 && d < radius) {

      float s = pow(d/radius, 1/ramp);
      float f = s*9*strength*50 * (1/(s+1)+((s-3)/4))/d;
      theNode.velocity.x += dx*f;
      theNode.velocity.y += dy*f;
    }
  }
}
import ddf.minim.analysis.*;
import ddf.minim.*;

class SoundManager {
  //SOUND
  Minim minim;
  AudioPlayer[] sounds;
  FFT fft;
  float bandHeight;

  String []fileNamesFR1= {"FR_01", "FR_02", "FR_03", "FR_04", "FR_05", "FR_06", "FR_07", "FR_08", "FR_09", "FR_10", "FR_11", "FR_12", "FR_13", "FR_14"};
  float []linesYPositions ={300., 450., 600., 750., 300., 450., 600., 750., 450., 600., 750., 450., 600., 750.};

  SoundManager(PApplet app) {

    minim = new Minim(app);
    currentSound =-1;
    sounds = new AudioPlayer[fileNamesFR1.length];
    for (int idx=0; idx<sounds.length; idx++) {
      sounds[idx] = minim.loadFile(fileNamesFR1[idx]+".wav", 2048);
      fft = new FFT(sounds[idx].bufferSize(), sounds[idx].sampleRate());
    }
  }

  void update() {

    // SOUND
    if (currentSound ==-1) { 
      startPlaying();
    } else if (!sounds[currentSound].isPlaying()) {  
      playNext();
    } else { 

      fft.forward(sounds[currentSound].mix);
      for (int i =0; i< fft.specSize(); i++) {
        float bandDB = 10*log(fft.getBand(i)/fft.timeSize());
        bandDB = constrain(bandDB, -1000, 1000);
        bandHeight = map(bandDB*4, 0, -220, 0, height);
      }

      initAttractor(currentSound);
      attractor.moveTo(map(sounds[currentSound].position(), 0, sounds[currentSound].length(), 0, width-100)-width/2, bandHeight/10-300);
      attractor.attract();

      updateAttractorLines( attractor_Lines.x = map(sounds[currentSound].position(), 0, sounds[currentSound].length(), 0, width-(100)/2), linesYPositions[currentSound]);

      for (int j = 0; j<myNodes.length; j++) {
        attractor_Lines.attract_Lines(myNodes[j]);
        myNodes[j].update();
      }
    }
  }


  void startPlaying() {
    currentSound=0;
    playCurrentSound();
  }

  void playNext() {

    if  (currentSound==3 || currentSound==7 || currentSound==10 || currentSound==13 ) {
      delay(pauseTime);
      if (millis()<pauseTime) {
        if (nodesAtEndPositions == null) {
          nodesAtEndPositions = new PVector[myNodes.length];
        }
      }
    } 
    currentSound++;
    if (currentSound > sounds.length-1) {
      currentSound=0;
      drawPhrases(phraseSets[0]);
    }  
    playCurrentSound();
  } 


  void playCurrentSound() {
    sounds[currentSound].rewind();
    sounds[currentSound].play();

    //initAttractor(currentSound);
  }
}
1 Like

Previous discussions:

This sketch relies on font files and multiple sound files to run – none of which are provided. Can you factor it down to a much simpler MCVE that demonstrates the problem? One that we can test without all the unrelated extras?

Dear Jeremy,
yes I will do so soon…
Thanks for replying.
L

Or maybe I can provide the sound files? Since the bug is linked with the timer and the sound files…

Dear Jeremy,
Here is a simplified version where the problem occur. When the mouse is over the 1rst line of the 1rst stanza the attractor moves the lines and they stay in place, but with the 2nd stanza when the mouse is over the 1rst line, the attractor move the lines, but when you move it the lines go back to their original locations. Why?!
It’s linked with time but can’t find a solution…
Thanks a lot in advance.
L

import geomerative.*;
RPoint[][] myPoints = new RPoint[4][0];
RFont font;

Attractor attractor;

String[][] phraseSets = new String[4][0];
String [] FR1 = {
  "On me dit de te haïr et je m'y efforce", 
  "Je t'imagine cruel, violent, implacable", 
  "Mais à te voir je n'ai bientôt plus de force", 
  "Et de te blesser je suis bien incapable", 
}; 
String [] FR2 = {
  "Tous mes camarades combattent avec rage", 
  "Et pleurent la nuit au souvenir des supplices", 
  "Infligés à leurs frères qui sont du même âge", 
  "et rêvent comme eux de toucher une peau lisse"
}; 
String [] FR3 = {
  "Et de pouvoir enfin caresser des obus", 
  "Autres que ceux produits par le pouvoir obtus", 
  "Je rêve de quitter ces boyaux infernaux"
};
String [] FR4 = {
  "De laisser ces furieux des deux bords du Rhin", 
  "Et de pouvoir embrasser ta chute de rein", 
  "Et porter notre amour sur les fonts baptismaux"
};

final color textColor = color(245);
// TIME
int startTime;
int index;
int duration;
int linespacing;
int fontsize;
int durPhrase1 = 4000;
int  durPhrase2 = 3500;
int  durPhrase3 = 2000;

int pauseTime=2500;

//----------------SETUP---------------------------------

void setup() {
  size(900, 600, JAVA2D);
  // add phrases to list
  phraseSets[0] = FR1;
  phraseSets[1] = FR2;
  phraseSets[2] = FR3;
  phraseSets[3] = FR4;
  smooth();
  RG.init(this);
  font = new RFont("FreeSans.ttf", 40, CENTER);
  stroke(textColor);
  strokeWeight(0.05);
  drawPhrases(phraseSets[0]);
  // TIME
  startTime = millis();
  index = 0;         // starting set of phrases
  duration = 2000;   // timer length
  linespacing = 100; // space between phrase lines
}

//----------------DRAW---------------------------------

void draw() {
  background(255);

  pushMatrix();
  initAttractor(index);
  attractor.moveTo(mouseX/4, mouseY/2); 
  attractor.attract();
  popMatrix();

  // draw from the center of the screen  
  translate(width/2, height/2);
  // vertically center text block by moving the top up by half the line spaces
  translate(0, -1.0 * linespacing * (phraseSets[index].length-1)/2.0);
  // loop through lines
  for (int i=0; i< myPoints.length; i++) {
    // draw a line
    for (int j=0; j< myPoints[i].length-1; j++) {
      pushMatrix(); 
      translate(myPoints[i][j].x, myPoints[i][j].y);
      noFill();
      stroke(0, 200);
      strokeWeight(0.25);
      float angle = TWO_PI*10;
      rotate(j/angle);
      bezier(-2*(noise(10)), 10, 25*(noise(10)), -5, 2*noise(5), -15, 10, -3);
      popMatrix();
    }
    // move to the next line
    translate(0, linespacing);
  }
  // check timer, and redraw phrases if time has passed 
  //changePhraseTimer(duration, phraseSets);
  
  changePhraseTimerNEW();
}

//----------------FUNCTIONS---------------------------------

/**
 * Timer will change phrases whenever to the next index
 * every time duration milliseconds passes.
 * Restarts itself by updating the global variable startTime.
 */

void changePhraseTimer(int duration, String[][] phraseSets) {
  if (millis()-startTime > duration) {
    index = (index+1) % phraseSets.length;
    println(index);
    startTime = millis();
    drawPhrases(phraseSets[index]);
  }
}

void changePhraseTimerNEW() {
  durPhrase1 = 4000;
  durPhrase2 = 3500;
  durPhrase3 = 2000;

  if (millis()-startTime >-1 && millis()-startTime < durPhrase1) {
    //drawPhrases(phraseSets[0]); 
  } else if (millis()-startTime > durPhrase1 && millis()-startTime < durPhrase1+durPhrase2) {
    drawPhrases(phraseSets[1]);
  } else if (millis()-startTime > durPhrase1+durPhrase2 && millis()-startTime < durPhrase1+durPhrase2+durPhrase3) {  
    drawPhrases(phraseSets[2]);
  } else if (millis()-startTime > durPhrase1+durPhrase2+durPhrase3 &&  millis()-startTime < durPhrase1+durPhrase2+durPhrase3*2) {    
    drawPhrases(phraseSets[3]);
  } else if (millis()-startTime > durPhrase1+durPhrase2+durPhrase3*2) {
    drawPhrases(phraseSets[0]);
    startTime = millis();
  }
}

//----------------TEXT ATTRACTOR INIT----------------------------------------------------------------------------------------------------------------------------------------
void initAttractor(int i) {
  if (i>=4 && i<8) {
    i-=4;
  } else if (i>=8 && i<11) {
    i-=8;
  } else if (i>=11 && i<14) { 
    i-=11;
  } else if (i>14) {
    i=0;
  }

  float x = 0;
  float y =-50; 
  // println(i);
  attractor = new Attractor(x, y, myPoints[i]);
}


/**
 * Renders a String[] array as a geomerative RGroup
 * (PolygonGroup) of points, then stores the points in
 * the global RPoint[][] array myPoints.
 */
void drawPhrases(String[] phrases) {
  myPoints = new RPoint[phrases.length][0];
  for (int j=0; j<phrases.length; j++) {
    RGroup myGroup = font.toGroup(phrases[j]);
    myGroup = myGroup.toPolygonGroup();
    myPoints[j] = myGroup.getPoints();
  }
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

class Attractor {

  float force_radious = 100;
  float maxForce = 15;
  RPoint position;
  RPoint[] points;

  Attractor(float x, float y, RPoint[] p) {
    points = p;
    position = new RPoint(x, y);
  }

  void attract() {

    for (int i =0; i < points.length; i++) {

      float d= points[i].dist(position);
      // println ("d : "+d);
      if (d < force_radious) {   
        RPoint desired = new RPoint(points[i]);
        //points[i]= new RPoint(points[i]);
        //println( "avant x : "+ points[i].x +" y: "+points[i].y);
        desired.sub(position);
        desired.normalize();
        desired.scale(map(d, 0, force_radious, maxForce, 0));
        points[i].add(desired);
        //println( "après x : "+ points[i].x +" y: "+points[i].y);
      }
    }
  }
  void display () {
    stroke(0);
    strokeWeight(2);
    // ellipse (position.x, position.y-750, 30, 30);
  }
  void moveTo(float x, float y) {
    position.x=x;
    position.y=y;
  }
}

@lolonulu===
in this code why have you commented line 127??
if you uncomment the 4 stanzas seem to behave the same way i think.

1 Like

Hi Akenaton,
Thanks for your reply. Actually the problem was more linked to the sound part of this sketch!
I’ll post an updated file later on.
Merci pour ton aide.
à biental,
L

pour le son je peux difficilement tester sans les fichiers
(bien que j’imagine, vu le nombre que ça correspond aux 14 vers)

happy coding!

Salut,
Merci de ton message. En fait maintenant je cherche à remettre à zéro une animation sans bloquer le programme avec delay(). Je crois qu’il existe une librairie avec un timer, ça te dit qq chose ?!

Are you referring to the CountdownTimer? You can install it via the contribution manager in the PDE and explore the examples that it comes with.

Kf

Dear kfrajer,
Well now my code is based on the length of my 14 sond files and it works well,
but then I don’t know how to freeze my animation between 2 stanzas after the 4 th
sound files has finished. Like when you use delay() but that allows you to execute another function during this time. I don’t know if CountdownTimer is what would suit in my case…?!
Thanks a lot for your help. Best, L

I tried your code below but then I don’t have your files. Without them, it is limited what I can do. Consider loading your project in github. It is free.

Kf

1 Like

dear kfrajer,
which code? where below?! Yes I’ll load my project on github since it’s linked to the sound…
Here is the link : https://github.com/lolonulu/Geomerative_sound_reactive_text
My problem is there: lines 68-88 of the SoundMaster class. I’d like to make a pause with the sound and the animation like with a delay(), but processing this animation coded in these lines during this 2 sec. pause, how should I proceed?!
Thanks a lot in advance for your help.

@lonolulu===

firstly running your code i get an arrayIndexOutOfBoundsException: 5 at the end of the first stanza, in the soundManager class and the playSound method, line 113.
secondly i dont understand what you mean with your pause: is it only with the sound or is it also with the letters an movements? - Anyway, it seems to me easy to add a boolean and a timer: at the end of the 4th stanza the timer is called and it triggers the boolean to true for 2000’’…

Thanks beaucoup Akenaton. Yes I have an OutOfBounds exception since I use pause() on the audioPlayer()…

I’d like that both the letters in movement and the lines in the background to freeze. After a second I would like to use a function to put back the background lines to their starting positions (lines 58 to 88 / SoundMaster class). Like that both the text and background lines will beset to their starting positions for the next stanza before the sounds files will deform them. The same at the end of each stanza.

A boolean and a timer is a good idea. I tried to see if there is a better option than sartTime variable and millis() but not sure that coundownTimer() fits my needs… Thanks for your help :smiley_cat:

I don’t manage to stop the sound… Don’t find a way or i get an arrayIndexOutOfBoundsException: 4 at the end of the first stanza. If you have some time to help…
Thks

where would you code the timer+boolean?!
before line 68 in the soundMaster class ?!

@lolonulu===
looking (too) quickly, but i will do that more accurately tomorrow
the problem is with your line 65
millis() cannot never be < millis()-sometime!
so your player dont pause
and you get the arrayIndexError…
get the sound stanza duration with minim .length();
create a timer() at the end of your draw()
create a boolean estEnPause; it is true when the timer > millis()+ soundstanzaduration
when it is true stop the player
create another timer with your pauseduration
when millis()> the second timer estEnPause = false:: the player has to restart
(add the estEnPause to your playSound method in soundManager class)

dear @akenaton
Thank you very much for your answer and your help. I’m trying what you suggest but as a beginner, not sure I get everything set properly ! tell me tomorrow! Thanks énormément !

Hey @akenaton,
I’ve posted the code on Github here : https://github.com/lolonulu/Geomerative_sound_reactive_text/tree/update_with_timers
Sorry already for the mistakes, my machine is verys slow here but at least I know it compiles, but no more…
Thanks a lot your help is precious
Best, L

1 Like