Synch Tdef and Synth with Global Clock

Hey, I am trying to synchronize all my Synths/Effects with a global clock. I came up with the following code. The first Ndef as well as the Tdef synchronize to the t.tempo. In the second Ndef, I try to synchronize an envelope-trigger with the global clock, but it doesnt work. Does anyone know what that could be? Thank you very much :slight_smile:

p = Ndef.dictFor(s);
p.makeTempoClock;
t = p.clock;
TempoClock.default = p.clock;

t.tempo = 1;

//works
Ndef(\out, { Ringz.ar(Impulse.ar(Ndef(\tempo).kr), [501, 500], 1/Ndef(\tempo).kr) * 0.1 }).play;

//works
(
Tdef(\klink, { loop {
	(note: 10, sustain: 0.05).play;
	"klink".postln;
	1.wait

} }).play(t);
)

//if I want to trigger an envelope, it doesnt work
(
Ndef(\sound, {arg time = 1;
	var sig, env, gate;
	gate = Impulse.ar(Ndef(\tempo).kr/time); // this doesnt work
	//gate = Impulse.ar(1/time); // this works
	env = EnvGen.ar(Env([0, 0.75, 0.75, 0],[time/2, 0 , time/2], curve: \sine ), gate: gate);
	sig = SoundIn.ar(0)*env;
	Out.ar(0, PanAz.ar(4, sig, env));
}).play;
)
1 Like

Sound cards do not run at exactly the advertised sample rate. You think 44,100 samples = exactly one second, but it’s actually slightly shorter or longer.

So all audio software has to make a choice:

  • Run according to the sample clock (meaning that 120 bpm is very slightly inaccurate).

  • Or run according to a stable system clock (meaning 120 bpm may be more accurate while playing in real time, but the number of samples between beats may be slightly off).

SuperCollider, when running in real time, does the latter. One reason for this (as I said just a couple of days ago on another thread) is that we want to support networked music applications, where there is no single sample clock.

The fallout from this is that you cannot use server-side and language-side timing simultaneously. (If you really need 100% sample-accurate timing control, either use non-real-time rendering in SC, or switch to ChucK.)

Your Ndef triggering the envelope will have to receive the trigger from the language.

FWIW, when I first started with SC, I thought it would be a good idea to do rhythm sequencing using server-side data, but I abandoned that approach pretty quickly. I understand what you’re trying to do, but in SC, it’s really not a good approach. It’s much better to put all control over rhythmic timing on the language side.

hjh

1 Like

Actually, I only just noticed the two versions of Impulse, where one was working and the other wasn’t.

But I suspect something else is going on. After modifying the envelope to make it easier to hear the onsets, and replacing SoundIn with a continuous source, Impulse.ar(Ndef(\tempo).kr / time) is triggering the envelope without any trouble.

Initially when I tried your code, indeed, I didn’t hear anything. Then, when I substituted SinOsc instead of SoundIn, then I heard a continuous tone with some amplitude modulation from the envelope. The envelope was definitely shaping the sound, but not clearly.

(
Ndef(\sound, {arg time = 1;
	var sig, env, gate;
	gate = Impulse.ar(Ndef(\tempo).kr / time);
	env = EnvGen.ar(Env([0, 0.75, 0.75, 0],[0.01, 0 , time/8], curve: \sine ), gate: gate);
	sig = SinOsc.ar(440 * 4/5)*env;
	Out.ar(0, [0, sig]);
}).play;
)

The drift between language and server timing is not very obvious within the first few minutes. I checked a recording in Audacity and the onset times are close but not quite exact. So I suspect, eventually you will need to deal with that problem.

EDIT: So I let it run for another 20 minutes or so – now, the Impulse (R) is about 24 ms earlier than the default synth (L). (Earlier, when I started testing it, the Impulse was slightly late.)

drift

hjh

1 Like

Thank you for your response! Very interesting!
I got rid of the Out UGen and then it works! But if I use a SynthDef (instead of Ndef) with an Out Ugen it does not work… Not sure why but at least the workaround is doing it

…
sig = PanAz.ar(4, sig, env)
}).play;

Hm, right, if you’re using a function with Ndef, then you shouldn’t use Out – if it’s SynthDef, then it should have an out argument to use as the bus number for Out.

I don’t know what you mean by “doesn’t work” though – in general, when discussing tech issues, it’s better to say “this is what I wanted to happen, and this is what actually happened” instead of “it doesn’t work.” The former is useful for troubleshooting while the latter is not.

Would you mind giving a little more detail on that?

hjh

Also, I found another reason for drift with Impulse.kr – if your sample rate is 44100 and block size is 64, then each second has 689.0625 blocks – but kr triggers must be on block boundaries. So Impulse.kr(1) produces a trigger every 689 * 64 = 44096 samples! Impulse.ar should not be affected by this though.

hjh

I’m leaning towards abandoning *kr, redirecting *new to *ar and saving myself a few characters…

sorry I didnt give all the information @jamshark70
Here is what I mean:

(//this works:
Ndef(\test, {|vol = 1,time = 1|
	var sig1, env, en, gate;	
	gate = Impulse.ar(Ndef(\tempo).ar/time);
	en = Env([0, 0.75, 0.75, 0],[time/2, 0 , time/2], curve: \sine );
	env = EnvGen.ar(en, gate: gate, doneAction:0);
	sig1 = Impulse.ar(50);
	sig1 = PanAz.ar(4, sig1, env);
}).play;
)

But when I use a SynthDef, I get a mono signal on the left speaker. The panning doesnt work. Am I missing something? Thank you very much for your responses!

(
SynthDef(\test, {|vol = 1,time = 1|
	var sig1, env, en, gate;
	
	gate = Impulse.ar(Ndef(\tempo).ar/time);

	//Envelope fürs panning, Signal läuft von Kanal 1/2 direkt zu 4
	en = Env([0, 0.75, 0.75, 0],[time/2, 0 , time/2], curve: \sine );
	env = EnvGen.ar(en, gate: gate, doneAction:0);
	sig1 = Impulse.ar(50);
	Out.ar(0, PanAz.ar(4, sig1, env));
}).play;
)

Oh, I see it now. It’s a very subtle distinction, which Ndef tries to keep hidden from you but which does leak out in some areas.

Ndef(\tempo).ar doesn’t actually give you just the tempo as a signal. It gives you the tempo signal wrapped in an array. The reason why is that an Ndef may represent any number of channels, and the JITLib developers felt it would be best if the output type of .ar or .kr (default behavior) is consistent. Because .ar/.kr may sometimes be an array, it should always be an array.

Normally, gate would be simply the Impulse, and this carries through as follows:

Impulse.ar(1)
-> an Impulse

EnvGen.ar(Env.new, Impulse.ar(1))
-> an EnvGen

PanAz.ar(4, SinOsc.ar, EnvGen.ar(Env.new, Impulse.ar(1)))
-> [ an OutputProxy, an OutputProxy, an OutputProxy, an OutputProxy ]

… and Out assigns the four channels to consecutive buses 0, 1, 2, 3.

You’re getting the following instead:

Impulse.ar(Ndef(\tempo).ar)
-> [ an Impulse ]

EnvGen.ar(Env.new, Impulse.ar(Ndef(\tempo).ar))
-> [ an EnvGen ]

PanAz.ar(4, SinOsc.ar, EnvGen.ar(Env.new, Impulse.ar(Ndef(\tempo).ar)))
-> [ [ an OutputProxy, an OutputProxy, an OutputProxy, an OutputProxy ] ]

… which Out treats as a single channel of four elements, producing four single-channel Out units, all targeting bus 0.

The solution is to write Ndef(\tempo).ar(1) instead, removing the array wrapping. (Now, the output type is not consistent with other cases, but you’re in control of that by passing 1 for the number of channels.)

Ndef gave you the right result without that tweak, because it “reshapes” the output array to make sense.

hjh

Btw there is no need to promote Impulse’s frequency argument to ar, just because the impulses are ar. Most UGens with a frequency argument allow a kr input for an ar unit (otherwise { |freq = 440| SinOsc.ar(freq) } would not work, a very basic case!).

Impulse.ar(Ndef(\tempo).kr(1) / time) will be fine here.

hjh

Wow! Thank you for your answers! Very informative!
Best regards!