TempoClock, quant, timing issues


#1

Hi guys,
I’ve a question related to timing, tempo clock and quantization.
In order to figure out the issue myself and to be better understand I’ve prepared a sort of an experiment in order to see if you can replicate such behaviour (at the end of the post you will find the complete code in order for an easier copy-paste).

Let’s start with the experiment!

Start the server

s.boot;

and also start the “metronome task”: an auto-sceduling task which will, at each new bar, print the current bar and beats number.

TempoClock.play({|beats, time, clock| ("bar: " ++(beats/4) ++ ", beats: " ++ beats).postln; 4}, quant:4);
});

You should see its output on the console every 4 secs (with 60 BPM and 4/4, the defaults).
Then evaluate these simple synth definitions:

(
SynthDef(\pulse, {
		|freq=440, amp=1|
		var env = EnvGen.ar(Env.perc(0.01, 0.5), doneAction:2);
		var sig = LFPulse.ar(freq) * env * amp;
		Out.ar(0, Pan2.ar(sig, 0.0));
};).add;

SynthDef(\saw, {
	|freq=440, amp=1, gate=1|
	var env = EnvGen.ar(Env.adsr(0.01, 0.01, 0.8, 1), gate, doneAction:2);
	var sig = LPF.ar(LFSaw.ar(freq), 500) * env * amp;
	Out.ar(0,Pan2.ar(sig, 0.0));
}).add;
)

Define some musical phrases to be used as a timing tests. These Pbinds represent these three bars:

As you can see, on bar A only the bass is playing with a quarter note on the first musical accent.
In bar B the pad enters with a C major chord while the bass plays a richer melody.
In bar C pad changes its chords creating a I-V-IV progression while the bass also adds some new elements.

(
~bass_barA = Pbind(
	\instrument, \pulse,
	\scale, Scale.major,
	\root, 0, \octave, 3,
	\degree, Pseq([0],inf),
	\amp, 0.5,
	\dur, Pseq([4], inf),
);

~bass_barB = Pbind(
	\instrument, \pulse,
	\scale, Scale.major,
	\root, 0, \octave, 3,
	\degree, Pseq([0,7,0,0],inf),
	\amp, Pseq([0.5, 0.3, 0.5, 0.25], inf),
	\dur, Pseq([0.5, 1, 0.5, 2], inf),
);

~bass_barC = Pbind(
    \instrument, \pulse,
	\scale, Scale.major,
	\root, 0, \octave, 3,
	\degree, Pseq([7,7,7,0,0,4,7],inf),
	\amp, Pseq([0.5, 0.33, 0.4, 0.25, 0.25], inf),
	\dur, Pseq([0.25,0.25, 1, 0.5, 1, 0.5, 0.5], inf),
);

~pads_barB = Pbind(
	\instrument, \saw,
	\scale, Scale.major,
	\root, 0, \octave, 4,
	\degree, Pseq([[0, 2, 4]],inf),
	\amp, 0.3,
	\dur, Pseq([4], inf)
);

~pads_barC = Pbind(
	\instrument, \saw,
	\scale, Scale.major,
	\root, 0, \octave, 4,
	\degree, Pseq([[0,2,4], [1,4,6], [0,3,5]],inf),
	\amp, 0.3,
	\dur, Pseq([2, 1, 1], inf),
);
)

Now let’s start testing. Evaluate the following line:

Pdef(\bass, ~bass_barA).play(quant:4);

What do you obtain? Are you listening to a bass note every time the console prints a new bars/beat message?
I’m seeing exactly this: every time a new line appears on the console (which means we are just entered a brand new bar), the bass plays its quarter note on the first accent of the bar as stated in ~bass_barA musical phrase.

Now, lets move on the the other bars, evaluate bar B, listen carefully:

(
Pdef(\bass, ~bass_barB).play(quant:4);
Pdef(\pads, ~pads_barB).play(quant:4);
)

Are you listening to this?

move on, now evaluate bar C

(
Pdef(\bass, ~bass_barC).play(quant:4);
Pdef(\pads, ~pads_barC).play(quant:4);
)

Are you listening to this?

Back again to bar A

(
Pdef(\bass, ~bass_barA).play(quant:4);
Pdef(\pads).stop;
)

Did you hear any strange misalignment between the bass and the pads?
Any misalignment between the two instruments and (what I suppose is a precise clock) the “metronome task”?

Don’t stop the sound, continue playing. Try evaluating bar A, B, C again, in different order.
If I’m right, you will experience such misalignment.

Question is: why?

Maybe I haven’t correctly understood the use of the keyword “quant” but, using a value of 4 for it, shouldn’t it mean bass and pads musical phrases should start at the beginning of every new bar (which corresponds to the exact moment a new line is printed to the console by the “metronome task”)?
Is here something I’m missing?

I wish I was able to explain the issue.
Thank you so much for your help!

In case you are tired, stop the sound.

(
Pdef(\bass).stop;
Pdef(\pads).stop;
)

Here the complete code to make it easier to copy and paste:

// start the server
s.boot;
// and also start the "metronome task": an auto-sceduling task
// which will, at each new bar, print the current bar and beats number.
TempoClock.play({|beats, time, clock| ("bar: " ++(beats/4) ++ ", beats: " ++ beats).postln; 4}, quant:4);
});
// you should see its output on the console every 4 secs
// (using the default 60 BPM and 4/4)

// Then evaluate these simple synth definitions
(
SynthDef(\pulse, {
		|freq=440, amp=1|
		var env = EnvGen.ar(Env.perc(0.01, 0.5), doneAction:2);
		var sig = LFPulse.ar(freq) * env * amp;
		Out.ar(0, Pan2.ar(sig, 0.0));
};).add;

SynthDef(\saw, {
	|freq=440, amp=1, gate=1|
	var env = EnvGen.ar(Env.adsr(0.01, 0.01, 0.8, 1), gate, doneAction:2);
	var sig = LPF.ar(LFSaw.ar(freq), 500) * env * amp;
	Out.ar(0,Pan2.ar(sig, 0.0));
}).add;
)

// Define some musical phrases to be used as a timing tests.
// These Pbinds represent these three bars:
// As you see, on bar A only the bass is playing with a quater note on the
// first musical accent.
// In bar B the pad enters with a C major chord while the bass plays a
// richer melody.
// In bar C pad changes its chords creating a I-V-IV progression while
// the bass also adds some new element.

(
~bass_barA = Pbind(
	\instrument, \pulse,
	\scale, Scale.major,
	\root, 0, \octave, 3,
	\degree, Pseq([0],inf),
	\amp, 0.5,
	\dur, Pseq([4], inf),
);

~bass_barB = Pbind(
	\instrument, \pulse,
	\scale, Scale.major,
	\root, 0, \octave, 3,
	\degree, Pseq([0,7,0,0],inf),
	\amp, Pseq([0.5, 0.3, 0.5, 0.25], inf),
	\dur, Pseq([0.5, 1, 0.5, 2], inf),
);

~bass_barC = Pbind(
    \instrument, \pulse,
	\scale, Scale.major,
	\root, 0, \octave, 3,
	\degree, Pseq([7,7,7,0,0,4,7],inf),
	\amp, Pseq([0.5, 0.33, 0.4, 0.25, 0.25], inf),
	\dur, Pseq([0.25,0.25, 1, 0.5, 1, 0.5, 0.5], inf),
);

~pads_barB = Pbind(
	\instrument, \saw,
	\scale, Scale.major,
	\root, 0, \octave, 4,
	\degree, Pseq([[0, 2, 4]],inf),
	\amp, 0.3,
	\dur, Pseq([4], inf)
);

~pads_barC = Pbind(
	\instrument, \saw,
	\scale, Scale.major,
	\root, 0, \octave, 4,
	\degree, Pseq([[0,2,4], [1,4,6], [0,3,5]],inf),
	\amp, 0.3,
	\dur, Pseq([2, 1, 1], inf),
);
)

// Now lets start testing.

// Evaluate the following line:
Pdef(\bass, ~bass_barA).play(quant:4);

// What do you obtain? Are you listening to a bass note every time
// the console prints a new bars/beat message?
// I'm seeing exactly this: every new line is appearing in the console
// (which means we are just entered a brand new bar)
// the bass plays its quarter note on the first accent of the bar
// as stated in ~bass_barA musical phrase.

// Now, lets move on the the other bars

// evaluate bar B, listen carefully
(
Pdef(\bass, ~bass_barB).play(quant:4);
Pdef(\pads, ~pads_barB).play(quant:4);
)

// move on, now evaluate bar C
(
Pdef(\bass, ~bass_barC).play(quant:4);
Pdef(\pads, ~pads_barC).play(quant:4);
)

// back again to bar A
(
Pdef(\bass, ~bass_barA).play(quant:4);
Pdef(\pads).stop;
)

// Did you hear any strange misalignemet between the bass and the pads?
// Any misalignemet between the two instrument and (what I suppose is a precise clock)
// the "metronome task"?

// Don't stop the sound, continue playing. Try evaluating bar A, B, C again, in different order.
// If I'm right, you will experience such misalignement.

// Question is: why?
// Maybe I've not correctly undertood the use of the keyword "quant" but, using a value of 4 for it, shouldn't mean bass and pads musical phrases should start at the beginning of every new bar (which corresponds to the exact moment a new line is printed to the console by the "metronome task")?
// Is here something I'm missing?


// in case you are tired, stop the sound.
(
Pdef(\bass).stop;
Pdef(\pads).stop;
)

#2

See Julian’s answer in this thread. Pdef has an own quant slot, I think that should solve the issue (untested).

https://www.listarc.bham.ac.uk/lists/sc-users/msg64131.html

for reference after 2019 take this link:

https://www.listarc.bham.ac.uk/lists/sc-users-2019/msg64131.html


#3

Hi @dkmayer and thanks for you reply.
Did you tried my code test? Have you obtained some different results?

Back to your reply: uhm, don’t know if it is exactly my case. Anyway, after reading it, I made another test: I’ve changed the default quantization for Pdefs:

Pdef.defaultQuant = 4;

I’ve noticed that this way, every time I launch a new “bar clip”, it will start exactly where I’m expecting (which is a different behaviors from before).

It seems the argument quant, used when the play message is sent to the Pdefs, is not working or, at least, not working every time.

What’s the difference between the two pieces of code below?
Why their musical output are different?
It should not be assumed that they will return the same output?

This is the code that is not working for me:

Pdef(\bass, ~bass_barA).play(quant:4);

(
Pdef(\bass, ~bass_barB).play(quant:4);
Pdef(\pads, ~pads_barB).play(quant:4);
)

(
Pdef(\bass, ~bass_barC).play(quant:4);
Pdef(\pads, ~pads_barC).play(quant:4);
)

(
Pdef(\bass, ~bass_barA).play(quant:4);
Pdef(\pads).stop;
)

this is the code that is working:

Pdef.defaultQuant = 4;
Pdef(\bass, ~bass_barA).play();

(
Pdef(\bass, ~bass_barB).play();
Pdef(\pads, ~pads_barB).play();
)

(
Pdef(\bass, ~bass_barC).play();
Pdef(\pads, ~pads_barC).play();
)

(
Pdef(\bass, ~bass_barA).play();
Pdef(\pads).stop;
)

Why?

Please don’t misunderstand me, what I’m experiencing are not subtle latencies or misalignments that are almost imperceptible.
Bass and pads are always syncronized with the metronome, but sometimes one or both of them go off tempo. They loose their alignement in terms of quarter notes so that, for example, bass notes are not musically coherent with the chords.

What is wrong with the quant argument method ( Pdef(\bass).play(quant:4); )?
If is there a problem with it, it might be a pity because it means we are forced to use the default quantization for Pdefs (it seems to work), and we no longer have the ability to assign different quantization to different Pdefs :frowning:

Any idea?
thanks so much


#4
  1. I can’t reproduce the problem. If I do this:

    Pdef(\four, Pbind(\degree, Pn(Pseries(0, 1, 4), inf), \dur, 1)).play(quant: 4);
    

    … then it starts on a barline, not on the next beat. (I’m on 3.10.2, which version for you? Maybe it’s something that was fixed recently? Or maybe you have an extension that breaks it?)

  2. You aren’t forced to use defaultQuant. Every Pdef has its own independent quant property: Pdef(\xyz).quant = whateverYouWantAndItCanBeDifferentFromOtherPdefs.

Here’s the relevant code btw (EventPatternProxy:play):

	play { arg argClock, protoEvent, quant, doReset=false;
		// here, it prefers the argument 'quant' over the Pdef's property
		playQuant = quant ? this.quant;
		if(player.isNil) {
			player = EventStreamPlayer(this.asProtected.asStream, protoEvent);
			player.play(argClock, doReset, playQuant);
		...
	}

hjh


#5

hi @jamshark70,
thanks for you reply and suggestions.

The SC version I’m using is 3.10.0.

I’ve tried this:

s.boot;

TempoClock.play({|beats, time, clock| ("bar: " ++(beats/4) ++ ", beats: " ++ beats).postln; 4}, quant:4);

// bar D
(
Pdef(\four, Pbind(\degree, Pn(Pseries(0, 1, 4), inf), \dur, 1)).play(quant: 4);
Pdef(\two, Pbind(\degree, Pseq([2, 4],inf), \dur, 2)).play(quant: 4);
)

// bar E
(
Pdef(\four, Pbind(\degree, Pn(Pseries(7, -1, 4), inf), \dur, 1)).play(quant: 4);
Pdef(\two, Pbind(\degree, Pseq([2, 4],inf), \dur, 2)).play(quant: 4);
)

The problem is still there (try to evaluate bar D and bar E multiple times, launching them at different times) but it goes away if I evaluate:

Pdef.defaultQuant = 4;

or

Pdef(\four).quant = 4;
Pdef(\two).quant = 4;

Can you try and let me know if you are experiencing something similar?
it seems to me that there’s some problem with … play(quant:4)


#6

I don’t know if I missed something, but surely if you use the trigger_(notation) it works? I tried your original code and see/hear exactly what you mean. I tried this and it worked fine, try evaluating anywhere/beat and it will start/continue from the beginning of the next bar(*):

// Now lets start testing.

// Evaluate the following line:
Pdef(\bass, ~bass_barA).play.quant_(4);
   
// Now, lets move on the the other bars

// evaluate bar B, listen carefully
 (
    Pdef(\bass, ~bass_barB).play.quant_(4);
    Pdef(\pads, ~pads_barB).play.quant_(4);
    )

// move on, now evaluate bar C
(
Pdef(\bass, ~bass_barC).play.quant_(4);
Pdef(\pads, ~pads_barC).play.quant_(4);
)

// back again to bar A
(
Pdef(\bass, ~bass_barA).play.quant_(4);
Pdef(\pads).stop;
)

And if you need to link to another clock use .play(t).quant_(x).

(*) I’m sure someone told me this years ago (on the list), or maybe am I dreaming?


#7

Aha, OK, I understand now. The problem is not actually with playing at all.

TL;DR you do have to use Pdef(\name).quant = 4 for this – because, when you change the Pdef’s content, the play method is irrelevant. It occurs after changing the contents, so the argument to play cannot retroactively affect an operation that has already happened.

More:

I think you might be assuming that you’re doing one operation (play), but in fact there are two (new and play).

Pdef(\four, Pbind(\degree, Pn(Pseries(0, 1, 4), inf), \dur, 1));  // A.
Pdef(\four).play(quant: 4);  // B.

Pdef(\four, Pbind(\degree, Pn(Pseries(7, -1, 4), inf), \dur, 1));  // C.
Pdef(\four).play(quant: 4);  // D.

A: Pdef’s *new method does two different things:

  • If you call it with only a name, it returns the existing Pdef (but changes nothing).
  • If you call it with a name and a pattern, it replaces the Pdef’s source pattern and returns the Pdef.

Now, note at this point that there is no quant argument for Pdef *new. The only information about metrical quant available to the Pdef is the Pdef’s own quant parameter, or defaultQuant. If either of these is set, then the pattern will be replaced on the quant boundary. If not set, the change takes place immediately – even in the middle of the bar (as in “C”).

B: If the Pdef is not playing, then it will start playing (using the quant argument if available, falling back to the Pdef’s quant or defaultQuant). The quant argument, following convention, applies only to this play call – it was never meant to set a property permanently.

If the Pdef is already playing, then what should play do?

Answer: Nothing. It should not change the state of the object in any way. (Well, one exception, it could obey the doReset flag… but otherwise, it will just keep going.)

You are assuming, at “D,” that the quant provided to play will control the pattern-replacement behavior at C. But C is already over and done. So this is impossible. It doesn’t matter that you wrote them on the same line. They are two different method calls, two different operations, and the first one finishes before the second one takes place. The second one cannot go back and modify how the first one behaves.

So you have to set the quant property on the Pdef object.

hjh


#8

Thank you all @joesh, @jamshark70 and @dkmayer.
I definetely understood the point.
To me, it was an extremely interesting discussion we had.
There’s always something new to learn about SC and the nice thing is to learn together by comparing ourselves like we did here. Thanks a lot!