Sampling a movement. Synchronize it on a tempo

Hello,
For my artistic machine project, I need to sample a movement and play it on a loop. This sample must be set on the tempo!
But, there is a small problem, let me explain:

I draw a movement with the mouse for two seconds.
It is repeated in loop starting exactly on the beginning of a new second.
The problem is that the number of frames per second is not constant.
Sometimes it takes 27 frames/sec, sometimes 28.
So I see discrepancies. I would have to manage to play this loop exactly from its beginning every two seconds.
The defect on two seconds is minimal, but on 8 seconds I have more significant shifts.
We have to find a way to have the same data beginning each two seconds
I think I need to move back datas each two cycles of one positions in the table of datas recorded . I think the solution is something like that

if actualSec%2==0
mx[i]=mx[i-1]
my[i]=my[i-1]

Thank you for your contributions.

My program below. NB: To create movement it is enough to press once the mouse. The drawing will start to be saved
at the beginning of the two-second period and exactly when the second changes.

//int num = 40; // you need normally 45 frames/s but actually with a 3D setting you  need only 40 frames
int num = 27; // you need normally 30 frames/s but actually with a 3D setting you  need only 26.33 frames
int numberSec = 2;

float mx[] = new float[num*numberSec]; // position X memorised 
float my[] = new float[num*numberSec]; // 

float rx[] = new float[num*numberSec]; // position X recorded 
float ry[] = new float[num*numberSec]; 

int beginTime,endTime,TimeMiddleElapsed,LastTimeMiddleElapsed,LastTimeElapsed;
int frame;
int restartTimer;
float Timer,Timer2;

boolean  mouseRecorded;
boolean synchroMeasure;
boolean beginSampling, endSampling;
int lastLastSec,lastSec,actualSec; 

public void settings() { // 
  size(600, 600, P3D);
} 

void setup() 
  {
  endSampling= false;
  synchroMeasure= false;
  mousePressed=false;
  mouseRecorded=false;

  frameRate (30);
  
  noStroke();
  fill(255, 0, 0, 50); 
  println("Start Drawing!");
  }
  
void draw(){  
  
     if  (lastLastSec!=lastSec){         
     print( "                 SAMPLING MODE " ) ;  print( " LASTLASTSEC " ) ; print( lastLastSec ) ; print( " LASTLAST%2 " ); print( lastLastSec%2 );
     print( " ACTUAL " ) ; print( actualSec ) ; print( " ACTUAL " ) ; print( actualSec ) ; print( " ACTUAL " ) ; println( actualSec ) ;
       
     lastLastSec=lastSec;
     synchroMeasure=true;
     background(40, 40, 255);  
     
     }
     else synchroMeasure=false;
    
     if  (actualSec!=lastSec){
         lastSec=actualSec;   
     }
         
    actualSec = second()%(numberSec+1);  // Values from 0 - x number of secondes
    samplingMovement(numberSec);
  }
  
void samplingMovement(float timeSec) {

     if (mousePressed==true)
      {
       mouseRecorded=true; 
     }  
    
      if(synchroMeasure==true && lastLastSec>=numberSec && lastLastSec<=numberSec){
      beginSampling=false;
     }    

     if (mouseRecorded==true  && synchroMeasure==true && lastLastSec<=0// 
     ) {
       print (" Restart Record ");  print (" Restart Record ");  println (" Restart Record ");
    mouseRecorded=false;
    endSampling= false;
    beginSampling=true;
    frame = 0; //
     }
     
    frame=frame+1;
     
    int i = int(frame%(num*timeSec+0)); // number of datas record = number of frame/ s  multipled by secondes
   
    if( beginSampling==true ) // frame>=0 &&
    {  
    rx[i] = mouseX;
    ry[i] = mouseY;
    mx[i] = rx[i];
    my[i] = ry[i];
    fill(255, 0, 0, 50); 
    circle(rx[i], ry[i], 10); 
    if (frame%1<=0) {
    print (" frame "); print (frame); print (" ry "); print (i); print ("  "); println (ry[i]);   // 
    }   
   }
   
    if( endSampling==true ) // begin to replay for 2 sec . If I Add frame>num*timeSec+1, there is no effect   
    {
    circle(mx[i]+400, my[i], 10);  
    if (frame%1<=0) {
    print (" frame "); print (frame); print (" ry "); print (i); print ("  "); println (ry[i]);   // 
    } 
     } 
    if(synchroMeasure==true && lastLastSec>=timeSec && lastLastSec<=timeSec){ // important to put condition here! or on the of the main loop
    endSampling=true;
   }   
}

This wonā€™t and canā€™t stay in sync for two reasons: your OS is not hard real-time and Processing is not hard real-time. Hard real-time means that the software guarantees that events will occur at the exact clock time that you schedule them to happen. Your OS is running a variety of programs and services and happily switches between them which means that at the exact time that you are trying to record a Processing frame, the OS might be busy running a virus scan in the background. Itā€™ll get back to you in just a moment but now your mouse position sample is slightly behind where you thought it was. Likewise, Processing is running on Java which can fire off memory garbage collection at any time, pausing your recording or playback just enough to push your timing off some more.

The best you can do is make your code try to adapt. Know that frames will not be exactly at 1/30 seconds apart for either recording or playback. Instead, use millis() to time each recorded sample and save that time along with your mouse samples and donā€™t depend on the frameCount at all. When you play back, donā€™t assume that the output frames will match the recorded frames but will instead fall between them. Use linear interpolation (lerp()) between the recorded samples on either side of your playback frame time to set your output mouse positions. If your playback frame time is off the end of your recorded frames, use the amount that you are off the end to index back into the start of the loop.

1 Like

Hi Scudly.
I tried to not depend of the frame but finally I depend of frame.
I canā€™t record frame depending of millis ().

Could you show me an example?
How can I create a table that could save datas each 25 mills secondes?
After Iā€™ll see how to make interpolation.

I put my program below. You can see I actualise second with millis () but I think it 's not the aim of you method.
And to record I have to press several time the mouse and I have more gap between occurrence of sample.

Thanks for helping

//int num = 40; // you need normally 45 frames/s but actually with a 3D setting you  need only 40 frames
int num = 27; // you need normally 30 frames/s but actually with a 3D setting you  need only 26.33 frames
int numberSec = 2;

float mx[] = new float[num*numberSec]; // position X memorised 
float my[] = new float[num*numberSec]; // 

float rx[] = new float[num*numberSec]; // position X recorded 
float ry[] = new float[num*numberSec]; 

int beginTime,endTime,TimeMiddleElapsed,LastTimeMiddleElapsed,LastTimeElapsed;
int frame;
int lastFrame;
int restartTimer;
float Timer,Timer2;

boolean  mouseRecorded;
boolean synchroMeasure;
boolean beginSampling, endSampling;
int lastLastSec,lastSec,actualSec; 

public void settings() { // 
  size(600, 600, P3D);
} 

void setup() 
  {
  endSampling= false;
  synchroMeasure= false;
  mousePressed=false;
  mouseRecorded=false;

  frameRate (30);
  
  lastFrame=27*numberSec;
  
  noStroke();
  fill(255, 0, 0, 50); 
  println("Start Drawing!");
  }
  
void draw(){  
     print (millis()%1000);
  
     
     if  (millis()%1000>982 || millis()%1000<18){         
     print( "                 SAMPLING MODE " ) ;  print( " LASTLASTSEC " ) ; print( lastLastSec ) ; print( " LASTLAST%2 " ); print( lastLastSec%2 );
     print( " LASTSEC " ) ; print( lastSec ) ; print( " ACTUAL " ) ; print( actualSec ) ; print( " ACTUAL " ) ; println( actualSec ) ;
       
     lastLastSec=lastSec;
     synchroMeasure=true;
     background(40, 40, 255);  
     
     }
     else synchroMeasure=false;
    
     if  (millis()%1000>982 || millis()%1000<18){
         lastSec=actualSec;  
         
     }
     
     if  (millis()%1000>982 || millis()%1000<18){
         actualSec+=1;  
         
     }
         
       samplingMovement(numberSec);
  
  }
  
void samplingMovement(float timeSec) {

     if (mousePressed==true)
      {
       mouseRecorded=true; 
       lastLastSec=0;
     }  
    
      if(synchroMeasure==true && lastLastSec>=numberSec && lastLastSec<=numberSec && numberSec>0){
      beginSampling=false;
     }    

     if (mouseRecorded==true  && synchroMeasure==true && lastLastSec<=0// 
     ) {
       print (" Restart Record ");  print (" Restart Record ");  println (" Restart Record ");
    mouseRecorded=false;
    endSampling= false;
    beginSampling=true;
    actualSec=0;
    frame = 0; //
  //  frame=frame+1;
     }
      if (frame>=0 ) {// 
      frame=frame+1;
     }
     
       if (synchroMeasure==true  && lastLastSec<2 && lastLastSec>=1// 
     ) {
  //  lastFrame=frame;
    print (" lastFrame "); println ( lastFrame);
      }
     
    int i =  int(frame%lastFrame); // number of datas record = number of frame/ s  multipled by secondes
   
    if( beginSampling==true && lastLastSec>=0 ) // frame>=0 &&
    {  
    rx[i] = mouseX;
    ry[i] = mouseY;
    mx[i] = rx[i];
    my[i] = ry[i];
    fill(255, 0, 0, 50); 
    circle(rx[i], ry[i], 10); 
    if (frame%1<=0) {
    print (" frameR "); print (frame); print (" ry "); print (i); print ("  "); println (ry[i]);   // 
    }   
   }
   
   if(synchroMeasure==true && lastLastSec>=timeSec && lastLastSec<=timeSec){ // important to put condition here! or on the of the main loop
    endSampling=true;
    
   } 
   
    if( endSampling==true && lastLastSec>2) // begin to replay for 2 sec . If I Add frame>num*timeSec+1, there is no effect   
    {
    circle(mx[i]+400, my[i], 10);  
    if (frame%1<=0) {
    print (" frameM "); print (frame); print (" my "); print (i); print ("  "); println (my[i]);   // 
    } 
     } 
      
}

If you are using the P3D OpenGL renderer, then itā€™s likely that your frame rate is synced to your monitor (depending on your OS and/or video driver settings) so you might be locked to 60 fps or one of its multiplicative factors (30, 20, 15). You might not be able to get 40 fps which would give you a 25ms frame rate.

Add another array to your mx, my arrays called time[] that records millis() - beginTime for each sample. Record until millis() - beginTime >= 2000 or however you want to stop recording. Print out your sample times, if you like, to see how skewed they are. On a perfect machine, you would see them as perfect multiples of 1000 / frameRate, but likely yours will be slightly larger than that.

To find your values at time t, find the sample i so that time[i] < t && t < time[i+1] then

float dt = (t - time[i]) / (time[i+1] - time[i]);
float x = lerp( mx[i], mx[i+1], dt );
float y = lerp( my[i], my[i+1], dt );
circle( x, y, 10 );

Hi.

I created a table and I add your program and I have point quite strange I think there is a problem with interpolation?

//int num = 40; // you need normally 45 frames/s but actually with a 3D setting you  need only 40 frames
//int num = 27; // you need normally 30 frames/s but actually with a 3D setting you  need only 26.33 frames
int num = 27; // with  frameRate(60) we have 58 frames/s but actually with a 3D setting 
int numberSec = 2;

float rx[] = new float[num*numberSec]; // position X recorded 
float ry[] = new float[num*numberSec]; 

float mx[] = new float[num*numberSec+1]; // position X memorised 
float my[] = new float[num*numberSec+1]; // 

float time[] =  new float[num*numberSec+1]; 

int beginTime,endTime,TimeMiddleElapsed,LastTimeMiddleElapsed,LastTimeElapsed;
int frame;
int frameRatio;
int doubleFrame;
int restartTimer;
float Timer,Timer2;
float t;

boolean  mouseRecorded;
boolean synchroMeasure;
boolean beginSampling, endSampling;
int lastLastSec,lastSec,actualSec; 

public void settings() { // 
  size(600, 600, P3D);
} 

void setup() 
  {
  endSampling= false;
  synchroMeasure= false;
  mousePressed=false;
  mouseRecorded=false;
  
  frameRatio=2;
  frameRate (30*frameRatio);
//  num=num* frameRatio; // it doesn't work
  
  
  noStroke();
  fill(255, 0, 0, 50); 
  print("Start Drawing ! The number of frame is "); println ( num );
  }
  
void draw(){  
  
     if  (lastLastSec!=lastSec){         
     print( "                 SAMPLING MODE " ) ;  print( " LASTLASTSEC " ) ; print( lastLastSec ) ; print( " LASTLAST%2 " ); print( lastLastSec%(numberSec-1) );
     print( " LASTSEC " ) ; print( lastSec) ; print( " ACTUAL " ) ; print( actualSec ) ; print( " ACTUAL " ) ; println( actualSec ) ;
       
     lastLastSec=lastSec;
     synchroMeasure=true;
     background(40, 40, 255);  
     
     }
     else synchroMeasure=false;
    
     if  (lastSec!=actualSec){
         lastSec=actualSec;   
     }
         
    actualSec = second()%(numberSec+1);  // Values from 0 - x number of secondes
    samplingMovement(numberSec);
  }
  
void samplingMovement(float timeSec) {

     if (mousePressed==true)
      {
       mouseRecorded=true; 
     }  
    
      if(synchroMeasure==true && lastLastSec>=numberSec && lastLastSec<=numberSec){
      beginSampling=false;
     }    

     if (mouseRecorded==true  && synchroMeasure==true && lastLastSec<=0// 
     ) {
       print (" Restart Record ");  print (" Restart Record ");  println (" Restart Record ");
    mouseRecorded=false;
    endSampling= false;
    beginSampling=true;
    frame = 0; //
    beginTime=millis();
     }      
    frame=frame+1;
     
    int i = int(frame%(num*timeSec)); // number of datas recorded = number of frame/ s  multipled by secondes
   
     if( beginSampling==true ) // 
    { 
  //  beginTime=millis();
    rx[i] = mouseX;
    ry[i] = mouseY;
    mx[i] = rx[i];
    my[i] = ry[i];
    time[i]= millis() - beginTime;
    print (" Time rec "); print (i); print ("  "); println (time[i]);
    
    fill(255, 0, 0, 50); 
    circle(rx[i], ry[i], 10); 
    if (frame%1<=0) {
    print (" frameRECi "); print (frame); print (" ry "); print (i); print ("  "); println (ry[i]);   // 
    }   
   }
      
    if( endSampling==true ) // begin to replay for 2 sec . If I Add frame>num*timeSec+1, there is no effect   
    {
    endTime=millis();
       
    float dt = (t - time[i]) / (time[i+1] - time[i]);
    float x = lerp( mx[i], mx[i+1], dt );
    float y = lerp( my[i], my[i+1], dt );
    fill(255, 50, 255, 50); 
    circle( x+380, y, 10 );
    print (" frameTim "); print (int (dt)); print (" my "); print (i); print ("  "); print (my[i]); print ("  "); println (y);    // 
    
    fill(255, 0, 0, 50); 
    circle(mx[i]+400, my[i], 10);  
    if (frame%1<=0) {
    print (" frameMEM "); print (frame); print (" my "); print (i); print ("  "); println (my[i]);   // 
    } 
     } 
    if(synchroMeasure==true && lastLastSec>=timeSec && lastLastSec<=timeSec){ // important to put condition here! or on the of the main loop
    endSampling=true;
   }   
}

Try this. Click and drag with the mouse to create a loop.

Edit: Iā€™ve tweaked the code to guarantee that the first sample starts at time 0. Also draws the line connecting the sample points and slows the frameRate to 10 so you can see the gradual drift that occurs during playback.

boolean bRecording = false;

class Sample {
  int t, x, y;
  Sample( int t, int x, int y ) {
    this.t = t;  this.x = x;  this.y = y;
  }
}

class Sampler {
  ArrayList<Sample> samples;
  int startTime;
  int playbackFrame;
  Sampler() {
    samples = new ArrayList<Sample>();
    startTime = 0;
  }
  void beginRecording() {
    samples = new ArrayList<Sample>();
    playbackFrame = 0;
  }
  void addSample( int x, int y ) {
    int now = millis();
    if( samples.size() == 0 ) startTime = now;
    samples.add( new Sample( now - startTime, x, y ) );
  }
  int fullTime() {
    return ( samples.size() > 1 ) ? 
      samples.get( samples.size()-1 ).t : 0;
  }
  void beginPlaying() {
    startTime = millis();
    playbackFrame = 0;
    println( samples.size(), "samples over", fullTime(), "milliseconds" );
  }
  void draw() {
    stroke( 255 );
    beginShape(LINES);
    for( int i=1; i<samples.size(); i++) {
      vertex( samples.get(i-1).x, samples.get(i-1).y );
      vertex( samples.get(i).x, samples.get(i).y );
    }
    endShape();
    int now = (millis() - startTime) % fullTime();
    if( now < samples.get( playbackFrame ).t ) playbackFrame = 0;
    while( samples.get( playbackFrame+1).t < now )
      playbackFrame = (playbackFrame+1) % (samples.size()-1);
    Sample s0 = samples.get( playbackFrame );
    Sample s1 = samples.get( playbackFrame+1 );
    float t0 = s0.t;
    float t1 = s1.t;
    float dt = (now - t0) / (t1 - t0);
    float x = lerp( s0.x, s1.x, dt );
    float y = lerp( s0.y, s1.y, dt );
    circle( x, y, 10 );
  }
}

Sampler sampler;

void setup() {
  size( 800, 800, P3D );
  frameRate( 10 );
  sampler = new Sampler();
}

void draw() {
  background( 0 );
  if( bRecording ) {
    circle( mouseX, mouseY, 10 );
    sampler.addSample( mouseX, mouseY );
  } else {
    if( sampler.fullTime() > 0 )
      sampler.draw();
  }
}

void mousePressed() {
  bRecording = true;
  sampler.beginRecording();
}

void mouseReleased() {
  bRecording = false;
  sampler.beginPlaying();
}

Hi.
So nice. Your work is better than I wanted :star_struck:. It gives me some ideas to control, develop and sculpt a loop in a real time!
But for the moment, i would like to be able de create a loop of 1, two or 4ā€¦ secondes beginning exactly at the change of the sec (synchronised with the pulse of the tempo).
Ideally when you pressed the mouse it trigs the recording of the loop beginning to the next pulse and ending to the next 1th or 2th or 4th pulseā€¦
And automatically after last pulse+1, the loop will be played.

I tried to find a way with 2 pulsation but it doesnā€™t work well.

Thank again for helping.

Sincerly, Benjamin

boolean bRecording = false;

int beginTime,endTime,TimeMiddleElapsed,LastTimeMiddleElapsed,LastTimeElapsed;
int numberOfPulse;

boolean  mouseRecorded, synchroMeasure, beginSampling, endSampling;
int lastLastSec,lastSec,actualSec; 

class Sample {
  int t, x, y;
  Sample( int t, int x, int y ) {
    this.t = t;  this.x = x;  this.y = y;
  }
}

class Sampler {
  ArrayList<Sample> samples;
  int startTime;
  int playbackFrame;
  Sampler() {
    samples = new ArrayList<Sample>();
    startTime = 0;
  }
  void beginRecording() {
    samples = new ArrayList<Sample>();
    playbackFrame = 0;
  }
  void addSample( int x, int y ) {
    int now = millis();
    if( samples.size() == 0 ) startTime = now;
    samples.add( new Sample( now - startTime, x, y ) );
  }
  int fullTime() {
    return ( samples.size() > 1 ) ? 
      samples.get( samples.size()-1 ).t : 0;
  }
  void beginPlaying() {
    startTime = millis();
    playbackFrame = 0;
    println( samples.size(), "samples over", fullTime(), "milliseconds" );
  }
  void draw() {
    stroke( 255 );
    beginShape(LINES);
    for( int i=1; i<samples.size(); i++) {
      vertex( samples.get(i-1).x, samples.get(i-1).y );
      vertex( samples.get(i).x, samples.get(i).y );
    }
    endShape();
    int now = (millis() - startTime) % fullTime();
    if( now < samples.get( playbackFrame ).t ) playbackFrame = 0;
    while( samples.get( playbackFrame+1).t < now )
      playbackFrame = (playbackFrame+1) % (samples.size()-1);
    Sample s0 = samples.get( playbackFrame );
    Sample s1 = samples.get( playbackFrame+1 );
    float t0 = s0.t;
    float t1 = s1.t;
    float dt = (now - t0) / (t1 - t0);
    float x = lerp( s0.x, s1.x, dt );
    float y = lerp( s0.y, s1.y, dt );
    circle( x, y, 10 );
  }
}

Sampler sampler;

void setup() {
  frameRate (30);
  size( 600, 600, P3D );
  sampler = new Sampler();
  endSampling= false;
  synchroMeasure= false;
  mousePressed=false;
  mouseRecorded=false;
}

void draw() {
  background( 0 );
  
  print ("beginSample ");  println ( beginSampling) ;

     if  (actualSec!=lastSec){
     print( "                 SAMPLING MODE " ) ;  print( " LASTLASTSEC " ) ; print( lastLastSec ) ;
     print( " ACTUAL%5 " ) ; print( actualSec%5 ) ; print( " LASTSEC " ) ; print( lastSec ) ; print( " ACTUAL " ) ; println( actualSec ) ;
       
     lastSec=actualSec; 
     synchroMeasure=true;
     background(40, 40, 255);  
     }
     
     else synchroMeasure=false;
             
    actualSec = second();  // Values from 0 - 59
    
      if (mousePressed==true )
      {
       lastLastSec=actualSec; 
       mouseRecorded=true; 
     }
        
      if(mouseRecorded==true && synchroMeasure==true && actualSec%5<2){
      bRecording = true;
      beginSampling=true;
      mouseRecorded=false;
     } 
    
      if(synchroMeasure==true && actualSec%5>1){ 
      bRecording = false;
      beginSampling=false;
      sampler.beginPlaying();
     }   
  
  if( bRecording  ) { 
    circle( mouseX, mouseY, 10 );
    sampler.addSample( mouseX, mouseY );
  } else { 
    if( sampler.fullTime() > 0   ) 
      sampler.draw();  
  }
}

void mouseReleased() {
   if (beginSampling==true) {//
  bRecording = true;
  sampler.beginRecording();
}

 else
  bRecording = false;
  sampler.beginPlaying();
}

Try this one. The loop period is inferred from the length of the loop and rounds up to the next power of two.

boolean bRecording = false;
float currTime;

class Sample {
  float t, x, y;
  Sample( float t, float x, float y ) {
    this.t = t;  this.x = x;  this.y = y;
  }
}

class Sampler {
  ArrayList<Sample> samples;
  float startTime;
  float sampleLengthTime;
  float period;
  int playbackFrame;
  Sampler( float now, float x, float y ) {
    samples = new ArrayList<Sample>();
    startTime = now;
    addSample( now, x, y );
    playbackFrame = 0;
  }
  void addSample( float now, float x, float y ) {
    samples.add( new Sample( now - startTime, x, y ) );
    sampleLengthTime = now - startTime;
    period = sampleLengthTime < 1 ? 1 :
      pow( 2, max( 0, ceil( log( sampleLengthTime ) / log(2) ) ) );
  }
  void draw( float now ) {
    if( samples.size() < 2 ) return;

    // draw the sample trail
    stroke( 64 );
    beginShape(LINES);
    for( int i=1; i<samples.size(); i++) {
      vertex( samples.get(i-1).x, samples.get(i-1).y );
      vertex( samples.get(i).x, samples.get(i).y );
    }
    endShape();

    // draw the current sample point
    float st = (now - startTime) % period;
    if( st > sampleLengthTime ) return;
    if( st < samples.get( playbackFrame ).t ) playbackFrame = 0;
    while( samples.get( playbackFrame+1).t < st )
      playbackFrame = (playbackFrame+1) % (samples.size()-1);

    Sample s0 = samples.get( playbackFrame );
    Sample s1 = samples.get( playbackFrame+1 );
    float t0 = s0.t;
    float t1 = s1.t;
    float dt = (st - t0) / (t1 - t0);
    float x = lerp( s0.x, s1.x, dt );
    float y = lerp( s0.y, s1.y, dt );
    noStroke();
    fill( 255 );
    circle( x, y, 10 );
  }
}

Sampler sampler;
ArrayList<Sampler> samplers;

void setup() {
  size( 800, 800, P3D );
  //frameRate( 10 );
  samplers = new ArrayList<Sampler>();
}

void draw() {
  currTime = millis() * 0.001;  // seconds since app started
  background( 0 );
  stroke(255);
  rect( (currTime % 1) / 1 * width,  0, 2, 8 );
  rect( (currTime % 2) / 2 * width, 10, 2, 8 );
  rect( (currTime % 4) / 4 * width, 20, 2, 8 );
  rect( (currTime % 8) / 8 * width, 30, 2, 8 );

  if( bRecording ) {
    samplers.get(samplers.size()-1).addSample( currTime, mouseX, mouseY );
  }
  
  for( Sampler s : samplers )
    s.draw( currTime );
}

void mousePressed() {
  bRecording = true;
  samplers.add( new Sampler( currTime, mouseX, mouseY ) );
}

void mouseReleased() {
  bRecording = false;
  Sampler s = samplers.get( samplers.size()-1 );
  println( s.samples.size(), "samples over", s.sampleLengthTime, "seconds, period", s.period, "seconds" );
}

Hi

I greatly appreciate your work because you anticipate my requests.
Very happy to be better understood than myself!
Indeed it will be good for the program to choose on its own whether the loop will have 2 or 4 or 8 pulses.
And ideally, it would be nice if the loops start at beat 0 and end at beat 2 or 4 or 8.

By pressing and dropping mouse, I tried to do two second loops that start drawing at beat 0.
I almost succeeded, but in fact we see a one-second loops.

To summarize it would be nice if the program stopped recording the loop when we stopped drawing and the loop started exactly at beat 0.

Thank you very much, Iā€™m very honored by all youā€™ve done so far, the program is almost perfect, let me know if youā€™d like some bitcoin.

Sincerely, Benjamin

Ps: I put the changes in the program

boolean bRecording = false;
boolean beginSample = false;
boolean synchroSample = false;
boolean mouseRecorded = true;
int countPulsation=0;
int lastCountPulsation=2;
boolean endRecord =true;

float currTime;



class Sample {

  float t, x, y;

  Sample( float t, float x, float y ) {

    this.t = t;  this.x = x;  this.y = y;

  }

}



class Sampler {

  ArrayList<Sample> samples;

  float startTime;

  float sampleLengthTime;

  float period;

  int playbackFrame;

  Sampler( float now, float x, float y ) {

    samples = new ArrayList<Sample>();

    startTime = now;

    addSample( now, x, y );

    playbackFrame = 0;

  }

  void addSample( float now, float x, float y ) {

    samples.add( new Sample( now - startTime, x, y ) );

    sampleLengthTime = now - startTime;

    period = sampleLengthTime < 1 ? 1 :

      pow( 2, max( 0, ceil( log( sampleLengthTime ) / log(2) ) ) );

  }



  void draw( float now ) {

    if( samples.size() < 2 ) return;



    // draw the sample trail

    stroke( 64 );

    beginShape(LINES);

    for( int i=1; i<samples.size(); i++) {

      vertex( samples.get(i-1).x, samples.get(i-1).y );

      vertex( samples.get(i).x, samples.get(i).y );

    }

    endShape();



    // draw the current sample point

    float st = (now - startTime) % period;

    if( st > sampleLengthTime ) return;

    if( st < samples.get( playbackFrame ).t ) playbackFrame = 0;

    while( samples.get( playbackFrame+1).t < st )

      playbackFrame = (playbackFrame+1) % (samples.size()-1);



    Sample s0 = samples.get( playbackFrame );

    Sample s1 = samples.get( playbackFrame+1 );

    float t0 = s0.t;

    float t1 = s1.t;

    float dt = (st - t0) / (t1 - t0);

    float x = lerp( s0.x, s1.x, dt );

    float y = lerp( s0.y, s1.y, dt );

    noStroke();

    fill( 255 );

    circle( x, y, 10 );

  }

}



Sampler sampler;

ArrayList<Sampler> samplers;



void setup() {

  size( 800, 800, P3D );

  frameRate( 30 );

  samplers = new ArrayList<Sampler>();

}



void draw() {

 samplingMovementPro();
}

void samplingMovementPro() {
 
  currTime = millis() * 0.001;  // seconds since app started
  
  if (millis()%1000<=35){ 
    
  countPulsation+=1; synchroSample= true; background (0);} // why background doesn't change?
  else synchroSample= false; background( 100, 40, 40 );
  
//  print (" countPulsation ");  print ( countPulsation );     print (" LastCountPulsation ");  println ( lastCountPulsation ); 
  
  stroke(255);
  rect( (currTime % 1) / 1 * width,  0, 2, 8 );
  rect( (currTime % 2) / 2 * width, 10, 2, 8 );
  rect( (currTime % 4) / 4 * width, 20, 2, 8 );
  rect( (currTime % 8) / 8 * width, 30, 2, 8 );
  
  if( bRecording ) {
    samplers.get(samplers.size()-1).addSample( currTime, mouseX, mouseY );
  }

  
  for( Sampler s : samplers )
    s.draw( currTime );
    
  if (mousePressed==true){ 
    lastCountPulsation=countPulsation;
    mouseRecorded=true;
   }
   
  if (mouseRecorded==true && synchroSample== true && countPulsation>2){ 
      endRecord=false;
   }
   
  if (endRecord==false){ 
  
     bRecording = true;
     samplers.add( new Sampler( currTime, mouseX, mouseY ) );
   }  
    
  if (countPulsation>=lastCountPulsation+2 && synchroSample== true){ 
   
    bRecording = false;
    Sampler s = samplers.get( samplers.size()-1 );
    println( s.samples.size(), "samples over", s.sampleLengthTime, "seconds, period", s.period, "seconds" );
    endRecord=true;
    }  
    
  
}

To play the loops from the start of their period rather than from when you started recording within the period, change the line

float st = (now - startTime) % period;

to just

float st = now % period;
1 Like

This version hard-codes the period to 1, 2, or 4 seconds (rather than growing the period to include the full sample) and stops recording at the end of that period. You can choose the next recorded sample period by typing ā€˜1ā€™, ā€˜2ā€™, or ā€˜4ā€™.

The samples are all stored in an array called ā€œsamplesā€. Shift-x will remove the last recorded sample.

It would be easy enough to speed up or slowdown the global clock by tweaking the way that currTime is computed. The interface for that could get rather complicated, though, since the same clock is used to compute both recording and playback. Sample playback currently assumes that time is going forward, but it would be trivial to add a reverse playback function as well.

boolean bRecording = false;
float currTime;
float nextSamplePeriod = 4.0;

class Sample {
  float t, x, y;
  Sample( float t, float x, float y ) {
    this.t = t;  this.x = x;  this.y = y;
  }
}

class Sampler {
  ArrayList<Sample> samples;
  float startTime;
  float sampleLengthTime;
  float period;
  int playbackFrame;
  Sampler( float newPeriod, float now, float x, float y ) {
    samples = new ArrayList<Sample>();
    startTime = now;
    addSample( now, x, y );
    playbackFrame = 0;
    period = newPeriod;
  }
  void addSample( float now, float x, float y ) {
    samples.add( new Sample( now - startTime, x, y ) );
    sampleLengthTime = now - startTime;
    if( sampleLengthTime > period )
      bRecording = false;
    // round up the period to the next power of 2
    //period = sampleLengthTime < 1 ? 1 :
    //  pow( 2, max( 0, ceil( log( sampleLengthTime ) / log(2) ) ) );
  }
  void draw( float now ) {
    if( samples.size() < 2 ) return;

    // draw the sample trail
    stroke( 64 );
    beginShape(LINES);
    for( int i=1; i<samples.size(); i++) {
      vertex( samples.get(i-1).x, samples.get(i-1).y );
      vertex( samples.get(i).x, samples.get(i).y );
    }
    endShape();

    // draw the current sample point
    //float st = (now - startTime) % period;
    float st = now % period;
    if( st > sampleLengthTime ) return;
    if( st < samples.get( playbackFrame ).t ) playbackFrame = 0;
    while( samples.get( playbackFrame+1).t < st )
      playbackFrame = (playbackFrame+1) % (samples.size()-1);

    Sample s0 = samples.get( playbackFrame );
    Sample s1 = samples.get( playbackFrame+1 );
    float t0 = s0.t;
    float t1 = s1.t;
    float dt = (st - t0) / (t1 - t0);
    float x = lerp( s0.x, s1.x, dt );
    float y = lerp( s0.y, s1.y, dt );
    noStroke();
    fill( 255 );
    circle( x, y, 10 );
  }
}

Sampler sampler;
ArrayList<Sampler> samplers;

void setup() {
  size( 800, 800, P3D );
  //frameRate( 10 );
  samplers = new ArrayList<Sampler>();
}

void draw() {
  currTime = millis() * 0.001;  // seconds since app started
  background( 0 );
  for( int i=0; i<3; i++ ) {
    float p = pow( 2, i );
    stroke( 64, p == nextSamplePeriod ? 255 : 64, 64 );
    rect( (currTime % p) / p * width, i*10, 2, 8 );
  }

  if( bRecording ) {
    samplers.get(samplers.size()-1).addSample( currTime, mouseX, mouseY );
  }
  
  for( Sampler s : samplers )
    s.draw( currTime );
}

void mousePressed() {
  bRecording = true;
  samplers.add( new Sampler( nextSamplePeriod, currTime, mouseX, mouseY ) );
}

void mouseReleased() {
  bRecording = false;
  Sampler s = samplers.get( samplers.size()-1 );
  println( s.samples.size(), "samples over", s.sampleLengthTime, "seconds, period", s.period, "seconds" );
}

void keyPressed() {
  if( key == '1' )
    nextSamplePeriod = 1.0;
  else if( key == '2' )
    nextSamplePeriod = 2.0;
  else if( key == '4' )
    nextSamplePeriod = 4.0;
  else if( key == 'X' ) {
    if( samplers.size() > 0 )
      samplers.remove( samplers.size() - 1 );
  }
}
1 Like

Hi M. Scud. Always happy to read you :wink:

To start the record of a sample I would like to press only once the mouse, i tried something but it doesnā€™t really work

boolean bRecording = false;

boolean mouseRecorded = true;

float currTime;

float st;

float nextSamplePeriod = 4.0;

float SampleLengthTime, Period;

class Sample {

float t, x, y;

Sample( float t, float x, float y ) {

this.t = t; this.x = x; this.y = y;

}

}

class Sampler {

ArrayList<Sample> samples;

float startTime;

float sampleLengthTime;

float period;

int playbackFrame;

Sampler( float newPeriod, float now, float x, float y ) {

samples = new ArrayList<Sample>();

startTime = now;

addSample( now, x, y );

playbackFrame = 0;

period = newPeriod;

}

void addSample( float now, float x, float y ) {

samples.add( new Sample( now - startTime, x, y ) );

sampleLengthTime = now - startTime;

SampleLengthTime= sampleLengthTime;

Period= period;

if( sampleLengthTime > period )

bRecording = false;

// round up the period to the next power of 2

//period = sampleLengthTime < 1 ? 1 :

// pow( 2, max( 0, ceil( log( sampleLengthTime ) / log(2) ) ) );

}

void draw( float now ) {

if( samples.size() < 2 ) return;

// draw the sample trail

stroke( 64 );

beginShape(LINES);

for( int i=1; i<samples.size(); i++) {

vertex( samples.get(i-1).x, samples.get(i-1).y );

vertex( samples.get(i).x, samples.get(i).y );

}

endShape();

// draw the current sample point

//float st = (now - startTime) % period;

st = now % period;

if( st > sampleLengthTime ) return;

if( st < samples.get( playbackFrame ).t ) playbackFrame = 0;

while( samples.get( playbackFrame+1).t < st )

playbackFrame = (playbackFrame+1) % (samples.size()-1);

Sample s0 = samples.get( playbackFrame );

Sample s1 = samples.get( playbackFrame+1 );

float t0 = s0.t;

float t1 = s1.t;

float dt = (st - t0) / (t1 - t0);

float x = lerp( s0.x, s1.x, dt );

float y = lerp( s0.y, s1.y, dt );

noStroke();

fill( 255 );

circle( x, y, 10 );

}

}

Sampler sampler;

ArrayList<Sampler> samplers;

void setup() {

size( 800, 800, P3D );

frameRate( 30 );

samplers = new ArrayList<Sampler>();

}

void draw() {

print (" mouseRecorded "); println ( mouseRecorded ) ;
print (" st "); println ( st ) ;
print (" SampleLengthTime "); println ( SampleLengthTime ) ;
print (" nextSamplePeriod "); println ( nextSamplePeriod ) ;

currTime = millis() * 0.001; // seconds since app started

background( 0 );

for( int i=0; i<3; i++ ) {

float p = pow( 2, i );

stroke( 64, p == nextSamplePeriod ? 255 : 64, 64 );

rect( (currTime % p) / p * width, i*10, 2, 8 );

}

if( bRecording ) {

samplers.get(samplers.size()-1).addSample( currTime, mouseX, mouseY );

}

for( Sampler s : samplers ){

s.draw( currTime );

}
if( st - nextSamplePeriod>=-0.1){

mouseRecorded=false;

}

if (mouseRecorded==true){

bRecording = true;

samplers.add( new Sampler( nextSamplePeriod, currTime, mouseX, mouseY ) );
}

else {

bRecording = false;

Sampler s = samplers.get( samplers.size()-1 );

println( s.samples.size(), "samples over", s.sampleLengthTime, "seconds, period", s.period, "seconds" );

}

}

void mousePressed() {
mouseRecorded=true;
}

void keyReleased() {

if( key == '1' )

nextSamplePeriod = 1.0;

else if( key == '2' )

nextSamplePeriod = 2.0;

else if( key == '4' )

nextSamplePeriod = 4.0;

else if( key == 'X' ) {

if( samplers.size() > 0 )

samplers.remove( samplers.size() - 1 );

}

}

In my previous code, if you remove mouseReleased(), it will begin recording on a mouse click and keep recording the mouse position until the end of the sample period (Samplerā€™s addSample() function sets bRecording to false when the sample length reaches the period). You donā€™t need your mouseRecorded flag.

The drawback is that you now have no way to record a sample that is shorter than currently set nextSamplePeriod.

1 Like

Hi God.

I will try to show you in a next post how I will sample a movement between one or two ā€¦ x measure in music.
Normally I will be able to synchronize the sample on a drum loop for example.

In another application of my machine, I will have to rework certain functions making patterns of oscillations and spirals. I hope you could help me.
I thank you as infinitely as the gratitude I have for you.

I will not hesitate to ask you for your valuable help. :sweat_smile:

Thanks again!
:kissing_heart: :saluting_face: :grin:

Hi,

I tried to adapt your program to sample the movement of my motor (the positions from 0 to 4000 from the serialPort of ArduinoDue) and it works, but my solution does not fit well.
I change data recorded for a period of 2 sec, to create a circular movement.
But as you can see, the mouvement is well recorded and repeated but with small bugs.
I think there is a problem with interpolation when the movement pas from 359Ā° to 1Ā°.

Could you please change the program to map correctly datas to make a beautiful circular movement?

Thank mister, i really need you!

// MANAGE ARDUINO && TENNSY
//import processing.serial.*;
//Serial DueSerialNativeUSBport101;

//int v1=0;
//int v1InMainLoop=0;

int actualSec;
int lastSec;

int virtualV2;

boolean bRecording = false;
boolean mouseRecorded;
float currTime;
float nextSamplePeriod = 2.0;

class Sample {
  float t, x, y;
  Sample( float t, float x, float y ) {
    this.t = t;  this.x = x;  this.y = y;
  }
}
class Sampler {
  ArrayList<Sample> samples;
  float startTime;
  float sampleLengthTime;
  float period;
  int playbackFrame;
  Sampler( float newPeriod, float now, float x, float y ) {
    samples = new ArrayList<Sample>();
    startTime = now;
    addSample( now, x, y );
    playbackFrame = 0;
    period = newPeriod;
  }
  void addSample( float now, float x, float y ) {
    samples.add( new Sample( now - startTime, x, y ) );
    sampleLengthTime = now - startTime;
    if( sampleLengthTime > period )
      bRecording = false;
      mouseRecorded = false;
    // round up the period to the next power of 2
    //period = sampleLengthTime < 1 ? 1 :
    //  pow( 2, max( 0, ceil( log( sampleLengthTime ) / log(2) ) ) );
  }
  void draw( float now ) {
    if( samples.size() < 2 ) return;

    // draw the sample trail
    stroke( 255 );
    beginShape(LINES);
    for( int i=1; i<samples.size(); i++) {
      vertex( samples.get(i-1).x, samples.get(i-1).y );
      vertex( samples.get(i).x, samples.get(i).y );
    }
    endShape();

    // draw the current sample point
    //float st = (now - startTime) % period;
    float st = now % period;
    if( st > sampleLengthTime ) return;
    if( st < samples.get( playbackFrame ).t ) playbackFrame = 0;
    while( samples.get( playbackFrame+1).t < st )
      playbackFrame = (playbackFrame+1) % (samples.size()-1);

    Sample s0 = samples.get( playbackFrame );
    Sample s1 = samples.get( playbackFrame+1 );
    float t0 = s0.t;
    float t1 = s1.t;
    float dt = (st - t0) / (t1 - t0);
    float x = lerp( s0.x, s1.x, dt );
    float y = lerp( s0.y, s1.y, dt );
    
     
  float X = map (x, 0, 400, 0,  TWO_PI);    // this has been data recorded for 2 sec
  float Y = map (y, 0, 400, 0,  TWO_PI);    // this has been data recorded for 2 sec

    noStroke();
    fill( 255, 40, 40 );
    circle ( 100* cos (X)+400, 100*sin (Y)+400, 20);
  }
}

Sampler sampler;
ArrayList<Sampler> samplers;

void setup() {

 //** printArray(Serial.list());
  
//**  DueSerialNativeUSBport101 = new Serial(this, Serial.list()[2], 9600);

  // Read bytes into a buffer until you get a linefeed (ASCII 10):
//***  DueSerialNativeUSBport101.bufferUntil('\n');

  size( 800, 800, P3D );

  frameRate( 30 );

  samplers = new ArrayList<Sampler>();
}

void draw() {

      print( " INTERNAL CLOCK lastLastSec " )  ; print( " actualSec " ) ; println( actualSec ) ;
       
     if  (actualSec!=lastSec){
         lastSec=actualSec;   
     }
         actualSec = second()%11;  // Values from 0 - 10
         
  //v1InMainLoop=  mouseY;// doesn't work
  //v1InMainLoop=  v1;// // doesn't work
   mouseY=virtualV2;
   mouseX=virtualV2;
 // mouseY= v1;// // work almost but there is one or two bad datas playedback in a loop
//activeSamplingMeasure();
  activeSamplingSecond();
  samplingMovementPro();  
    
  virtualV2+=5;
  virtualV2= (virtualV2)%400;
  float x = map (virtualV2, 0, 400, 0,  TWO_PI);
  float y = map (virtualV2, 0, 400, 0,  TWO_PI);
  
  circle ( 100* cos (x)+200, 100*sin (y)+200, 20);

  }
 
void samplingMovementPro() {
  
  currTime = millis() * 0.001;  // seconds since app started
  background( 0 );
  for( int i=0; i<3; i++ ) {
    float p = pow( 2, i );
    stroke( 64, p == nextSamplePeriod ? 255 : 64, 64 );
    rect( (currTime % p) / p * width, i*10, 2, 8 );
  }

  if( bRecording ) {
    samplers.get(samplers.size()-1).addSample( currTime, mouseX, mouseY ); // work almost with mouseY=v1
  //  samplers.get(samplers.size()-1).addSample( currTime, mouseX, v1InMainLoop ); // doesn't work
  //net.phase[net.size()-1]=  map (mouseY, 0, 400, 0, TWO_PI);
  }
  
  for( Sampler s : samplers )
    s.draw( currTime );
}

void mousePressed() {  
  mouseRecorded = true;
  }


void activeSamplingSecond() { 
   if (actualSec<=0 && actualSec!=lastSec && mouseRecorded == true) {
  bRecording = true;
  samplers.add( new Sampler( nextSamplePeriod, currTime, mouseX, mouseY ) );// work almost with mouseY=v1 
//  samplers.get(samplers.size()-1).addSample( currTime, mouseX, v1InMainLoop );// doesn't work
  }
}

void activeSamplingMeasure() { 
   if (actualSec<=0 && actualSec!=lastSec && mouseRecorded == true) {
  //   if (measure%4<=0 && beatTrigged == true && mouseRecorded == true){
  bRecording = true;
  samplers.add( new Sampler( nextSamplePeriod, currTime, mouseX, mouseY ) ); // work almost with mouseY=v1
//  samplers.get(samplers.size()-1).addSample( currTime, mouseX, v1InMainLoop );// doesn't work
  }
}

void keyPressed() {
  if( key == '1' )
    nextSamplePeriod = 1.0;
  else if( key == '2' )
    nextSamplePeriod = 2.0;
  else if( key == '4' )
    nextSamplePeriod = 4.0;
  else if( key == '8' )
    nextSamplePeriod = 8.0;
  else if( key == 'X' ) {
  if( samplers.size() > 0 )
      samplers.remove( samplers.size() - 1 );
  }
}
/*
void serialEvent(Serial DueSerialNativeUSBport101) { // receive 2 datas splited with , and the last is send with println from ARDUINO

  // read the serial buffer:
  String myString = DueSerialNativeUSBport101.readStringUntil('\n');

  // if you got any bytes other than the linefeed:
  myString = trim(myString);

  // split the string at the commas
  // and convert the sections into integers:
  int values[] = int(split(myString, ','));

  if (values.length > 0) {// v1 is the value of my stepperMotor from  0 to 4000
    v1 = (int) map (values[0], 0, 4000, 10, 410);
    print (" MouseY ");   print (mouseY);   print (" v1 ");   println (v1);     
  }
 }
 */