// redFrik 050621
// beatmatching, interpolation, clock synchronisation
// this will adjust tempo to assure downbeat after x seconds
+TempoClock {
sync {|tempo, secs= 4, resolution= 1|
var next, time, durCur, durNew, durDif, durAvg, stepsPerBeat,
delta, factor, steps, sum, durs, index= 0;
secs= secs.max(0.03); //saftey and lower jitter limit
next= this.timeToNextBeat(1);
time= secs-(this.tempo.reciprocal*next);
if(time<next, { //jump directly
this.tempo_(next/secs); //set a high tempo
this.sched(next, {
this.tempo_(tempo);
nil;
});
}, { //else interpolate
this.sched(next, { //offset the thing to next beat
durCur= this.tempo.reciprocal;
durNew= tempo.reciprocal;
durDif= durNew-durCur;
durAvg= durCur+durNew/2; //average duration for number of steps
stepsPerBeat= resolution.max(0.001).reciprocal.round;
steps= (time/durAvg).round*stepsPerBeat;
delta= stepsPerBeat.reciprocal; //quantized resolution
durs= Array.series(steps, durCur, durDif/steps);
sum= durs.sum/stepsPerBeat;
factor= time/sum;
this.sched(0, {
var tmp;
if(index<steps, {
tmp= (durs[index]*factor).reciprocal;
this.tempo_(tmp);
index= index+1;
delta;
}, {
this.tempo_(tempo);
nil;
});
});
nil;
});
});
}
}
examples:
//////////////////////////////////
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.2);
OffsetOut.ar(0, z*e);
}).send(s);
)
//-- go from 1.5 to 0.8 and end on downbeat 4 sec from now
~from= 1.5;
~to= 0.8;
~sec= 4;
c= TempoClock(~from);
c.sched(c.timeToNextBeat(1), {Synth(\ping, [\freq, 800]); 1});
(
SystemClock.sched(~sec, {Synth(\ping, [\freq, 1200]); ~to.reciprocal});
c.sync(~to, ~sec);
)
//-- dec temo. after 8.1 sec tempo 1.1
~from= 1.9;
~to= 1.1;
~sec= 8.1;
c= TempoClock(~from);
c.sched(c.timeToNextBeat(1), {Synth(\ping, [\freq, 800]); 1});
(
SystemClock.sched(~sec, {Synth(\ping, [\freq, 1200]); ~to.reciprocal});
c.sync(~to, ~sec);
)
//-- inc
~from= 1.1;
~to= 1.9;
~sec= 8.1;
c= TempoClock(~from);
c.sched(c.timeToNextBeat(1), {Synth(\ping, [\freq, 800]); 1});
(
SystemClock.sched(~sec, {Synth(\ping, [\freq, 1200]); ~to.reciprocal});
c.sync(~to, ~sec);
)
//-- quick adjust. interpolation suffers a little.
~from= 1.0;
~to= 1.2;
~sec= 3.3;
c= TempoClock(~from);
c.sched(c.timeToNextBeat(1), {Synth(\ping, [\freq, 800]); 1});
(
SystemClock.sched(~sec, {Synth(\ping, [\freq, 1200]); ~to.reciprocal});
c.sync(~to, ~sec);
)
//-- 2 tempoclocks!!! syncs them after 4.7 seconds
~from1= 1.02;
~to1= 1.8;
~from2= 1.3;
~to2= 1.8;
~sec= 4.7;
c= TempoClock(~from1);
d= TempoClock(~from2);
c.sched(c.timeToNextBeat(1), {Synth(\ping, [\freq, 800]); 1});
d.sched(d.timeToNextBeat(1), {Synth(\ping, [\freq, 1200]); 1});
(
c.sync(~to1, ~sec);
d.sync(~to2, ~sec);
)
//-- 2 tempoclocks synced almost at once
~from1= 1.02;
~to1= 1.8;
~from2= 1.3;
~to2= 1.8;
~sec= 0;
c= TempoClock(~from1);
d= TempoClock(~from2);
c.sched(c.timeToNextBeat(1), {Synth(\ping, [\freq, 800]); 1});
d.sched(d.timeToNextBeat(1), {Synth(\ping, [\freq, 1200]); 1});
(
c.sync(~to1, ~sec);
d.sync(~to2, ~sec);
)
//-- resolution make the interpolation smoother. this will update tempo 5times/beat (0.2)
~from= 1.1;
~to= 1.5;
~sec= 7.3;
c= TempoClock(~from);
a= Pbind(\degree, Pseq([0, 5, 3, 2], inf), \dur, 0.125, \amp, 0.1).play(c);
c.sync(~to, ~sec, 0.2);
see also syncing tempclocks