When answering a related question I noticed that Ppar
pulls one event from its upstream for every of its internal, parallelized streams, i.e. compare:
(p = Ppar([
Pbind(\degree, Pkey(\melody), \dur, 0.2),
Pbind(\degree, Pkey(\melody), \ctranspose, 24, \dur, 0.2)
]) <> Pbind (\melody, Pshuf([1,2,3,4], inf)) );
// with
(p = Ppar([
Pbind(\degree, Pkey(\melody), \dur, 0.2),
Pbind(\degree, Pkey(\melody), \ctranspose, 24, \dur, 0.2)
]) <> Pbind (\melody, Pstutter(2, Pshuf([1,2,3,4], inf))) );
I first thought the “double pull” problem is in Pkey
, but it’s actually in PPar:
(p = Ppar([
Pbind(\degree, Pkey(\melody), \dur, 0.2),
Pbind(\degree, Pkey(\melody2), \ctranspose, 24, \dur, 0.2)
]) <> Pbind (\melody, Pshuf([1,2,3,4], inf), \melody2, Pkey(\melody)));
With the example immediately above \melody
and \melody2
are “in sync” internally in each Pbind, but the two Pbinds inside the Ppar see different values relative to the other Pbind, e.g.
-> [ ( \melody: 3, \degree: 3, \dur: 0.2, \delta: 0.0,
\melody2: 3 ), ( \melody: 4, \degree: 4, \dur: 0.2, \ctranspose: 24,
\delta: 0.2, \melody2: 4 ) ]
-> [ ( \melody: 2, \degree: 2, \dur: 0.2, \delta: 0.0,
\melody2: 2 ), ( \melody: 1, \degree: 1, \dur: 0.2, \ctranspose: 24,
\delta: 0.2, \melody2: 1 ) ]
So, is this behavior of Ppar
to pull (from upstream) a new event for each of its parallelized sub-stream as intended, or can it be considered a bug?
After thinking about it a bit more, it’s probably difficult to avoid this behavior/semantics because Ppar doesn’t know which of its (internal) sub-streams will “go next”, so it can’t meaningfully buffer whatever events it receives from its (data) upstream in order to “round-robin” it.
It’s actually pretty confusing what Ppar does precisely (besides roughly sync-ing its sub-streams) when the sub-streams have different durs, e.g.
(p = Ppar([
Pbind(\dur, 0.2, \x, Pseries()),
Pbind(\ctranspose, 24, \dur, 0.4, \y, Pseries())
]))
r = p.asStream
r.nextN(2, ()) // a few times
Output
-> [ ( \delta: 0.0, \dur: 0.2, \x: 0 ), ( \y: 0, \delta: 0.2, \dur: 0.4, \ctranspose: 24 ) ]
-> [ ( \delta: 0.2, \dur: 0.2, \x: 1 ), ( \y: 1, \delta: 0.0, \dur: 0.4, \ctranspose: 24 ) ]
-> [ ( \delta: 0.2, \dur: 0.2, \x: 2 ), ( \delta: 0.2, \dur: 0.2, \x: 3 ) ]
-> [ ( \y: 2, \delta: 0.0, \dur: 0.4, \ctranspose: 24 ), ( \delta: 0.2, \dur: 0.2, \x: 4 ) ]
-> [ ( \delta: 0.2, \dur: 0.2, \x: 5 ), ( \delta: 2.2204460492503e-16, \dur: 0.2, \x: 6 ) ]
-> [ ( \y: 3, \delta: 0.2, \dur: 0.4, \ctranspose: 24 ), ( \delta: 0.2, \dur: 0.2, \x: 7 ) ]
-> [ ( \delta: 2.2204460492503e-16, \dur: 0.2, \x: 8 ), ( \y: 4, \delta: 0.2, \dur: 0.4, \ctranspose: 24 ) ]
It seems to use delta to override dur (sometimes with zero-length, i.e. delta = 0, or nearly so, events) so one probably needs to be careful not to provide delta on its own substreams… But those zero-length events probably pull/consume data/events from upstream. I would have guessed PpolyPar
was created in part because of this kind of issues with Ppar
, but it also seems to output events with near zero deltas, which probably pull data/events from upstream too.
(p = PpolyPar([
[\dur, 0.2, \x, Pseries()],
[\dur, 0.4, \y, Pseries(), \ctranspose, 24]
], \default!2))
r = p.asStream
r.nextN(2, ()) // a few times
Output
-> [ ( \instrument: default, \group: Group(3199), \dur: 1e-06, \addToCleanup: [ a Function ],
\cleanup: an EventStreamCleanup, \type: on ), ( \instrument: default, \group: Group(3200), \dur: 1e-06, \addToCleanup: [ a Function ],
\cleanup: an EventStreamCleanup, \type: on ) ]
-> [ ( \synths: 0, \dur: 0.2, \args: [ x ], \x: 0,
\delta: 1e-06, \type: set, \id: [ Group(3199) ] ), ( \synths: 1, \dur: 0.4, \args: [ y, ctranspose ], \ctranspose: 24,
\y: 0, \delta: 0.199999, \type: set, \id: [ Group(3200) ] ) ]
-> [ ( \synths: 0, \dur: 0.2, \args: [ x ], \x: 1,
\delta: 0.2, \type: set, \id: [ Group(3199) ] ), ( \synths: 0, \dur: 0.2, \args: [ x ], \x: 2,
\delta: 9.9999999997324e-07, \type: set, \id: [ Group(3199) ] ) ]
-> [ ( \synths: 1, \dur: 0.4, \args: [ y, ctranspose ], \ctranspose: 24,
\y: 1, \delta: 0.199999, \type: set, \id: [ Group(3200) ] ), ( \synths: 0, \dur: 0.2, \args: [ x ], \x: 3,
\delta: 0.2, \type: set, \id: [ Group(3199) ] ) ]
-> [ ( \synths: 0, \dur: 0.2, \args: [ x ], \x: 4,
\delta: 9.9999999991773e-07, \type: set, \id: [ Group(3199) ] ), ( \synths: 1, \dur: 0.4, \args: [ y, ctranspose ], \ctranspose: 24,
\y: 2, \delta: 0.199999, \type: set, \id: [ Group(3200) ] ) ]
Moral of the story seem to be that parallel programming/patterns seem to need idempotent upstream events, or else the results can be quite unpredictable. because it’s hard to say how many times they upstream will get pulled.)