s.boot; ( SynthDef(\ping, {|freq| var e, z; e= EnvGen.ar(Env.perc(0, 0.1), doneAction:2); z= SinOsc.ar(freq.dup, 0, 0.1); OffsetOut.ar(0, z*e); }).send(s); ) ( //number 0 is master clock, 1-3 are slaves ~v= Main.elapsedTime.ceil; ~t= {TempoClock(1, 0, ~v)}.dup(4); //create 4 clocks in an array ~diffs= 0.dup(4); //array that store offsets compared to master ~tasks= [ {|beat, sec| Synth(\ping, [\freq, 800]); [\t0, beat, sec].postln; 1}, {|beat, sec| Synth(\ping, [\freq, 900]); [\t1, beat, sec].postln; 1}, {|beat, sec| Synth(\ping, [\freq, 1000]); [\t2, beat, sec].postln; 1}, {|beat, sec| Synth(\ping, [\freq, 1100]); [\t3, beat, sec].postln; 1} ]; ~t.do{|x, i| x.sched(~t[0].timeToNextBeat(1), ~tasks[i])}; //start all clocks and their tasks ) //dis-sync the clocks ( ~t[0].tempo_(1.1); ~t[1].tempo_(1.2); ~t[2].tempo_(1.3); ~t[3].tempo_(1.4); ) ~diffs.postln; //offsets still all 0 //sync ( ~t[0].sched(~t[0].timeToNextBeat(1), { //follow master clock 3.do{|i| //do for all slaves ~diffs.put(i+1, ~t[i+1].timeToNextBeat(1)); //store offset ~t[i+1].tempo_(~t[0].tempo).clear; //set all to master's tempo ~t[i+1].sched(0, ~tasks[i+1]); //reschedule }; nil; }); ) //after a sync like this you need to schedule using these offsets ~diffs.postln; //so scheduling on the master clock is easy... (ignore offset as it is always 0) ~t[0].sched(~t[0].timeToNextBeat(1), {|beat, sec| Synth(\ping, [\freq, 2100]); 0.5}); //but note that for any other clock, scheduling need to calculate with the offsets in ~diffs //example: start something on clock #4 ~t[3].sched(~t[3].timeToNextBeat(1)+(1-~diffs[3]), {|beat, sec| Synth(\ping, [\freq, 4100]); 1/3}); ~t.do{|x| x.clear};
but one can also adjust by changing tempo to something slower/faster for one intermediate beat. a bit slower but not so brutal.
( //number 0 is master clock, 1-3 are slaves ~v= Main.elapsedTime.ceil; ~t= {TempoClock(1, 0, ~v)}.dup(4); //create 4 clocks in an array ~tasks= [ {|beat, sec| Synth(\ping, [\freq, 800]); [\t0, beat, sec].postln; 1}, {|beat, sec| Synth(\ping, [\freq, 900]); [\t1, beat, sec].postln; 1}, {|beat, sec| Synth(\ping, [\freq, 1000]); [\t2, beat, sec].postln; 1}, {|beat, sec| Synth(\ping, [\freq, 1100]); [\t3, beat, sec].postln; 1} ]; ~t.do{|x, i| x.sched(~t[0].timeToNextBeat(1), ~tasks[i])}; //start all clocks and their tasks ) //dis-sync the clocks ( ~t[0].tempo_(1.1); ~t[1].tempo_(1.2); ~t[2].tempo_(1.3); ~t[3].tempo_(1.4); ) //sync ( var master= ~t[0]; master.sched(master.timeToNextBeat(1), { //follow master clock var newTempo= master.tempo; //set all to master's tempo (or other) 3.do{|i| //do for all slaves var left, temp; left= ~t[i+1].timeToNextBeat(1); //percent left until next beat temp= newTempo/left.reciprocal; //find adjust tempo ~t[i+1].tempo_(temp); master.sched(master.timeToNextBeat(1), { ~t[i+1].tempo_(newTempo); //set to newTempo nil; }); }; nil; }); ) //after a sync like this you not _not need to schedule using offsets ~t[0].sched(~t[0].timeToNextBeat(1), {|beat, sec| Synth(\ping, [\freq, 2100]); 0.5}); ~t[3].sched(~t[3].timeToNextBeat(1), {|beat, sec| Synth(\ping, [\freq, 4100]); 1/3}); ~t.do{|x| x.clear};
see also tempoclock beatmatching, interpolation, synchronisation
/redFrik