Resetting TempoClock?

Dear SC people,

my friends that run MAX (and I won’t attempt to evangelize them :- ) have a button that resets their clock to the ONE whenever they press it, and that’s a simple but effective way to synch our setups by hand - good enough if the BPM tempi are set to the same values.

Trying to implement this functionality in SC, I found that we can set the .beats of a TempoClock to whatever value. Setting it to 0 is not gonna work as it effectively stops all the playing Tasks. Setting it to the next bar, however, does what I want, but with an unwanted side effenct, that I wonder if it can be worked around: corresponding to what the TempoClock help says in the “.beats = beats” section, it “immediately performs all tasks scheduled until the new time”. So we get a whole lot of notes playing at once. And some hanging notes as well for sustained Synths (which is not a problem in my use case, as currently all the notes I’m playing are self ending).
Does anyone of you have a better solution for this, or else, a way to temporarily suppress the execution of all the server commands the client sends in the instant just after executing the reset function?

		(
			SynthDef(\ping, {|freq, pan, amp=0.3|
				var e, z;
				e= EnvGen.ar(Env.perc(0, 0.2, amp), doneAction:2);
				z= SinOsc.ar(freq, 0, 0.2);
				OffsetOut.ar(0, Pan2.ar(z*e, pan));
			}).add;

		);
		t = TempoClock.default.tempo_(0.66);
		(
			Pdef(\a,
				Pbind(
					\instrument, \ping,
					\degree, Pseq((0..7), inf) + 7,
					\dur, 0.125, \pan, -0.8, \amp, 0.75
				)
			).play(t);

		);

		f = { t.beats = t.nextBar };
		f.(); // jumps to the ONE on the beginning of the next bar. But plays all the in-between notes AT ONCE as well. How to supress this?

		(
			Pdef(\b,
				Pbind(
					\degree, Pseq((0..7).reverse, inf) ,
					\dur, 0.125, \pan, 0.8, \amp, 0.1, \legato, 0.2
				)
			).play(t);
		);

		Pdef(\a).stop;
		f.(); // even worse: will leave hanging notes at some points (try several times to hear)

I’d suggest to stop the old TempoClock and make a new one.

It might even work to reschedule tasks on the new clock, though I haven’t tested. If both clocks remained running, I’d expect problems with double scheduling, but if the old clock is stopped, then that shouldn’t happen.

IMO this is likely to be more reliable than shifting beats.

hjh

Thanks, James!

Yes, what you suggest is of course a neater solution, and I think I’ve seen it implemented somewhere, but I can’t get it to work at all.

Continuing with the above example, I’ve tried this, entirely unsuccessfully. Can you, or anyone else, please give me a hint at how to create a neat transitioning for the running tasks to keep on playing on the new Clock?

f = { 
     t.stop; // destroy old clock
     t = TempoClock.new; // create a new one
     TempoClock.default = t;  // make it deflt
     // fails!
};

SystemClock.clear.sched(0, r { } )

Always works in my setup… should work for TempoClock as well.

TempoClock gets a bit tricky… it is very unique in the sense, that use of the class name will often default to the global instance for the most commonly used methods… you can see more here:

http://doc.sccode.org/Classes/TempoClock.html#*default

TempoClock.pause.reset.play is another possible solution…

Also, setting the clock as .permanent_(true) will allow it to persist, instead of being destroyed, by .stop or Cmd .

The basic format is like this. Note that it isn’t enough to stop the old clock and establish a new one as default. Tasks scheduled on the old clock need to be rescheduled onto the new clock. (What do you guess would happen if they aren’t rescheduled? The old clock is stopped, so the old clock will not wake them up. The new clock doesn’t have any reference to the tasks, so the new clock won’t wake them up either.)

This example reschedules the task by using a direct variable reference to the task. (It shouldn’t be formally necessary to separate the task function into a separate variable – but it will simplify the second example below.)

t = TempoClock.new;

(
~taskFunc = {
	loop {
		[thisThread.beats, thisThread.seconds].postln;
		1.0.wait;
	}
};

r = Task(~taskFunc).play(argClock: t, quant: 1);
)

// t.beats.frac: if t is now at beat 122.7,
// u should start at 0.7
(
u = TempoClock(t.tempo, t.beats.frac);
u.schedAbs(r.nextBeat + u.beats - t.beats, r);
t.stop;  // Do not omit this
)

r.stop; u.stop;

That could be inconvenient because you would have to be aware of everything scheduled on the old clock.

You can get the scheduled items from the old clock, by hacking into the queue’s data structure. This is not documented, but the following works:

(
~resetToNewClock = { |oldClock, newClock, beatOffset|
	var queue;
	if(beatOffset.isNil) {
		beatOffset = oldClock.beats - newClock.beats;
	};
	queue = oldClock.queue;
	(0, 3 .. queue.size - 2).do { |i|
		var priority = queue[i],  // probably don't need
		beat = queue[i+1],
		thing = queue[i+2];
		newClock.schedAbs(beat - beatOffset, thing);
	};
	oldClock.stop;  // Do not omit this
	newClock
};
)

t = TempoClock.new;

r = Task(~taskFunc).play(argClock: t, quant: 1);

// note that resetToNewClock needs no reference to `r`
// -- it just pops everything from t onto u
(
u = TempoClock(t.tempo, t.beats.frac);
~resetToNewClock.value(t, u);
)

r.stop; u.stop;

NOTE: It’s extremely important here that the old clock be stopped immediately. If that doesn’t happen, then tasks will be awakened both by the old and new clocks – hence t.stop in the first example and oldClock.stop in the resetToNewClock function.

hjh