Stretching a Pspawner (delta is not stretched)

I don’t know if this is a bug, or (more likely) me misunderstanding something. I am using Pspawner and I want to “stretch” the whole result to slow it down or speed it up. Here is a simple example that shows the problem:

p = Pbind(\degree, Pseq((0..3)), \dur, 0.5);
// Play the original
p.trace.play;

// This works as expected (plays a slower version):
Pmul(\stretch, 4, p).trace.play;

// This stretches the played durations but sp.seq seems to set
// an explicit 'delta', which is not stretched, so the notes
// still play at the original speed
Pmul(\stretch, 4, Pspawner({|sp| sp.seq(p) })).trace.play

This seems to be an issue with the delta key, which is not stretched when it’s been explicitly set (Pspawner seems to do that). In the regular Pbind case, delta isn’t explicitly set, so it gets evaluated – and that takes into account the stretch key.

Thanks,
Glen.

BTW if I move the Pmul inside, it works as expected:

Pspawner({|sp| sp.seq(Pmul(\stretch, 4, p)) }).trace.play

However, in general I will have a complex routine inside the Pspawner, and I don’t want to have to insert Pmul to the argument of each p.seq and p.par… Is there a way I can do what I want from the “outside”? E.g., could I effectively multiply the rate of the clock when it’s used inside this Spawner, that would affect both Event onsets and durations?

I can also get the effect I want here by chaining an Event with the stretch in it.

(Pspawner({|sp| sp.seq(p) }) <> (stretch: 4)).trace.play

But again, in general, the patterns may already be composed of different stretch keys, so I really want to multiply the existing ones (to stretch the result), not set them all to a fixed value.

You could multiply durations with an extra

Pfunc { ~stretch }

If it’s feasible to define subpatterns outside the Pspawner the effort is not so big:

(
p = Pbind(\dur, 0.4, \midinote, Pxrand((45..54), 3) + [0, 7]);
q = Pbind(\dur, 0.3, \midinote, Pxrand((73..82), 3) + [0, 4]);
r = Pbind(\dur, 0.2, \midinote, Pxrand((87..96), 3) + [0, 3]);

x = [p, q, r].collect { |x| Pmul(\dur, Pfunc { ~stretch }, x) };

~stretch = 1;

u = Pspawner {|sp|
	loop {
		sp.par(x[0]);
		0.5.wait;
		sp.par(x[1]);
		0.5.wait;
		sp.par(x[2]);
		0.5.wait
	}
}.play
)		

~stretch = 0.5;

~stretch = 1.5;

~stretch = 0.1;

u.stop

Other than that you could define an event type that does the “meta stretch”. I suppose an event of that type could be chained with the Pspawner, have not checked though.

That can be combined with standard stretch, e.g.:

p = Pbind(\dur, 0.2, \midinote, Pxrand((45..54), 3) + [0, 7], \stretch, 2);
q = Pbind(\dur, 0.2, \midinote, Pxrand((73..82), 3) + [0, 4], \stretch, 1.5);
r = Pbind(\dur, 0.2, \midinote, Pxrand((87..96), 3) + [0, 3], \stretch, 1);

x = [p, q, r].collect { |x| Pmul(\dur, Pfunc { ~stretch }, x) };

...

Just to mention the simple solution: taking a dedicated TempoClock and set its tempo – if it makes sense in your use case. The temptating alternative – setting the tempo key in the Event – is bad, because this sets the default TempoClock and can lead to confusion.

Thanks, those are helpful things to try, although they are a bit intrusive if you have a complex routine (and the TempoClock solution doesn’t really work well unless maybe I could have it synced to the default clock, with a fixed multiplier).

Does this mean you think the existing Pspawner behaviour in this case is correct? Or that it couldn’t be “fixed” by an overload/replacement class that would take stretch into account when calculating the deltas – I made some naïve attempts (overloading Spawner.embedInStream) but haven’t been able to get it to change the timing correctly so far…

If you mean this …

… as a matter of fact, not all pattern classes can be combined in a way that they act as one might assume, dozens of such cases have been reported over the years. I’m sure many rare combos have never been tested by anyone at all – you might well have been the first one to wrap a Pspawner into a Pmul.

I might be wrong, but AFAICS Spawner is using its priority queue based on times, Pmul can’t change what already has been used. This looks a bit similiar to the situation with Ppar / Ptpar that cannot be taken as input to many Patterns.

Try wrapping the Pspawner in Pstretch instead.

hjh

1 Like

Ah, thank you! Seems so obvious (but undocumented)… This does indeed seem to work exactly as I need.

It still would be nice to have a way to “modify” the clock (e.g. apply a scaling factor to it) when playing a pattern, or a dependent clock that remains synced to the default TempoClock, but with a tempo multiplier on it (is there a way to keep such a thing in sync?). Because the Pstretch class actually modifies time, it means you can’t just “speed up” or slow down patterns that use time, without modifying their behaviour. (Sometimes you may want that, but other times I want them just to be sped up or slowed down, as if played on a different-speed – but synchronized – clock.)

(
p = Pbind(
	\degree, Pseq((0..3), 4),
	\dur, 0.25,
	\amp, (Ptime() / 2).wrap(0,1),
	\pan, Pstep([0, -1, 1], [1.5, 0.25, 0.25], inf)
);
// Play a pattern that uses some time-dependent value patterns
p.trace.play
)

// Now, try stretching it...the behaviour is not just a slower version;
// the values for amp and pan are different from the original,
// because the incoming time is now different.
Pstretch(4, p).trace.play

// What I would actually like would be something like:
~barClock = TempoClock(TempoClock.tempo / TempoClock.beatsPerBar, TempoClock.beats);
p.trace.play(~barClock)

// Except that ~barClock would stay in sync with TempoClock.default,
// i.e. when the default tempo changes, it would update to be a scaled version of it.

If time could be “remapped” like this, it would solve this problem for me, as well as the original Pspawner issue (which, at least, is fixed by Pstretch when there is are no time-dependent pattern keys in its inner patterns).

This isn’t well-documented, but: A TempoClock broadcasts tempo changes. You can use this to sync tempo changes on other clocks.

TempoClock.schedAbs(TempoClock.nextBar, {
	~barClock = TempoClock(TempoClock.tempo / 4);
});

TempoClock.default.gui;
~barClock.gui;

~updater = SimpleController(TempoClock.default)
.put(\tempo, { ~barClock.tempo = TempoClock.tempo / TempoClock.beatsPerBar });

TempoClock.tempo = 2;

TempoClock.tempo = 1.5;

(For that matter, dependencies in general are not well-documented… but they are extremely useful. Many problems can be solved easily using the Observer pattern, which may be hard to solve by other, more “obvious” means.)

hjh

2 Likes

Thanks again! :+1:

This is great; for example, I was able to prototype a “bar time clock” that stays in sync with the regular TempoClock (unless you go and explicitly change its tempo or beats per bar)… In case anyone is interested:

(
var resetTempoFrom = { |primaryClock|
	~barClock.tempo = primaryClock.tempo / primaryClock.beatsPerBar
};

var syncMeterAndBeatsFrom = { |clock, primaryClock|
	clock.schedAbs(clock.beats.ceil, {
		clock.beatsPerBar = 1;
		clock.beats = clock.bars2beats(primaryClock.beats2bars(primaryClock.beats));
	});
};

~syncBarClock = {
	var primaryClock = TempoClock.default;
	primaryClock.schedAbs(primaryClock.nextBar, {
		resetTempoFrom.value(primaryClock);
		if (~barClock.beatsPerBar != 1) {
			syncMeterAndBeatsFrom.value(~barClock, primaryClock);
		} {
			~barClock.beats = ~barClock.bars2beats(primaryClock.beats2bars(primaryClock.beats));
		}
	});
};

TempoClock.schedAbs(TempoClock.nextBar, {
	~barClock = TempoClock(TempoClock.tempo / TempoClock.beatsPerBar).permanent_(true);
	syncMeterAndBeatsFrom.value(~barClock, TempoClock.default);
	~tempoControllers = [
		SimpleController(TempoClock.default).put(\tempo, resetTempoFrom),
		SimpleController(TempoClock.default).put(\meter, resetTempoFrom)
	];
});

)

(
TempoClock.default.gui.w.alwaysOnTop_(true);
~barClock.gui.w.alwaysOnTop_(true).bounds = Rect(600, 20, 572, 89);
)

You can change the tempo or meter of the default clock and the “bar clock” will stay in sync:

TempoClock.tempo = 2;

TempoClock.tempo = 1.5;

TempoClock.schedAbs(TempoClock.nextBar, { TempoClock.beatsPerBar = 3});

If you adjust the tempo of the “driven” clock accidentally, you can get it back in sync by running:

~syncBarClock.value

(it may require time to “catch up” if you ran it ahead)

Thanks again for the pointer, @jamshark70!