Pdef rerun - change quant and finite Pbind simultaneously

Hello everyone,

This thread is related to this one on rerunning Pdefs at different tempi which was resolved, but bumped into the following problem since then:

I have a Pdef playing back a finite Pbind, at 120bpm. I’d like to rerun this Pdef while simultaneously changing Pbind to another finite one as well as changing the quantization.

I created several versions, none of them playback with half a beat of offset. Some play the pattern back with 120bpm at first run, some don’t. Some - but not all - only play back the pattern at 120bpm with an offset if I rerun the same line. I got lost at what is going on here, I’ve been trying to make this work for a while now, tried to follow the instructions in several tutorials, forum threads, I suspect it shouldn’t be this difficult but I seem to not find material that helps me with this. In any case, this is my take on it:

(
SynthDef("gpdef",
    { arg out=0, freq=440, sustain=0.05, amp=0.1, pan;
        var env;
        env = EnvGen.kr(Env.perc(0.01, sustain), doneAction: Done.freeSelf) * amp;
        Out.ar(out, Pan2.ar(SinOsc.ar(freq, 0, env), pan))
    }).add;
Pdef(\metronom).quant_([8,0]); // start at the 8th beat
Pdef(\x); // start at the 8th beat

// Pbinds
a = Pbind(\dur, 1, \degree, Pseq([3, 4, 5b, 6]+1, 2));
b = Pbindf(a, \instrument, \gpdef);
m = Pbind(\instrument, \gpdef, \dur, 1, \degree, Pseq([28,14,14,14,21,14,14,14],inf), \legato, 0.1);

t = TempoClock(2);
Pdef(\metronom, m).play(t);
"SynthDef + Pbinds + start metronome";
)

// play on the 8th beat, no quant offset
Pdef(\x, b).quant_([8,0]).play(t);

// change Pbind + quant offset in the same time
// v1
Pdef(\x, a).quant_([8,0.5]).play(t); // doesn't play it back at 120BPM + no offset, even when rerun
// v2
Pdef(\x).remove; "Pdef(\x) removed";
Pdef(\x); "silent Pdef(\x) started";
Pdef(\x, b).quant_([8,0]).play(t); "start default Pbind + quant values";
Pdef(\x, a).clock_(t).quant_([8,0.5]).play; // doesn't play it back at 120BPM + no offset; only if I rerun this will it be played back at 120BPM with an offset of half a beat.
// v3
Pdef(\x).remove; "Pdef(\x) removed";
Pdef(\x); "silent Pdef(\x) started";
Pdef(\x, b).quant_([8,0]).play(t); "start default Pbind + quant values";
Pdef(\x).quant_([8,0.5]).clock_(t); "assign the quant values and the clock to the Pdef only, without playing it back"
Pdef(\x,a).play; // playback at 120BPM, but no offset, only if I rerun this line.
// v4
Pdef(\x).remove; "Pdef(\x) removed";
Pdef(\x); "silent Pdef(\x) started";
Pdef(\x, b).quant_([8,0]).play(t); "start default Pbind + quant values";
Pdef(\x).clock_(t); "assign the quant values to the Pdef only, without playing it back"
Pdef(\x,a).play(quant:[8,0.5]); // playback at 120BPM, but no offset, only if I rerun this line.
// v5
Pdef(\x).remove; "Pdef(\x) removed";
Pdef(\x); "silent Pdef(\x) started";
Pdef(\x, b).quant_([8,0]).play(t); "start default Pbind + quant values";
Pdef(\x).clock_(t); "assign the quant values to the Pdef only, without playing it back"
Pdef(\x,a).quant_([8,0.5]).play; // playback at 120BPM, but no offset, only if I rerun this line.

Pdef.removeAll; "Pdefs removed";

I’d really appreciate if someone could explain why I can not rerun a Pdef with a new finite Pbind at 120bpm with an offset simultaneously.

Thank you,
cd

I had a look at this… it appears to be a bit of a hot mess (and points to reasons why I’ve avoided using Pdef for production… its ways are mysterious).

The main issue here is that a Pdef does not set its status to “not playing” when its source pattern comes to an end.

Pdef(\x, b).quant_([8,0]).play(t);

// wait for it to stop, then:
Pdef(\x).isPlaying;
-> true

It appears to me that the logic is intended to detect that it’s stopped, but that isn’t happening.

As a result, when you change its source to a different pattern, it will replay immediately, using the old quant value. The logic seems to be, the Pdef hasn’t been told to stop, so it should try to run as much as possible, which it does immediately when given a new task.

That is, in Pdef(\x, a).quant_([8, 0.5]), the order of operations is 1a/ set the source to a; 1b/ replay immediately; 2/ set the new quant (but it’s too late, because it’s already been scheduled).

So then I thought, what about changing quant first?

// for good measure, let's make sure the Pdef *really* knows its clock
Pdef(\x, b).clock_(t).quant_([8, 0]).play;

// then...
// no .play needed for the reason given above
// cumbersome syntax, but it should set quant first
Pdef(\x).quant_([8, 0.5]).source_(a);

… and I found that it does update the quant variable, but playback still uses the old one :confused:

It looks like the reason is that an EventPatternProxy has two member variables: quant and playQuant. quant can be set directly. playQuant is updated only when calling .playbut as noted above, if the Pdef is still in “playing” state, then it bypasses .play and goes to .wakeUp – and .wakeUp uses playQuant, which has not been updated.

I guess the reason might be that there’s a semantic difference between playing a Pdef fresh vs merely refreshing its source, and the assumption is that the latter operation should not change the effective quant. It all strikes me as a bit over-complex.

One solution would be to force-.stop the Pdef when the stream runs out.

(
SynthDef("gpdef",
    { arg out=0, freq=440, sustain=0.05, amp=0.1, pan;
        var env;
        env = EnvGen.kr(Env.perc(0.01, sustain), doneAction: Done.freeSelf) * amp;
        Out.ar(out, Pan2.ar(SinOsc.ar(freq, 0, env), pan))
    }).add;
Pdef(\metronom).quant_([8,0]); // start at the 8th beat
Pdef(\x); // start at the 8th beat

// Pbinds
a = Pbind(\dur, 1, \degree, Pseq([3, 4, 5b, 6]+1, 2));
b = Pbindf(a, \instrument, \gpdef);
m = Pbind(\instrument, \gpdef, \dur, 1, \degree, Pseq([28,14,14,14,21,14,14,14],inf), \legato, 0.1);

t = TempoClock(2);
Pdef(\metronom, m).play(t);
"SynthDef + Pbinds + start metronome";

~playMyPdef = { |key, pattern, quant, clock|
	if(quant.notNil) { Pdef(key).quant = quant };
	if(clock.notNil) { Pdef(key).clock = clock };
	Pdef(key,
		Pfset(nil, pattern, {
			// defer is necessary to avoid re-cleaning up from within the cleanup
			{ Pdef(key).stop }.defer
		})
	);
	if(Pdef(key).isPlaying.not) { Pdef(key).play };
};
)

~playMyPdef.(\x, b, [8, 0], t);

~playMyPdef.(\x, a, [8, 0.5]);  // now it works as you'd expect

… though nobody’s likely to stumble on that approach accidentally.

hjh

1 Like

Hello hjh,

Thank you so much for looking into this, I really appreciate it!

It seems to me that using Pbind instead of Pdef doesn’t make too much of a difference for many applications/contexts/situations I’m using it for, so - as you also noted - I might just refrain from using Pdef for now.

This of course arose for me the question of in what situation is Pdef a better choise for scheduling events!

All in all, thank you again your explanation clarified many things, getting me closer to understand scheduling events via the pattern family, which seemed a bit enigmatic to me for a while and also letting go of Pdef with understanding why is also helpful!

Best,
cd

The trick is not to call play again when the pattern is already playing, if I understand your intentions correctly:

(
SynthDef("gpdef",
    { arg out=0, freq=440, sustain=0.05, amp=0.1, pan;
        var env;
        env = EnvGen.kr(Env.perc(0.01, sustain), doneAction: Done.freeSelf) * amp;
        Out.ar(out, Pan2.ar(SinOsc.ar(freq, 0, env), pan))
    }).add;
Pdef(\metronom).quant_([8,0]); // start at the 8th beat
Pdef(\x); // start at the 8th beat

// Pbinds
a = Pbind(\dur, 1, \degree, Pseq([3, 4, 5b, 6]+1, 2));
b = Pbindf(a, \instrument, \gpdef);
m = Pbind(\instrument, \gpdef, \dur, 1, \degree, Pseq([28,14,14,14,21,14,14,14],inf), \legato, 0.1);

t = TempoClock(2);
Pdef(\metronom, m).play(t);
"SynthDef + Pbinds + start metronome";
)

// play on the 8th beat, no quant offset
Pdef(\x, b).quant_([8,0]).play(t);

// change Pbind + quant offset in the same time
// v1
Pdef(\x, a).quant_([8,0.5]); // don't call .play again