Routine triggered twice

I’m using a SendTrig and HPZ1 to trigger a finite Routine when a Phasor loops. They should match in time, as the Routine loops 4 times with a 1 second wait, and the Phasor is 4 seconds long—but I’m wanting to use this approach to ensure that they are always perfectly lined up.

Sometimes, without any apparent pattern, the Routine seems to play twice, and therefore reaches its end halfway through the Phasor’s period.

(
~rout = Routine({4.do{ |i|
	i.asInteger.postln;
	1.wait;
}});

SynthDef(\phasor, {
	var sig;
	sig = Phasor.ar(0, 1, 0, s.sampleRate * 4);
	SendTrig.ar(HPZ1.ar(sig).neg);
}).add;

OSCdef(\looped, { |msg|
	~rout.stop;
	~rout.reset;
	~rout.play(SystemClock);
	msg.postln;
}, '\tr');
)

Synth(\phasor);

I don’t think that it’s the SendTrig triggering the OSCdef twice, as the post window only shows the trigger message once when this doubling occurs. It always looks like this, but sometimes two numbers from the routine are posted at the same time:

[ /tr, 1005, 0, 0.0 ]
0
1
2
3

What could be causing this? The purpose of this is so that Routine will trigger some synths metronomically that I don’t want to drift from the Phasor looping (which is playing a sample). Is there a different approach to keep things lined up with a Phasor?

This isn’t a safe pattern to use. “Stop” doesn’t remove it from the clock.

I’d recommend to stop the old routine and then throw it away – make and play an all-new routine object.

hjh

1 Like

I’m not sure I can solve your problem, but watch out for OSC timing issues: Why you should always wrap Synth(...) and Synth:set in Server.default.bind { ... }

If you try to trigger synths from this Routine, you’ll have to deal with the indeterminacy of scsynth → sclang OSC plus the jitter/latency tradeoff when instantiating synths. I would advise doing the timekeeping in sclang if you can and schedule OSC messages with s.bind, but that’s not applicable for all scenarios. Is there a particular reason for having a server-side tempo phasor here?

1 Like

Regarding:

There are two fundamental timing issues which mean that this can never happen:

  1. The server-side and the language clocks run at different acutal sample rates. Evaluate s.actualSampleRate to check.

  2. Even if the two clocks where exactly lined up, communication between language and server takes a small amount of time (and this time varies quite a bit).

I modified you code a little to exemplify the timing issues , the main thing is that on each iteration af new Routine is returned by a function instead of the same Routine being stopped and reset.

(
var initTime;
~rout = { 
	Routine({4.do{ |i|
		[\i, i, \langTime, SystemClock.seconds - initTime].postln;
		1.wait;
	}
	})
};

SynthDef(\phasor, {
	var sig;
	sig = Phasor.ar(0, 1, 0, s.sampleRate * 4);
	SendTrig.ar(HPZ1.ar(sig).neg);
}).add;

OSCdef(\looped, { |msg|
	// ~rout.stop;
	// ~rout.reset;
	~rout.().play(SystemClock);
	msg.postln;
}, '\tr');
{ 
	 initTime = SystemClock.seconds;
	Synth(\phasor) 
}.fork.play(quant: 1);
)
1 Like

Thanks so much for the detailed info! I’m doing a live input loop into a buffer with the Phasor, with feedback. I initially thought of retriggering the Phasor in a Routine, but then I would have pops with the buffer.

If you’re loop-recording continuously, there is no need for the language to retrigger the Phasor at all. The Phasor will loop around on its own, with sample-accurate timing (which you will never get with language involvement).

If you write the current recording position onto another bus, then other synths can be aware of it…

(If it’s a one-shot recording, then you’d trigger it from the language on demand – but then not retrigger – here again I’d see no need to try the Routine approach.)

hjh