Thinking about this issue further, though –
Part of this depends on what you want to do. If you simply want the tempo available on the server side, then it’s easy – just make your own node proxy to store the tempo as a number. (This is what TempoBusClock does.)
TempoClock.default = l = LinkClock.new.latency_(s.latency).permanent_(true);
Ndef(\tempo, l.tempo); // numeric proxy
(
var ctl;
ctl = SimpleController(l)
.put(\tempo, { Ndef(\tempo, l.tempo) })
.put(\stop, { ctl.remove });
)
Now anything in the server can get the tempo from Ndef(\tempo).kr(1)
in a SynthDef, or Ndef(\tempo).bus.asMap
in a pattern.
BUT that “example c” is running Impulses. This is nastier than you think.
Sync between server-side impulses and language-side clocks is already difficult. If it’s Impulse.ar
, it would be OK against a TempoClock for awhile, but sound card clocks are usually not super-accurate, so you might experience some drift after a long time. Impulse.kr
at 44.1 kHz drifts out of sync within a few minutes (because 1 second does not divide evenly into an integer number of control blocks).
Sync against a LinkClock will be even less accurate because LinkClock makes continual micro-adjustments to stay in sync with other peers. You gain the ability to play in time with other computers, but lose any hope of sample accuracy.
A TempoBusClock strategy vs LinkClock is not enough to ensure sync, then.
What I would do is to drive impulses from the language side. If there’s a new pulse-train synth every beat, then the micro-adjustments remain micro and it should sound pretty smooth.
Roadbumps: 1. The click synth needs to be sure to reset the control bus back to 0 before it releases (–> Delay1). 2. The synth arg name can’t be tempo
because this is reserved in the event system. 3. The click nodeproxy needs to initialize at control rate, because without this, it assumes that every pattern played on the proxy will be audio rate.
So this is a case where you might get the basic principle right, but there are a bunch of other things waiting to trip you up.
s.boot;
TempoClock.default = l = LinkClock.new.latency_(s.latency);
// no need to re-evaluate this if you did it already in the earlier example
(
var ctl;
Ndef(\tempo, l.tempo); // numeric proxy
ctl = SimpleController(l)
.put(\tempo, { Ndef(\tempo, l.tempo) })
.put(\stop, { ctl.remove });
)
(
SynthDef(\krClick, { |out, beatsPerSec = 1, beats = 1, subdiv = 4|
var click = Impulse.kr(beatsPerSec * subdiv);
var count = PulseCount.kr(click);
FreeSelf.kr(Delay1.kr(count >= (subdiv * beats)));
Out.kr(out, click);
}).add;
)
// It turns out, if I don't initialize the proxy
// to a kr signal, then the Pbind will be assumed to be ar
// and that will make the clicks unavailable
Ndef(\click, { DC.kr(0) }).quant_(1).clock_(l);
Ndef(\click, Pbind(
\instrument, \krClick,
\beatsPerSec, Ndef(\tempo).bus.asMap,
\dur, 1
));
Ndef(\boop, {
var trig = Ndef(\click).kr(1);
var eg = Decay2.kr(trig, 0.01, 0.1);
var osc = SinOsc.ar(TExpRand.kr(200, 800, trig));
(osc * eg * 0.5).dup
});
Ndef(\boop).play;
Ndef(\boop).stop;
I tested this against another Link peer, where the other one is changing tempo, and sync is tight.
What this version doesn’t give you on the server side is information about the metrical position of each click. Unfortunately, I’ve already spent more time than I expected on this, so I don’t quite have time to do that today.
hjh