SynthDef Ugraph and TempoClock synchronization

Hi guys,

a would like to synchronize a SynthDef Ugraph with the music clock I’m using in order to have internal synthesis to always stay “aligned” with musical barlines.

Let’s say I have a synth which is creating a sound grain every so often thanks to an impulse.

  • How can I be able to have this impulse being fired on every barline?
  • And maybe have this structure being responsive to a BPM change?
  • Is there any “T-type” Ugen I can use?

you might schedule a pattern on your TempoClock which writes triggers to a Bus … and your Synths can read that bus to drive demand rate Ugens for example or retrigger EnvGens…

1 Like

There is a subclass of TempoClock known as TempoBusClock, which supposedly synchronizes it’s tempo with the Server… using a control bus which can passed as an argument when the clock is created.

It’s a fascinating concept, though it appears to be rarely used… it may be worth looking into, and there are at least a few examples in the documentation.

1 Like

Thank you so much @Rainer for your reply and your suggestions!

It seems to me that the TempoBusClock is a way to facilitate communication between tempo changes the language side and the synth instances on the server side via a “control-bus-mapped-to-a-synth-parameter” mechanism.

That’s fine to me but it is not quite what I want, also because, it seems to me that here we have not a perfect synchronism at the barlines, even if we have actually a value transfer between the language and the synth instance.

If you want to follow me in my mind flow, you can try what I’m telling making some change to the TempoBusClock example code:

a = { |tempo=1| Ringz.ar(Impulse.ar(tempo), [501, 500], 1/tempo) }.play;
t = TempoBusClock(a);
c = Task { loop { "klink".postln; 1.wait } }

If you try evaluating these lines of code at random moments in time you will eventually find that the sonic impulse is not time-synchronized with the text appearing in the post window (they are better synchronized if all the code is evaluated in a single evaluation shot, of course).

c.play(t);
c.stop;

But even if sound and text are offset one another, they still maintain a coherency in their relative delay when we change the tempo:

t.tempo = 1.3;
t.tempo = 0.125;
t.tempo = 1.0;

Still some doubts remain:

  • how to synchronize multiple synths to the tempo clock? Do we have to create multiple TempoBusClocks, one for each synth instance?
  • wouldn’t be much simpler to make a single TempoBusClock to “write” on top of a control bus, on its turn mapped to the same parameter for multiple synths (I’ve tried but it seems not to work :expressionless: )

Does it makes sense to you?
Do you have any other suggestions?

Thank you so much

This is a very interesting approach @semiquaver ,

can you please share some snippet of code showing me the “pattern writing to the bus” part in particular?
I think I’ve never done something like that before even if I’m quite used to the Pbindef.
Thank you anyway

Let’s see if I can do this from a cell phone and memory (forgive any mistakes)… Getting this right is a mildly tricky pattern. You need something like:

SynthDef(\tick, {
  var tick;
  tick = \beats.kr(0) * Impulse.ar(0) * Env.kr([1,1], [ControlDur.ir * 2]).Kr(gate:1, doneAction:2):
  OffsetOut.ar(\out.ir, tick)
}).add.

Pdef(\tick, Pbind(
  \dur, 1,
  \beats, Ptime(),
  \out, ~someBus
));

This should give you a trigger every beat, or however often you want it. You need the OffsetOut - with any other solution, your ticks will be rounded to your buffer size, which can make for jitter that can sometimes be audible with e.g. drum programming. If you want s monotonically increasing clock, you have to track the time via a UGen like Phasor, but reset it every time you get a new tick because the audio thread clock will drift with respect to your TempoClock.

1 Like

Yes, the examples in the documentation appear somewhat limited… I believe TempoBusClock may have been written to support the ProxySpace interface, for example, a clock can be provided as an argument to the ProxySpace(), however, the instance method .makeTempoClock can be called on the existing space, and this will always return a TempoBusClock.

It’s good to be aware that it exists with an argument value for a control to be supplied… as I’m sure you’ve read:

can be anything that responds to the message set(key, val, …) e.g. a Synth or a NodeProxy.

Yet it appears to be uncharted territory, and I have yet to see anyone use this class directly, even once, despite it’s implications.

1 Like

Perhaps there is a block of code that would clearly & concisely demonstrate the exact effect you wish to achieve?

s.boot;

c = TempoClick(subdiv: 4).tempo_(100/60);

(
a = { |trig|
	var sig = SinOsc.ar(TExpRand.kr(200, 800, trig));
	var eg = Decay2.kr(trig, 0.005, 0.05);
	(sig * eg * 0.1).dup
}.play(args: [trig: c.asMap]);
)

// do this as many times as you need,
// to be satisfied that it's responsing appropriately to tempo changes
c.tempo = rrand(60, 180) / 60;

a.release;
c.free;

Note that TempoClick is sending messages with server.latency applied, so any language-side sequences running alongside should also send timestamped (latency-ified) messages. (But there was a bug with this, which I fixed 5 minutes ago… so if you already have ddwCommon, please update from jamshark70/ddwCommon.)

hjh

2 Likes

I’d nominate this to be added to the vanilla class library.

:grin: I’ll leave that for others to decide. (Though, if it’s added at all, it might be good to change the name, because the same class name defined in core and in a quark is a class library compilation error – which creates a versioning issue for me as the quark author. Or I could change the name in the quark.) Also I’d revise the coding style.

I wrote this many years ago before I’d settled on a working method. I thought it might be good to run drum sequences from data in buffers, but Impulse.kr drifts away from the language-side clock very quickly at 44.1 kHz because a 64-sample block doesn’t divide 1 second evenly. So TempoClick resyncs the pulses once per beat. … But then I decided that buffer-based sequencing wasn’t flexible enough for my needs, so I abandoned it and basically never used TempoClick again after that.

hjh

Hi guys,
I think I have found something related to this topic on the pattern documentation tutorials.
I refer to what was said here by @semiquaver:

in particular on Pattern Guide 08: Event Types and Parameters where the type of the Pattern can be set to bus instead of note (the default).

I’m still working on this approach and doing some research on something is not clear to me but I think this could be a more than sensible solution for my purpose.

On the topic, here’s also something interesting.

I think I’ve found a solution to what I was looking for using the “pattern-write-on-a-control-bus” approach.

Here’s the code:

// first create a control bus to be used to write a value on on 
// every barline and read this by a synth on the server side
~my_bus = Bus.control(s, 1);
// eventaully plot it in order to check the values on the bus
~my_bus.scope;

// evaluate a pattern of "bus" type to write values on the bus
(
Pbindef(\test_bus_ctrl,
	\type, \bus,
	\out, ~my_bus,
	\array, Pseq([0.0, 1.0], inf),
	\dur, 1
).quant_([4]).stop.play;
)

// define a synth to read from the buffer and use values on it to change 
// the sound it produces.
(
SynthDef(\freezer, {
	|out=0, gate, amp=0.9, pan=0.0,
	atk=5, dcy=0.2, sus=0.7, rel=5,
	buf, bus
	|
	var sig, env, chain;
	// we are using a control bus to feed the synth
	var trigger = In.kr(bus,1);

	env = EnvGen.kr(Env.adsr(atk, dcy, sus, rel), gate, doneAction:2);
	sig = PlayBuf.ar(1, buf, loop:1);

	chain = FFT( LocalBuf((2048), 1), sig);
    chain = PV_Freeze(chain, trigger );
	sig = IFFT(chain);

	sig = LeakDC.ar(sig);
	sig = sig * env * amp;

	Out.ar(out, Pan2.ar(sig, pan));
}).add;
);

// load a buffer, anything you want
~my_buffer = Buffer.read(s, "/path/to/your/mono/soundfile.wav");

// now istantiate your synth, you should hear the sound being 
// freezed on every strong accent barlines while values ones and zeroes
// alternate in the control bus
(
x = Synth(\freezer, [
	\out, 0, \gate, 1, \amp, 0.9,
	\buf, ~my_buffer,
	\bus, ~my_bus, \pan, 0.0
])
)

// you can easily see how a tempo change has immediate effect on the sound
t = TempoClock.default;
t.tempo_(60/60)
t.tempo_(95/60)
t.tempo_(120/60)

// eventually stop the pattern and free the synth
Pbindef(\test_bus_ctrl).stop;
x.free;

3 Likes