Timing Tempoclock From max/msp to sc

Hi to everyone,
In max/msp there is a way to sync audio signals and time events to a transport, https://docs.cycling74.com/max5/refpages/max-ref/transport.html
in other words to a bpm using standard durations like:

1nd - Dotted whole note - 2880 ticks
1n - Whole note - 1920 ticks
1nt - Whole note triplet - 1280 ticks
2nd - Dotted half note - 1440 ticks
2n - Half note - 960 ticks
2nt - Half note triplet - 640 ticks
4nd - Dotted quarter note - 720 ticks
4n - Quarter note - 480 ticks
4nt - Quarter note triplet - 320 ticks
8nd - Dotted eighth note - 360 ticks
8n - Eighth note - 240 ticks
8nt - Eighth note triplet - 160 ticks
16nd - Dotted sixteenth note - 180 ticks
16n - Sixteenth note - 120 ticks
16nt - Sixteenth note triplet - 80 ticks
32nd - Dotted thirty-second note - 90 ticks
32n - thirty-second note - 60 ticks
32nt - thirty-second-note triplet - 40 ticks
64nd - Dotted sixty-fourth note - 45 ticks
64n - Sixty-fourth note - 30 ticks
128n - One-hundred-twenty-eighth note - 15 ticks

It’s a really handy things when you want to sync different automations lfos… to a master bpm.
Here it’s possible to see a general example with a phasor~
https://docs.cycling74.com/max5/refpages/msp-ref/phasor~.html

My question is:
Is there something similar in SC in the context of
Tempoclock?

Hope the question is clear.
Thanks

Well, when you play a Pbind ( or a Routine ) to a TempoClock, you are already talking in beats. TempoClock’s Default tempo is 1, which is 1bps = 60bpm, where 1 beat = 1 second. So you can have:

6 - Dotted whole note
4 - Whole note
8/3 - Whole note triplet
3 - Dotted half note
2 - Half note
4/3 - Half note triplet
1.5 - Dotted quarter note
1 - Quarter note
2/3 - Quarter note triplet
0.75 - Dotted eighth note
0.5 - Eighth note
1/3 - Eighth note triplet
0.375 - Dotted sixteenth note
0.25 - Sixteenth note
1/6 - Sixteenth note triplet
…

Then, to sync different patterns to the same clock, you can check out Quant

I think this is the preferred way to sync different processes: syncing patterns on the language side. Synchronization is a hot topic, you’ll find a lot of discussions about it here on the forum, for example see this:

1 Like

The language TempoClock runs in beats. The server runs in seconds. To run an LFO in tempo, you have to convert beats to seconds:

seconds = beats / tempo
beats = seconds * tempo

lfoFreq = cyclesPerBeat * tempo

Over long periods of time, language and server timing may drift.

Btw if you want to sync SC and Max (same machine or different machines)… SC 3.11 is due in a couple months and it will include Ableton Link.

hjh

2 Likes

Hello elgiano,
I know it’s possible to talk in beats and usually I have something siimilar to this code:

// metronome
(
~bpm = 120;
~beatsPerBar = 4;
~secondPerBar= (60/~bpm)*~beatsPerBar;
"Bar dur: ".post;~secondPerBar.postln;
)
// Initialization metronome

// metronome base
(
~metroBase = TempoClock.new;
~metroBase.tempo = ~bpm/60;
~metroBase.permanent_(true);
~metroBase.schedAbs(~metroBase.nextBar,{~metroBase.beatsPerBar_(~beatsPerBar)});
)
~metroBase.tempo.postln

// Test BaseMetro
(
"Beats per bar,
Metro base".postln;~metroBase.beatsPerBar.postln;
"Beat at the beginning of the next bar, metronome  base ".postln;~metroBase.nextBar.postln;
)

// Activating and Deactivating Metronome Base  

//  vedi
s.boot;
(
s.plotTree;
s.meter;
s.scope
)

//synth
(
SynthDef(\clickPing, { |phase = 0, amp = 0.25|
	var silence, s,r;
	s = Impulse.ar(0);
	r = Ringz.ar(s,2000,0.2,3,0);
	silence = DetectSilence.ar(r,time:0.1,doneAction:2);
	Out.ar(0,r*amp);
}).add;
)

(
~playMetroBase = Pbind(
	\instrument, \clickPing,
	\stretch,1,
	\amp,0.1,
	\dur,(~metroBase.tempo) // bar
	).play(~metroBase,quant:~metroBase.beatsPerBar);
)

~playMetroBase.stop;
~playMetroBase.play;

(

SynthDef(\element2, { |phase = 0, amp = 0.25|
var silence, s,r;
r = Pan2.ar(Impulse.ar(0),0);
silence = DetectSilence.ar(r,time:0.1,doneAction:2);
Out.ar(0,r*amp);
}).add;
)

//  40/16
~playElement2 = Pbind(
	\instrument, \element2,
	\stretch, 4,
	\amp,0.4,
	\dur, Pseq([
		1/16,
		3/16,
		1/16,
		2/16,
		3/16,
		1/16,
		2/16,
		1/16,
		3/16,
		4/16, //1
		1/16,
		1/16,
		4/16, //2
		1/16,
		3/16,
		3/16,
		1/16,
		4/16, //3
		1/16 // 19 elements
	],inf)
	)
)
~playElement2.play(~metroBase,quant:~metroBase.beatsPerBar);

it depends what it means over long periods…

Anyway here I tryied to make a beating in sync

    // metronome
    (
    ~bpm = 120;
    ~beatsPerBar = 4;
    ~secondPerBar= (60/~bpm)*~beatsPerBar;
    "Bar dur: ".post;~secondPerBar.postln;
    )
    // Initialization metronome

    // metronome base
    (
    ~metroBase = TempoClock.new;
    ~metroBase.tempo = ~bpm/60;
    ~metroBase.permanent_(true);
    ~metroBase.schedAbs(~metroBase.nextBar,{~metroBase.beatsPerBar_(~beatsPerBar)});
    )
//synth
(
SynthDef(\clickPing, { |phase = 0, amp = 0.25|
	var silence, s,r;
	s = Impulse.ar(0);
	r = Ringz.ar(s,2000,0.2,3,0);
	silence = DetectSilence.ar(r,time:0.1,doneAction:2);
	Out.ar(0,r*amp);
}).add;
)

(
~playMetroBase = Pbind(
	\instrument, \clickPing,
	\stretch,1,
	\amp,0.1,
	\dur,(~metroBase.tempo) // bar
	).play(~metroBase,quant:~metroBase.beatsPerBar);
)

~playMetroBase.stop;
~playMetroBase.play;

(
SynthDef(\f, { |out=0,vFreq=440,cyclesPerBeat  =4,tempO =1,amp|
	var sig =

	SinOsc.ar(freq:vFreq,mul:0.125)
	+
    SinOsc.ar(freq:vFreq +(cyclesPerBeat * tempO),mul:0.125);
	sig = Pan2.ar(sig,0,amp);

	sig = HPF.ar(sig,400);
    Out.ar(out,sig);
}).add;
)

a = Synth(\f);
a.free;

// run me multiple times
(
r = Routine({
	a.set(\vFreq, 53.midicps,
		\cyclesPerBeat, rrand(1,9).asInteger,
		\tempO,~metroBase.tempo,
		\amp,0.5	);
	nil;
	}
);
// run the routine at the next bar
r.play(clock:~metroBase, quant:[~metroBase.nextBar,0]);
)

Do you think is it correct in this way the sync part?
I honestly can’t say if it is or not …

Thanks again

it sounds very interesting :slightly_smiling_face:

It looks basically OK to me.

If you want different layers to be in sync, then they should be following the same time base. Language-side TempoClocks may have different tempi, but they all follow the same underlying system clock – so these are one time base.

(
{
	var now, t_end, u_end,
	cond = Condition({ t_end.notNil and: { u_end.notNil } });
	
	t = TempoClock(1);
	u = TempoClock(3/4);
	
	now = SystemClock.seconds;
	
	t.play(Routine {
		4.do { 1.wait };
		t_end = SystemClock.seconds;  // 4 beats later
		defer { t.stop };
		cond.signal;
	});
	
	u.play(Routine {
		3.do { 1.wait };
		u_end = SystemClock.seconds;  // 3 beats later
		defer { u.stop };
		cond.signal;
	});
	
	cond.wait;
	
	([t_end, u_end] - now).postln;
}.fork(SystemClock);
)

-> a Routine
[ 4.0, 4.0 ]

^^ So, 4 beats at 60 bpm and 3 beats at 45 bpm end up being exactly the same amount of time – they are correctly locked together.

The server does not follow the same time base. So, you can’t assume that language-scheduled synths or set messages will be sample accurate in the server. They will be synced relative to each other, but not perfectly synced to audio samples.

So, in your example, the two patterns will run in sync on the language side. But you can’t be sure that the set statements for Synth(\f) will always match the oscillator cycles. Close enough for most purposes.

(Note also, if you use Ableton Link, then the language-side timing undergoes some slight adjustments to match other Link clients – so a LinkClock's timebase can’t be assumed to be locked into other TempoClocks. With Link, you get good sync with other software, even on other machines, but you lose a small amount of local accuracy.)

hjh

1 Like