Pattern Ppar doesn't changed duration as expected

I am new to Patterns in SC.
My aim is to define a Pattern and to use it at different speed within other patterns.
I expected the following pattern to output a steady pulse.
It seems that Ppar doesn’t deal with duration and stretch as expected.
What would be the appropriate approach?

(
n=Ndef(\test,{
	// Poll.kr(\trig.tr(1),\dur.kr(0),\dur);
	// Poll.kr(\trig.tr(1),\stretch.kr(0),\stretch);
	Poll.kr(\trig.tr(1),Timer.kr(\trig.tr(1)),\timer);
        K2A.ar(\trig.tr(1));
}).play;
n=Ndef(\test2,{
	// Poll.kr(\trig.tr(0),\dur.kr(0),\dur2);
	// Poll.kr(\trig.tr(0),\stretch.kr(0),\stretch2);
	// Poll.kr(\trig.tr(0),Timer.kr(\trig.tr(0)),\timer2);
}).play;
b=Pbind(*[dur:Pseq([1],1),id: Ndef(\test).nodeID,instrument: \test,type:\set, args: #[\stretch,\trig,\dur]]);
c=Pbind(*[dur:Pseq([1],1),id: Ndef(\test2).nodeID,instrument: \test,type:\set, args: #[\stretch,\trig,\dur]]);
Pseq([Pseq([Ppar([b,c]),b],2).stretch(1/2),Pseq([Ppar([b,c]),b],2).scaleDur(1/2)]).play
)

but it’s output is:

timer: 0.333333
timer: 0.176
timer: 0.333333
timer: 0.176
timer: 0.353333
timer: 0.176
timer: 0.353333
timer: 0.176
timer: 0.353333

Here is a way to easily reuse the same base pattern with different dur values. This of course is a simple example, the base pattern could be anything you want.

Pdef.clear

t = TempoClock(1); // ie, 60 BPM.
Pdef(\basePattern, Pbind(\degree, Pseq([0, 2, 1, 4, 5] - 12, inf)));

/// Play the base pattern. No dur value is specified so the default value of 1 is used, ie. 1 beat per second. 

Pdef(\basePattern).play(t)
Pdef(\basePattern).stop

 

/// Now we can override the dur value of the base pattern to play the base pattern at different speeds. Start one by one or all at the same time.

Pdef(\pat1, Pchain((\dur: 0.25, \octave: 5), Pdef(\basePattern))).play(t); 
Pdef(\pat2, Pchain((\dur: 0.5, \octave: 6), Pdef(\basePattern))).play(t);
Pdef(\pat3, Pchain((\dur: 1/3, \octave: 7), Pdef(\basePattern))).play(t);


/// Change while playing , one by one or all at the same time

Pdef(\pat1, Pchain((\dur: 1, \octave: 5), Pdef(\basePattern)));
Pdef(\pat2, Pchain((\dur: 1.5, \octave: 6), Pdef(\basePattern)));
Pdef(\pat3, Pchain(Pbind(\dur, Pseq([2/3, 1/3], inf), \octave, 7), Pdef(\basePattern)));

/// change tempo: The above Pdefs are all playing on the TempoClock 't'. No quant values were given in which case the default quant value of 1 is used. This means everything is synchronized to the same beat. You can change the tempo while playing, everything will still be in sync. 

t.tempo = 1.5;
t.tempo = 2;
t.tempo = 1;

/// Change base pattern

Pdef(\basePattern, Pbind(\degree, Pseq([0, 6, 5, -2, -5] - 12, inf)));

Pdef(\pat1).stop;
Pdef(\pat2).stop;
Pdef(\pat3).stop;

Pdef.clear
1 Like

Short answer is that .stretch and .scaleDur should not be used after Ppar (though they may be used on the patterns within Ppar).

You may use Pstretch(factor, Ppar(...)) instead.

Background (which I’m writing out in advance because my time is OK right now, but limited later – hoping this will reduce the number of follow-up questions, and help others to take on those questions instead of myself):

Ppar must override events’ delta values.

(
p = Ppar([
	Pbind(\a, 1, \dur, 1),
	Pbind(\a, 2, \dur, 0.5)
]).asStream;
)

p.next(());
-> ( 'delta': 0.0, 'a': 1, 'dur': 1 )

p.next(());
-> ( 'delta': 0.5, 'a': 2, 'dur': 0.5 )

p.next(());
-> ( 'delta': 0.5, 'a': 2, 'dur': 0.5 )

p.next(());
-> ( 'delta': 0.0, 'a': 1, 'dur': 1 )

\dur is as given in the source patterns – it has to stay this way, so that \sustain can be calculated correctly as \dur * \legato * \stretch. But \delta is “tweaked” to reflect the parallelism. The first a=1 and a=2 events occur at the same time, so their inter-onset interval must be 0.0.

“Wouldn’t it be simpler if Ppar just overwrote dur?” If it did, then that first event would have a sustain time = dur * legato * stretch = 0 * 0.8 * 1 = 0.0 = stuck node. So it’s absolutely necessary to have a concept of time within one sub-stream (dur), and another concept of time shared among all sub-streams (delta).

Pstretch takes all of this into account. If I wrap the above Ppar inside Pstretch(2, Ppar(...)), then both dur and delta get doubled and the behavior will be consistent.

scaleDur is implemented as ^Pmulp(\dur, x, this) – it only affects dur, not delta. Therefore, to use it after Ppar is too late. You will get the scaled dur, but delta will be based on the old dur.

stretch is implemented as ^Pmulp(\stretch, x, this) – similar problem – delta has already been calculated based on parallelism and will not be altered after the fact by stretch.

It is a bit strange. We have, for instance, Pattern:collect → Pcollect, so one could argue that Pattern:stretch should → Pstretch. But scaleDur, addDur and stretch seem to make up a(n undocumented :confused:) mini-protocol for timing alterations, which are consistent with each other (and all of them are incompatible with post-Ppar usage).

hjh

Thank you for the detailed answer.
I also found that Pmulpre is able to modify \stretch within Ppar. It works with *.play, but not with *.asStream :-/ .

To use Rest with Pstretch you need to add the following to your extensions

+Rest{
	asEvent { ^Event.silent(this) }
}

I realize that in the github thread, I suggested that we “have to” support rests-as-events because the current version of Rest always included playAndDelta (making it possible to yield from an event stream).

I have a bit of a reservation about that, though.

A version of Occam’s Razor for programming might go like this: Given two competing designs to solve a problem, the better design is probably the one that can be described with a simpler principle.

Compare these:

  • “Rest represents a value; if a Rest, representing a value, appears as a member of an Event, then it marks the Event as a silent event.”

  • “Rest represents a value; if a Rest, representing a value, appears as a member of an Event, then it marks the Event as a silent event. Rest may also appear alongside Events as a result of an event pattern/stream. At some point the analogy between Event and Rest will break down; nobody knows exactly where that point is, though.”

By that last point, I mean: We’ve gone from “Rests represent values” → “Rests represent values and may substitute for events within event streams” and this might suggest that rests would be interchangeable with events. Hypothetically, then, somebody could say, “I’d like to write a probability-masking filter pattern. I thought I could force the stream’s result to be a rest by doing stream.next(Rest.new) but this dies upon put.” Intuitively, most of us would say it’s not reasonable to expect .next(Rest.new) to work – and, most of us would say Rest vs Ppar or Pstretch is a reasonable expectation. So, the line between reasonable and unreasonable is somewhere between Pstretch and .next(Rest.new) – and nobody knows exactly where that line is.

So the latter is a complicated, somewhat ambiguous rule whose interpretation might change at any time, based on some developer’s whim.

This leads me to suspect that we should probably discourage yielding Rests from an event stream (even if the class library includes some support for that). Should Rest really be a “now it’s a value, now it’s an event” shape-shifter? Eeeeeeerrrhhhhh… really…? A lot of users seem to want it but is it really a good idea?

hjh

I created a PR for the issue because I think it is a good idea. I am not aware of the moderation strategies for SC, so take it or leave it.

Whaaaa…? I think you’ve misunderstood.

I wasn’t talking about the technical merits of the PR. I was talking about user expectations.

hjh