Rec a pattern to a .gif in Processing || when a pattern is Over?

Hi everyone,
I’m trying to find a way to “cut” properly a .gif , to show how patterns work visually.
But some of the .gif animations look not cutted precisely, I’m doing it in this quite rough way.

You need the librarie in processing called gif-animation

Processing 3.x

Download and unzip the gifAnimation.zip and copy the gifAnimation-folder into your processing libraries folder.

Here a nice and simple processing example:

So the idea is to:

  1. send the stratrec from sc -> processing that run the function to start recording the .gif
  2. processing ask the osc data of the busses and dysplay them in the draw()…
  3. send the stoprec from sc -> processing that run the function to stop recording the .gif
    end exit from the draw().

so in the end I can find my .gif in the processing folder, like here :slight_smile:Palindromo:
palindromo

It works but not always, sometimes the gif is not compleate or truncated.

I was doing like that,
and inside the routine I have to calculate the .wait time,
but … if I’m using some random thing in the pattern It’s not possible to know how long it would be the pattern eg : \dur, Pseq((1/16!12.rand),1);

so it’ would be really useful to know when the pattern is Over, and then send the message to stop the recording.

And to be honest it would be really nice to find a way to do everything inside the pattern itself,
I mean to send start/stop rec…

like send start at the same time or just before the pattern starts, and then when it’s over.

I have a bunch of pattern that I would love to visualize, so something a little more generic,and robust it would be needed.

And I guess it could be used for other porposes as well, like recording “videos” from processing, that is not a bad thing…

// general tempo metro that I always use in every projects.. 
~t = TempoClock.new(72/60).permanent_(true);

// reserve a 6 channel bus for lights
~lightsBus =  Bus.control(s,6);
// let's see some waves
~lightsBus.scope;

~lightsAddr = NetAddr("127.0.0.1", 12321);

// reply to Processing's data requests
// this is  to show what is going on in the busses only
OSCdef(\processing,{
    var msg = ["/amps"] ++ ~lightsBus.getnSynchronous(6);
    ~lightsAddr.sendMsg(*msg)
}, "/getAmps").permanent_(true);


(
r =Routine({

~lightsAddr.sendMsg('/startrec',"startrec".postln);
//------------------------------------

Pbindef(\test,
    \bus,Pseq([0,5,1,4,2,3],1) + ~lightsBus.index,
    \dur, Pseq((1/4!4),inf),
    ).play;
	
	1.wait;

//------------------------------------
~lightsAddr.sendMsg('/stoprec',"stoprec".postln);

Pbindef(\test).stop.clear;

}).play(~t,quant:.beatsPerBar);
)

processing listen and ask

import gifAnimation.*;
import oscP5.*;
import netP5.*;

GifMaker gifExport;
int frames = 0;

OscP5 osc;
NetAddress sc;

float amp1, amp2, amp3, amp4, amp5, amp6;

public void setup() {
    frameRate(60);
  smooth();
  size(420, 66);
 
  osc = new OscP5(this, 12321);
  sc = new NetAddress("127.0.0.1", 57120);
  osc.plug(this, "newamps", "/amps");
  osc.plug(this, "startrec", "/startrec");
  osc.plug(this, "stoprec", "/stoprec");
 

  noFill();
  stroke(0);
  strokeWeight(20);
 
  gifExport = new GifMaker(this, "export.gif", 100);
}

void draw() {

  background(255);

  background(0);

  //ask data at frameRate using .send
  OscMessage msg = new OscMessage("/getAmps");
  osc.send(msg, sc);

  // UI
  noStroke();

  // channel 1
  fill(153, 255*amp1, 0);
  rect(0, 0, width/6, height*amp1);

  // channel 2
  
  fill(153, 255*amp2, 0);
  rect(70, 0, width/6, height*amp2);

  // channel 3
  fill(153, 255*amp3, 0);
  rect(140, 0, width/6, height*amp3);

  // channel 4
  fill(153, 255*amp4, 0);
  rect(210, 0, width/6, height*amp4);

  // channel 5
  fill(153, 255*amp5, 0);
  rect(280, 0, width/6, height*amp5);

  // channel 6
  fill(153, 255*amp6, 0);
  rect(350, 0, width/6, height*amp6);

  if (gifExport != null) {
    gifExport.setDelay(20);
    gifExport.addFrame();
    frames++;
  }
}

void newamps(float rms1, float rms2, float rms3, float rms4, float rms5, float rms6) {
  amp1 = rms1;
  amp2 = rms2;
  amp3 = rms3;
  amp4 = rms4;
  amp5 = rms5;
  amp6 = rms6;
  println("[", amp1, amp2, amp3, amp4, amp5, amp6, "]");
}

void startrec(String s) {
  gifExport = new GifMaker(this, "export.gif", 100);
  gifExport.setRepeat(0); // make it an "endless" animation
  println("----------- start " + s);
}

void stoprec(String s) {
  gifExport.finish();
  exit();
  println("----------- stop " + s);
}

Any suggestions?

Really thanks for your time like always.

Let’s look at how Pattern.record does it:

so maybe do the same Pprotect/Pfset/Pseq combination, or even just Pfset, but instead of working with recorder, send some OSC to processing?
The nice thing about Pfset here is the cleanup function that you can use to send /stoprec:

(simplified example, with postln instead of sendMsg)

Pfset(
  {"starting".postln}, 
  Pbind(\dur,1,\note,Pseq((0..10))).trace, 
  {"stopped".postln}
).play
1 Like

I had a problem because of the global variables that I wasn’t able to “see” inside the Pfset… but like that it works.

(
p = Pbind(
\dur, Pseq((1/4!4),1)
);

r = Pfset(
func:{(
~lightsAddr = NetAddr(“127.0.0.1”, 12321);
~lightsAddr.sendMsg(’/startrec’,“startrec”.postln);
)},
pattern: p,
cleanupFunc:
{
(
~lightsAddr = NetAddr(“127.0.0.1”, 12321);
~lightsAddr.sendMsg(’/stoprec’,“stoprec”.postln);
)
}).play;
)
r.stop;

Thanks a lot for the example!

If you write your “global” variable to a local one, in the scope where you create the Pfset, then Pfset functions can see it:

(
var lightsAddr = ~lightsAddr;
Pfset(
    {lightsAddr.postln},
    Pbind(\dur,1,\note,Pseq((0..1))),
    {lightsAddr.postln}
).play;
)
1 Like